aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/subset/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/subset/__init__.py')
-rw-r--r--Lib/fontTools/subset/__init__.py890
1 files changed, 513 insertions, 377 deletions
diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py
index f687b056..53b440da 100644
--- a/Lib/fontTools/subset/__init__.py
+++ b/Lib/fontTools/subset/__init__.py
@@ -8,12 +8,15 @@ from fontTools.ttLib.tables import otTables
from fontTools.otlLib.maxContextCalc import maxCtxFont
from fontTools.pens.basePen import NullPen
from fontTools.misc.loggingTools import Timer
+from fontTools.subset.util import _add_method, _uniq_sort
from fontTools.subset.cff import *
+from fontTools.subset.svg import *
import sys
import struct
import array
import logging
from collections import Counter, defaultdict
+from functools import reduce
from types import MethodType
__usage__ = "pyftsubset font-file [glyph...] [--option=value]..."
@@ -21,82 +24,100 @@ __usage__ = "pyftsubset font-file [glyph...] [--option=value]..."
__doc__="""\
pyftsubset -- OpenType font subsetter and optimizer
- pyftsubset is an OpenType font subsetter and optimizer, based on fontTools.
- It accepts any TT- or CFF-flavored OpenType (.otf or .ttf) or WOFF (.woff)
- font file. The subsetted glyph set is based on the specified glyphs
- or characters, and specified OpenType layout features.
-
- The tool also performs some size-reducing optimizations, aimed for using
- subset fonts as webfonts. Individual optimizations can be enabled or
- disabled, and are enabled by default when they are safe.
-
-Usage:
- """+__usage__+"""
-
- At least one glyph or one of --gids, --gids-file, --glyphs, --glyphs-file,
- --text, --text-file, --unicodes, or --unicodes-file, must be specified.
-
-Arguments:
- font-file
- The input font file.
- glyph
- Specify one or more glyph identifiers to include in the subset. Must be
- PS glyph names, or the special string '*' to keep the entire glyph set.
-
-Initial glyph set specification:
- These options populate the initial glyph set. Same option can appear
- multiple times, and the results are accummulated.
- --gids=<NNN>[,<NNN>...]
- Specify comma/whitespace-separated list of glyph IDs or ranges as
- decimal numbers. For example, --gids=10-12,14 adds glyphs with
- numbers 10, 11, 12, and 14.
- --gids-file=<path>
- Like --gids but reads from a file. Anything after a '#' on any line
- is ignored as comments.
- --glyphs=<glyphname>[,<glyphname>...]
- Specify comma/whitespace-separated PS glyph names to add to the subset.
- Note that only PS glyph names are accepted, not gidNNN, U+XXXX, etc
- that are accepted on the command line. The special string '*' will keep
- the entire glyph set.
- --glyphs-file=<path>
- Like --glyphs but reads from a file. Anything after a '#' on any line
- is ignored as comments.
- --text=<text>
- Specify characters to include in the subset, as UTF-8 string.
- --text-file=<path>
- Like --text but reads from a file. Newline character are not added to
- the subset.
- --unicodes=<XXXX>[,<XXXX>...]
- Specify comma/whitespace-separated list of Unicode codepoints or
- ranges as hex numbers, optionally prefixed with 'U+', 'u', etc.
- For example, --unicodes=41-5a,61-7a adds ASCII letters, so does
- the more verbose --unicodes=U+0041-005A,U+0061-007A.
- The special strings '*' will choose all Unicode characters mapped
- by the font.
- --unicodes-file=<path>
- Like --unicodes, but reads from a file. Anything after a '#' on any
- line in the file is ignored as comments.
- --ignore-missing-glyphs
- Do not fail if some requested glyphs or gids are not available in
- the font.
- --no-ignore-missing-glyphs
- Stop and fail if some requested glyphs or gids are not available
- in the font. [default]
- --ignore-missing-unicodes [default]
- Do not fail if some requested Unicode characters (including those
- indirectly specified using --text or --text-file) are not available
- in the font.
- --no-ignore-missing-unicodes
- Stop and fail if some requested Unicode characters are not available
- in the font.
- Note the default discrepancy between ignoring missing glyphs versus
- unicodes. This is for historical reasons and in the future
- --no-ignore-missing-unicodes might become default.
-
-Other options:
- For the other options listed below, to see the current value of the option,
- pass a value of '?' to it, with or without a '='.
- Examples:
+pyftsubset is an OpenType font subsetter and optimizer, based on fontTools.
+It accepts any TT- or CFF-flavored OpenType (.otf or .ttf) or WOFF (.woff)
+font file. The subsetted glyph set is based on the specified glyphs
+or characters, and specified OpenType layout features.
+
+The tool also performs some size-reducing optimizations, aimed for using
+subset fonts as webfonts. Individual optimizations can be enabled or
+disabled, and are enabled by default when they are safe.
+
+Usage: """+__usage__+"""
+
+At least one glyph or one of --gids, --gids-file, --glyphs, --glyphs-file,
+--text, --text-file, --unicodes, or --unicodes-file, must be specified.
+
+Args:
+
+font-file
+ The input font file.
+glyph
+ Specify one or more glyph identifiers to include in the subset. Must be
+ PS glyph names, or the special string '*' to keep the entire glyph set.
+
+Initial glyph set specification
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+These options populate the initial glyph set. Same option can appear
+multiple times, and the results are accummulated.
+
+--gids=<NNN>[,<NNN>...]
+ Specify comma/whitespace-separated list of glyph IDs or ranges as decimal
+ numbers. For example, --gids=10-12,14 adds glyphs with numbers 10, 11,
+ 12, and 14.
+
+--gids-file=<path>
+ Like --gids but reads from a file. Anything after a '#' on any line is
+ ignored as comments.
+
+--glyphs=<glyphname>[,<glyphname>...]
+ Specify comma/whitespace-separated PS glyph names to add to the subset.
+ Note that only PS glyph names are accepted, not gidNNN, U+XXXX, etc
+ that are accepted on the command line. The special string '*' will keep
+ the entire glyph set.
+
+--glyphs-file=<path>
+ Like --glyphs but reads from a file. Anything after a '#' on any line
+ is ignored as comments.
+
+--text=<text>
+ Specify characters to include in the subset, as UTF-8 string.
+
+--text-file=<path>
+ Like --text but reads from a file. Newline character are not added to
+ the subset.
+
+--unicodes=<XXXX>[,<XXXX>...]
+ Specify comma/whitespace-separated list of Unicode codepoints or
+ ranges as hex numbers, optionally prefixed with 'U+', 'u', etc.
+ For example, --unicodes=41-5a,61-7a adds ASCII letters, so does
+ the more verbose --unicodes=U+0041-005A,U+0061-007A.
+ The special strings '*' will choose all Unicode characters mapped
+ by the font.
+
+--unicodes-file=<path>
+ Like --unicodes, but reads from a file. Anything after a '#' on any
+ line in the file is ignored as comments.
+
+--ignore-missing-glyphs
+ Do not fail if some requested glyphs or gids are not available in
+ the font.
+
+--no-ignore-missing-glyphs
+ Stop and fail if some requested glyphs or gids are not available
+ in the font. [default]
+
+--ignore-missing-unicodes [default]
+ Do not fail if some requested Unicode characters (including those
+ indirectly specified using --text or --text-file) are not available
+ in the font.
+
+--no-ignore-missing-unicodes
+ Stop and fail if some requested Unicode characters are not available
+ in the font.
+ Note the default discrepancy between ignoring missing glyphs versus
+ unicodes. This is for historical reasons and in the future
+ --no-ignore-missing-unicodes might become default.
+
+Other options
+^^^^^^^^^^^^^
+
+For the other options listed below, to see the current value of the option,
+pass a value of '?' to it, with or without a '='.
+
+Examples::
+
$ pyftsubset --glyph-names?
Current setting for 'glyph-names' is: False
$ ./pyftsubset --name-IDs=?
@@ -105,239 +126,299 @@ Other options:
Current setting for 'hinting' is: True
Current setting for 'hinting' is: False
-Output options:
- --output-file=<path>
- The output font file. If not specified, the subsetted font
- will be saved in as font-file.subset.
- --flavor=<type>
- Specify flavor of output font file. May be 'woff' or 'woff2'.
- Note that WOFF2 requires the Brotli Python extension, available
- at https://github.com/google/brotli
- --with-zopfli
- Use the Google Zopfli algorithm to compress WOFF. The output is 3-8 %
- smaller than pure zlib, but the compression speed is much slower.
- The Zopfli Python bindings are available at:
- https://pypi.python.org/pypi/zopfli
-
-Glyph set expansion:
- These options control how additional glyphs are added to the subset.
- --retain-gids
- Retain glyph indices; just empty glyphs not needed in-place.
- --notdef-glyph
- Add the '.notdef' glyph to the subset (ie, keep it). [default]
- --no-notdef-glyph
- Drop the '.notdef' glyph unless specified in the glyph set. This
- saves a few bytes, but is not possible for Postscript-flavored
- fonts, as those require '.notdef'. For TrueType-flavored fonts,
- this works fine as long as no unsupported glyphs are requested
- from the font.
- --notdef-outline
- Keep the outline of '.notdef' glyph. The '.notdef' glyph outline is
- used when glyphs not supported by the font are to be shown. It is not
- needed otherwise.
- --no-notdef-outline
- When including a '.notdef' glyph, remove its outline. This saves
- a few bytes. [default]
- --recommended-glyphs
- Add glyphs 0, 1, 2, and 3 to the subset, as recommended for
- TrueType-flavored fonts: '.notdef', 'NULL' or '.null', 'CR', 'space'.
- Some legacy software might require this, but no modern system does.
- --no-recommended-glyphs
- Do not add glyphs 0, 1, 2, and 3 to the subset, unless specified in
- glyph set. [default]
- --no-layout-closure
- Do not expand glyph set to add glyphs produced by OpenType layout
- features. Instead, OpenType layout features will be subset to only
- rules that are relevant to the otherwise-specified glyph set.
- --layout-features[+|-]=<feature>[,<feature>...]
- Specify (=), add to (+=) or exclude from (-=) the comma-separated
- set of OpenType layout feature tags that will be preserved.
- Glyph variants used by the preserved features are added to the
- specified subset glyph set. By default, 'calt', 'ccmp', 'clig', 'curs',
- 'dnom', 'frac', 'kern', 'liga', 'locl', 'mark', 'mkmk', 'numr', 'rclt',
- 'rlig', 'rvrn', and all features required for script shaping are
- preserved. To see the full list, try '--layout-features=?'.
- Use '*' to keep all features.
- Multiple --layout-features options can be provided if necessary.
- Examples:
- --layout-features+=onum,pnum,ss01
- * Keep the default set of features and 'onum', 'pnum', 'ss01'.
- --layout-features-='mark','mkmk'
- * Keep the default set of features but drop 'mark' and 'mkmk'.
- --layout-features='kern'
- * Only keep the 'kern' feature, drop all others.
- --layout-features=''
- * Drop all features.
- --layout-features='*'
- * Keep all features.
- --layout-features+=aalt --layout-features-=vrt2
- * Keep default set of features plus 'aalt', but drop 'vrt2'.
- --layout-scripts[+|-]=<script>[,<script>...]
- Specify (=), add to (+=) or exclude from (-=) the comma-separated
- set of OpenType layout script tags that will be preserved. LangSys tags
- can be appended to script tag, separated by '.', for example:
- 'arab.dflt,arab.URD,latn.TRK'. By default all scripts are retained ('*').
-
-Hinting options:
- --hinting
- Keep hinting [default]
- --no-hinting
- Drop glyph-specific hinting and font-wide hinting tables, as well
- as remove hinting-related bits and pieces from other tables (eg. GPOS).
- See --hinting-tables for list of tables that are dropped by default.
- Instructions and hints are stripped from 'glyf' and 'CFF ' tables
- respectively. This produces (sometimes up to 30%) smaller fonts that
- are suitable for extremely high-resolution systems, like high-end
- mobile devices and retina displays.
-
-Optimization options:
- --desubroutinize
- Remove CFF use of subroutinizes. Subroutinization is a way to make CFF
- fonts smaller. For small subsets however, desubroutinizing might make
- the font smaller. It has even been reported that desubroutinized CFF
- fonts compress better (produce smaller output) WOFF and WOFF2 fonts.
- Also see note under --no-hinting.
- --no-desubroutinize [default]
- Leave CFF subroutinizes as is, only throw away unused subroutinizes.
-
-Font table options:
- --drop-tables[+|-]=<table>[,<table>...]
- Specify (=), add to (+=) or exclude from (-=) the comma-separated
- set of tables that will be be dropped.
- By default, the following tables are dropped:
- 'BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'SVG ', 'PCLT', 'LTSH'
- and Graphite tables: 'Feat', 'Glat', 'Gloc', 'Silf', 'Sill'.
- The tool will attempt to subset the remaining tables.
- Examples:
- --drop-tables-='SVG '
- * Drop the default set of tables but keep 'SVG '.
- --drop-tables+=GSUB
- * Drop the default set of tables and 'GSUB'.
- --drop-tables=DSIG
- * Only drop the 'DSIG' table, keep all others.
- --drop-tables=
- * Keep all tables.
- --no-subset-tables+=<table>[,<table>...]
- Add to the set of tables that will not be subsetted.
- By default, the following tables are included in this list, as
- they do not need subsetting (ignore the fact that 'loca' is listed
- here): 'gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2', 'loca', 'name',
- 'cvt ', 'fpgm', 'prep', 'VMDX', 'DSIG', 'CPAL', 'MVAR', 'cvar', 'STAT'.
- By default, tables that the tool does not know how to subset and are not
- specified here will be dropped from the font, unless --passthrough-tables
- option is passed.
- Example:
- --no-subset-tables+=FFTM
- * Keep 'FFTM' table in the font by preventing subsetting.
- --passthrough-tables
- Do not drop tables that the tool does not know how to subset.
- --no-passthrough-tables
- Tables that the tool does not know how to subset and are not specified
- in --no-subset-tables will be dropped from the font. [default]
- --hinting-tables[-]=<table>[,<table>...]
- Specify (=), add to (+=) or exclude from (-=) the list of font-wide
- hinting tables that will be dropped if --no-hinting is specified,
- Examples:
- --hinting-tables-='VDMX'
- * Drop font-wide hinting tables except 'VDMX'.
- --hinting-tables=''
- * Keep all font-wide hinting tables (but strip hints from glyphs).
- --legacy-kern
- Keep TrueType 'kern' table even when OpenType 'GPOS' is available.
- --no-legacy-kern
- Drop TrueType 'kern' table if OpenType 'GPOS' is available. [default]
-
-Font naming options:
- These options control what is retained in the 'name' table. For numerical
- codes, see: http://www.microsoft.com/typography/otspec/name.htm
- --name-IDs[+|-]=<nameID>[,<nameID>...]
- Specify (=), add to (+=) or exclude from (-=) the set of 'name' table
- entry nameIDs that will be preserved. By default, only nameIDs between 0
- and 6 are preserved, the rest are dropped. Use '*' to keep all entries.
- Examples:
- --name-IDs+=7,8,9
- * Also keep Trademark, Manufacturer and Designer name entries.
- --name-IDs=''
- * Drop all 'name' table entries.
- --name-IDs='*'
- * keep all 'name' table entries
- --name-legacy
- Keep legacy (non-Unicode) 'name' table entries (0.x, 1.x etc.).
- XXX Note: This might be needed for some fonts that have no Unicode name
- entires for English. See: https://github.com/fonttools/fonttools/issues/146
- --no-name-legacy
- Drop legacy (non-Unicode) 'name' table entries [default]
- --name-languages[+|-]=<langID>[,<langID>]
- Specify (=), add to (+=) or exclude from (-=) the set of 'name' table
- langIDs that will be preserved. By default only records with langID
- 0x0409 (English) are preserved. Use '*' to keep all langIDs.
- --obfuscate-names
- Make the font unusable as a system font by replacing name IDs 1, 2, 3, 4,
- and 6 with dummy strings (it is still fully functional as webfont).
-
-Glyph naming and encoding options:
- --glyph-names
- Keep PS glyph names in TT-flavored fonts. In general glyph names are
- not needed for correct use of the font. However, some PDF generators
- and PDF viewers might rely on glyph names to extract Unicode text
- from PDF documents.
- --no-glyph-names
- Drop PS glyph names in TT-flavored fonts, by using 'post' table
- version 3.0. [default]
- --legacy-cmap
- Keep the legacy 'cmap' subtables (0.x, 1.x, 4.x etc.).
- --no-legacy-cmap
- Drop the legacy 'cmap' subtables. [default]
- --symbol-cmap
- Keep the 3.0 symbol 'cmap'.
- --no-symbol-cmap
- Drop the 3.0 symbol 'cmap'. [default]
-
-Other font-specific options:
- --recalc-bounds
- Recalculate font bounding boxes.
- --no-recalc-bounds
- Keep original font bounding boxes. This is faster and still safe
- for all practical purposes. [default]
- --recalc-timestamp
- Set font 'modified' timestamp to current time.
- --no-recalc-timestamp
- Do not modify font 'modified' timestamp. [default]
- --canonical-order
- Order tables as recommended in the OpenType standard. This is not
- required by the standard, nor by any known implementation.
- --no-canonical-order
- Keep original order of font tables. This is faster. [default]
- --prune-unicode-ranges
- Update the 'OS/2 ulUnicodeRange*' bits after subsetting. The Unicode
- ranges defined in the OpenType specification v1.7 are intersected with
- the Unicode codepoints specified in the font's Unicode 'cmap' subtables:
- when no overlap is found, the bit will be switched off. However, it will
- *not* be switched on if an intersection is found. [default]
- --no-prune-unicode-ranges
- Don't change the 'OS/2 ulUnicodeRange*' bits.
- --recalc-average-width
- Update the 'OS/2 xAvgCharWidth' field after subsetting.
- --no-recalc-average-width
- Don't change the 'OS/2 xAvgCharWidth' field. [default]
- --recalc-max-context
- Update the 'OS/2 usMaxContext' field after subsetting.
- --no-recalc-max-context
- Don't change the 'OS/2 usMaxContext' field. [default]
- --font-number=<number>
- Select font number for TrueType Collection (.ttc/.otc), starting from 0.
-
-Application options:
- --verbose
- Display verbose information of the subsetting process.
- --timing
- Display detailed timing information of the subsetting process.
- --xml
- Display the TTX XML representation of subsetted font.
-
-Example:
- Produce a subset containing the characters ' !"#$%' without performing
- size-reducing optimizations:
+Output options
+^^^^^^^^^^^^^^
+
+--output-file=<path>
+ The output font file. If not specified, the subsetted font
+ will be saved in as font-file.subset.
+
+--flavor=<type>
+ Specify flavor of output font file. May be 'woff' or 'woff2'.
+ Note that WOFF2 requires the Brotli Python extension, available
+ at https://github.com/google/brotli
+
+--with-zopfli
+ Use the Google Zopfli algorithm to compress WOFF. The output is 3-8 %
+ smaller than pure zlib, but the compression speed is much slower.
+ The Zopfli Python bindings are available at:
+ https://pypi.python.org/pypi/zopfli
+
+Glyph set expansion
+^^^^^^^^^^^^^^^^^^^
+
+These options control how additional glyphs are added to the subset.
+
+--retain-gids
+ Retain glyph indices; just empty glyphs not needed in-place.
+
+--notdef-glyph
+ Add the '.notdef' glyph to the subset (ie, keep it). [default]
+
+--no-notdef-glyph
+ Drop the '.notdef' glyph unless specified in the glyph set. This
+ saves a few bytes, but is not possible for Postscript-flavored
+ fonts, as those require '.notdef'. For TrueType-flavored fonts,
+ this works fine as long as no unsupported glyphs are requested
+ from the font.
+
+--notdef-outline
+ Keep the outline of '.notdef' glyph. The '.notdef' glyph outline is
+ used when glyphs not supported by the font are to be shown. It is not
+ needed otherwise.
+
+--no-notdef-outline
+ When including a '.notdef' glyph, remove its outline. This saves
+ a few bytes. [default]
+
+--recommended-glyphs
+ Add glyphs 0, 1, 2, and 3 to the subset, as recommended for
+ TrueType-flavored fonts: '.notdef', 'NULL' or '.null', 'CR', 'space'.
+ Some legacy software might require this, but no modern system does.
+
+--no-recommended-glyphs
+ Do not add glyphs 0, 1, 2, and 3 to the subset, unless specified in
+ glyph set. [default]
+
+--no-layout-closure
+ Do not expand glyph set to add glyphs produced by OpenType layout
+ features. Instead, OpenType layout features will be subset to only
+ rules that are relevant to the otherwise-specified glyph set.
+
+--layout-features[+|-]=<feature>[,<feature>...]
+ Specify (=), add to (+=) or exclude from (-=) the comma-separated
+ set of OpenType layout feature tags that will be preserved.
+ Glyph variants used by the preserved features are added to the
+ specified subset glyph set. By default, 'calt', 'ccmp', 'clig', 'curs',
+ 'dnom', 'frac', 'kern', 'liga', 'locl', 'mark', 'mkmk', 'numr', 'rclt',
+ 'rlig', 'rvrn', and all features required for script shaping are
+ preserved. To see the full list, try '--layout-features=?'.
+ Use '*' to keep all features.
+ Multiple --layout-features options can be provided if necessary.
+ Examples:
+
+ --layout-features+=onum,pnum,ss01
+ * Keep the default set of features and 'onum', 'pnum', 'ss01'.
+ --layout-features-='mark','mkmk'
+ * Keep the default set of features but drop 'mark' and 'mkmk'.
+ --layout-features='kern'
+ * Only keep the 'kern' feature, drop all others.
+ --layout-features=''
+ * Drop all features.
+ --layout-features='*'
+ * Keep all features.
+ --layout-features+=aalt --layout-features-=vrt2
+ * Keep default set of features plus 'aalt', but drop 'vrt2'.
+
+--layout-scripts[+|-]=<script>[,<script>...]
+ Specify (=), add to (+=) or exclude from (-=) the comma-separated
+ set of OpenType layout script tags that will be preserved. LangSys tags
+ can be appended to script tag, separated by '.', for example:
+ 'arab.dflt,arab.URD,latn.TRK'. By default all scripts are retained ('*').
+
+Hinting options
+^^^^^^^^^^^^^^^
+
+--hinting
+ Keep hinting [default]
+
+--no-hinting
+ Drop glyph-specific hinting and font-wide hinting tables, as well
+ as remove hinting-related bits and pieces from other tables (eg. GPOS).
+ See --hinting-tables for list of tables that are dropped by default.
+ Instructions and hints are stripped from 'glyf' and 'CFF ' tables
+ respectively. This produces (sometimes up to 30%) smaller fonts that
+ are suitable for extremely high-resolution systems, like high-end
+ mobile devices and retina displays.
+
+Optimization options
+^^^^^^^^^^^^^^^^^^^^
+
+--desubroutinize
+ Remove CFF use of subroutinizes. Subroutinization is a way to make CFF
+ fonts smaller. For small subsets however, desubroutinizing might make
+ the font smaller. It has even been reported that desubroutinized CFF
+ fonts compress better (produce smaller output) WOFF and WOFF2 fonts.
+ Also see note under --no-hinting.
+
+--no-desubroutinize [default]
+ Leave CFF subroutinizes as is, only throw away unused subroutinizes.
+
+Font table options
+^^^^^^^^^^^^^^^^^^
+
+--drop-tables[+|-]=<table>[,<table>...]
+ Specify (=), add to (+=) or exclude from (-=) the comma-separated
+ set of tables that will be be dropped.
+ By default, the following tables are dropped:
+ 'BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'PCLT', 'LTSH'
+ and Graphite tables: 'Feat', 'Glat', 'Gloc', 'Silf', 'Sill'.
+ The tool will attempt to subset the remaining tables.
+
+ Examples:
+
+ --drop-tables-='BASE'
+ * Drop the default set of tables but keep 'BASE'.
+
+ --drop-tables+=GSUB
+ * Drop the default set of tables and 'GSUB'.
+
+ --drop-tables=DSIG
+ * Only drop the 'DSIG' table, keep all others.
+
+ --drop-tables=
+ * Keep all tables.
+
+--no-subset-tables+=<table>[,<table>...]
+ Add to the set of tables that will not be subsetted.
+ By default, the following tables are included in this list, as
+ they do not need subsetting (ignore the fact that 'loca' is listed
+ here): 'gasp', 'head', 'hhea', 'maxp', 'vhea', 'OS/2', 'loca', 'name',
+ 'cvt ', 'fpgm', 'prep', 'VMDX', 'DSIG', 'CPAL', 'MVAR', 'cvar', 'STAT'.
+ By default, tables that the tool does not know how to subset and are not
+ specified here will be dropped from the font, unless --passthrough-tables
+ option is passed.
+
+ Example:
+
+ --no-subset-tables+=FFTM
+ * Keep 'FFTM' table in the font by preventing subsetting.
+
+--passthrough-tables
+ Do not drop tables that the tool does not know how to subset.
+
+--no-passthrough-tables
+ Tables that the tool does not know how to subset and are not specified
+ in --no-subset-tables will be dropped from the font. [default]
+
+--hinting-tables[-]=<table>[,<table>...]
+ Specify (=), add to (+=) or exclude from (-=) the list of font-wide
+ hinting tables that will be dropped if --no-hinting is specified.
+
+ Examples:
+
+ --hinting-tables-='VDMX'
+ * Drop font-wide hinting tables except 'VDMX'.
+ --hinting-tables=''
+ * Keep all font-wide hinting tables (but strip hints from glyphs).
+
+--legacy-kern
+ Keep TrueType 'kern' table even when OpenType 'GPOS' is available.
+
+--no-legacy-kern
+ Drop TrueType 'kern' table if OpenType 'GPOS' is available. [default]
+
+Font naming options
+^^^^^^^^^^^^^^^^^^^
+
+These options control what is retained in the 'name' table. For numerical
+codes, see: http://www.microsoft.com/typography/otspec/name.htm
+
+--name-IDs[+|-]=<nameID>[,<nameID>...]
+ Specify (=), add to (+=) or exclude from (-=) the set of 'name' table
+ entry nameIDs that will be preserved. By default, only nameIDs between 0
+ and 6 are preserved, the rest are dropped. Use '*' to keep all entries.
+
+ Examples:
+
+ --name-IDs+=7,8,9
+ * Also keep Trademark, Manufacturer and Designer name entries.
+ --name-IDs=''
+ * Drop all 'name' table entries.
+ --name-IDs='*'
+ * keep all 'name' table entries
+
+--name-legacy
+ Keep legacy (non-Unicode) 'name' table entries (0.x, 1.x etc.).
+ XXX Note: This might be needed for some fonts that have no Unicode name
+ entires for English. See: https://github.com/fonttools/fonttools/issues/146
+
+--no-name-legacy
+ Drop legacy (non-Unicode) 'name' table entries [default]
+
+--name-languages[+|-]=<langID>[,<langID>]
+ Specify (=), add to (+=) or exclude from (-=) the set of 'name' table
+ langIDs that will be preserved. By default only records with langID
+ 0x0409 (English) are preserved. Use '*' to keep all langIDs.
+
+--obfuscate-names
+ Make the font unusable as a system font by replacing name IDs 1, 2, 3, 4,
+ and 6 with dummy strings (it is still fully functional as webfont).
+
+Glyph naming and encoding options
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+--glyph-names
+ Keep PS glyph names in TT-flavored fonts. In general glyph names are
+ not needed for correct use of the font. However, some PDF generators
+ and PDF viewers might rely on glyph names to extract Unicode text
+ from PDF documents.
+--no-glyph-names
+ Drop PS glyph names in TT-flavored fonts, by using 'post' table
+ version 3.0. [default]
+--legacy-cmap
+ Keep the legacy 'cmap' subtables (0.x, 1.x, 4.x etc.).
+--no-legacy-cmap
+ Drop the legacy 'cmap' subtables. [default]
+--symbol-cmap
+ Keep the 3.0 symbol 'cmap'.
+--no-symbol-cmap
+ Drop the 3.0 symbol 'cmap'. [default]
+
+Other font-specific options
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+--recalc-bounds
+ Recalculate font bounding boxes.
+--no-recalc-bounds
+ Keep original font bounding boxes. This is faster and still safe
+ for all practical purposes. [default]
+--recalc-timestamp
+ Set font 'modified' timestamp to current time.
+--no-recalc-timestamp
+ Do not modify font 'modified' timestamp. [default]
+--canonical-order
+ Order tables as recommended in the OpenType standard. This is not
+ required by the standard, nor by any known implementation.
+--no-canonical-order
+ Keep original order of font tables. This is faster. [default]
+--prune-unicode-ranges
+ Update the 'OS/2 ulUnicodeRange*' bits after subsetting. The Unicode
+ ranges defined in the OpenType specification v1.7 are intersected with
+ the Unicode codepoints specified in the font's Unicode 'cmap' subtables:
+ when no overlap is found, the bit will be switched off. However, it will
+ *not* be switched on if an intersection is found. [default]
+--no-prune-unicode-ranges
+ Don't change the 'OS/2 ulUnicodeRange*' bits.
+--recalc-average-width
+ Update the 'OS/2 xAvgCharWidth' field after subsetting.
+--no-recalc-average-width
+ Don't change the 'OS/2 xAvgCharWidth' field. [default]
+--recalc-max-context
+ Update the 'OS/2 usMaxContext' field after subsetting.
+--no-recalc-max-context
+ Don't change the 'OS/2 usMaxContext' field. [default]
+--font-number=<number>
+ Select font number for TrueType Collection (.ttc/.otc), starting from 0.
+--pretty-svg
+ When subsetting SVG table, use lxml pretty_print=True option to indent
+ the XML output (only recommended for debugging purposes).
+
+Application options
+^^^^^^^^^^^^^^^^^^^
+
+--verbose
+ Display verbose information of the subsetting process.
+--timing
+ Display detailed timing information of the subsetting process.
+--xml
+ Display the TTX XML representation of subsetted font.
+
+Example
+^^^^^^^
+
+Produce a subset containing the characters ' !"#$%' without performing
+size-reducing optimizations::
$ pyftsubset font.ttf --unicodes="U+0020-0025" \\
--layout-features='*' --glyph-names --symbol-cmap --legacy-cmap \\
@@ -362,26 +443,6 @@ log.glyphs = MethodType(_log_glyphs, log)
timer = Timer(logger=logging.getLogger("fontTools.subset.timer"))
-def _add_method(*clazzes):
- """Returns a decorator function that adds a new method to one or
- more classes."""
- def wrapper(method):
- done = []
- for clazz in clazzes:
- if clazz in done: continue # Support multiple names of a clazz
- done.append(clazz)
- assert clazz.__name__ != 'DefaultTable', \
- 'Oops, table class not found.'
- assert not hasattr(clazz, method.__name__), \
- "Oops, class '%s' has method '%s'." % (clazz.__name__,
- method.__name__)
- setattr(clazz, method.__name__, method)
- return None
- return wrapper
-
-def _uniq_sort(l):
- return sorted(set(l))
-
def _dict_subset(d, glyphs):
return {g:d[g] for g in glyphs}
@@ -527,6 +588,17 @@ def subset_glyphs(self, s):
else:
assert 0, "unknown format: %s" % self.Format
+@_add_method(otTables.Device)
+def is_hinting(self):
+ return self.DeltaFormat in (1,2,3)
+
+@_add_method(otTables.ValueRecord)
+def prune_hints(self):
+ for name in ['XPlaDevice', 'YPlaDevice', 'XAdvDevice', 'YAdvDevice']:
+ v = getattr(self, name, None)
+ if v is not None and v.is_hinting():
+ delattr(self, name)
+
@_add_method(otTables.SinglePos)
def subset_glyphs(self, s):
if self.Format == 1:
@@ -543,14 +615,27 @@ def subset_glyphs(self, s):
@_add_method(otTables.SinglePos)
def prune_post_subset(self, font, options):
- if not options.hinting:
- # Drop device tables
- self.ValueFormat &= ~0x00F0
+ if self.Value is None:
+ assert self.ValueFormat == 0
+ return True
+
+ # Shrink ValueFormat
+ if self.Format == 1:
+ if not options.hinting:
+ self.Value.prune_hints()
+ self.ValueFormat = self.Value.getEffectiveFormat()
+ elif self.Format == 2:
+ if not options.hinting:
+ for v in self.Value:
+ v.prune_hints()
+ self.ValueFormat = reduce(int.__or__, [v.getEffectiveFormat() for v in self.Value], 0)
+
# Downgrade to Format 1 if all ValueRecords are the same
if self.Format == 2 and all(v == self.Value[0] for v in self.Value):
self.Format = 1
self.Value = self.Value[0] if self.ValueFormat != 0 else None
del self.ValueCount
+
return True
@_add_method(otTables.PairPos)
@@ -587,10 +672,22 @@ def subset_glyphs(self, s):
@_add_method(otTables.PairPos)
def prune_post_subset(self, font, options):
if not options.hinting:
- # Drop device tables
- self.ValueFormat1 &= ~0x00F0
- self.ValueFormat2 &= ~0x00F0
- return True
+ attr1, attr2 = {
+ 1: ('PairSet', 'PairValueRecord'),
+ 2: ('Class1Record', 'Class2Record'),
+ }[self.Format]
+
+ self.ValueFormat1 = self.ValueFormat2 = 0
+ for row in getattr(self, attr1):
+ for r in getattr(row, attr2):
+ if r.Value1:
+ r.Value1.prune_hints()
+ self.ValueFormat1 |= r.Value1.getEffectiveFormat()
+ if r.Value2:
+ r.Value2.prune_hints()
+ self.ValueFormat2 |= r.Value2.getEffectiveFormat()
+
+ return bool(self.ValueFormat1 | self.ValueFormat2)
@_add_method(otTables.CursivePos)
def subset_glyphs(self, s):
@@ -606,9 +703,15 @@ def subset_glyphs(self, s):
@_add_method(otTables.Anchor)
def prune_hints(self):
- # Drop device tables / contour anchor point
- self.ensureDecompiled()
- self.Format = 1
+ if self.Format == 2:
+ self.Format = 1
+ elif self.Format == 3:
+ for name in ('XDeviceTable', 'YDeviceTable'):
+ v = getattr(self, name, None)
+ if v is not None and v.is_hinting():
+ setattr(self, name, None)
+ if self.XDeviceTable is None and self.YDeviceTable is None:
+ self.Format = 1
@_add_method(otTables.CursivePos)
def prune_post_subset(self, font, options):
@@ -713,7 +816,6 @@ def subset_glyphs(self, s):
@_add_method(otTables.MarkMarkPos)
def prune_post_subset(self, font, options):
if not options.hinting:
- # Drop device tables or contour anchor point
for m in self.Mark1Array.MarkRecord:
if m.MarkAnchor:
m.MarkAnchor.prune_hints()
@@ -976,7 +1078,7 @@ def closure_glyphs(self, s, cur_glyphs):
chaos.update(range(seqi, len(getattr(r, c.Input))+2))
lookup.closure_glyphs(s, cur_glyphs=pos_glyphs)
elif self.Format == 3:
- if not all(x.intersect(s.glyphs) for x in c.RuleData(self)):
+ if not all(x is not None and x.intersect(s.glyphs) for x in c.RuleData(self)):
return []
r = self
input_coverages = getattr(r, c.Input)
@@ -1071,7 +1173,7 @@ def subset_glyphs(self, s):
return bool(rss)
elif self.Format == 3:
- return all(x.subset(s.glyphs) for x in c.RuleData(self))
+ return all(x is not None and x.subset(s.glyphs) for x in c.RuleData(self))
else:
assert 0, "unknown format: %s" % self.Format
@@ -1266,7 +1368,13 @@ def subset_lookups(self, lookup_indices):
self.LookupListIndex = [lookup_indices.index(l)
for l in self.LookupListIndex]
self.LookupCount = len(self.LookupListIndex)
- return self.LookupCount or self.FeatureParams
+ # keep 'size' feature even if it contains no lookups; but drop any other
+ # empty feature (e.g. FeatureParams for stylistic set names)
+ # https://github.com/fonttools/fonttools/issues/2324
+ return (
+ self.LookupCount or
+ isinstance(self.FeatureParams, otTables.FeatureParamsSize)
+ )
@_add_method(otTables.FeatureList)
def subset_lookups(self, lookup_indices):
@@ -2007,7 +2115,7 @@ def closure_glyphs(self, s):
self.ColorLayers = self._decompileColorLayersV0(self.table)
self.ColorLayersV1 = {
rec.BaseGlyph: rec.Paint
- for rec in self.table.BaseGlyphV1List.BaseGlyphV1Record
+ for rec in self.table.BaseGlyphList.BaseGlyphPaintRecord
}
decompose = s.glyphs
@@ -2031,31 +2139,48 @@ def subset_glyphs(self, s):
from fontTools.colorLib.unbuilder import unbuildColrV1
from fontTools.colorLib.builder import buildColrV1, populateCOLRv0
+ # only include glyphs after COLR closure, which in turn comes after cmap and GSUB
+ # closure, but importantly before glyf/CFF closures. COLR layers can refer to
+ # composite glyphs, and that's ok, since glyf/CFF closures happen after COLR closure
+ # and take care of those. If we also included glyphs resulting from glyf/CFF closures
+ # when deciding which COLR base glyphs to retain, then we may end up with a situation
+ # whereby a COLR base glyph is kept, not because directly requested (cmap)
+ # or substituted (GSUB) or referenced by another COLRv1 PaintColrGlyph, but because
+ # it corresponds to (has same GID as) a non-COLR glyph that happens to be used as a
+ # component in glyf or CFF table. Best case scenario we retain more glyphs than
+ # required; worst case we retain incomplete COLR records that try to reference
+ # glyphs that are no longer in the final subset font.
+ # https://github.com/fonttools/fonttools/issues/2461
+ s.glyphs = s.glyphs_colred
+
self.ColorLayers = {g: self.ColorLayers[g] for g in s.glyphs if g in self.ColorLayers}
if self.version == 0:
return bool(self.ColorLayers)
- colorGlyphsV1 = unbuildColrV1(self.table.LayerV1List, self.table.BaseGlyphV1List)
- self.table.LayerV1List, self.table.BaseGlyphV1List = buildColrV1(
+ colorGlyphsV1 = unbuildColrV1(self.table.LayerList, self.table.BaseGlyphList)
+ self.table.LayerList, self.table.BaseGlyphList = buildColrV1(
{g: colorGlyphsV1[g] for g in colorGlyphsV1 if g in s.glyphs}
)
del self.ColorLayersV1
+ if self.table.ClipList is not None:
+ clips = self.table.ClipList.clips
+ self.table.ClipList.clips = {g: clips[g] for g in clips if g in s.glyphs}
+
layersV0 = self.ColorLayers
- if not self.table.BaseGlyphV1List.BaseGlyphV1Record:
+ if not self.table.BaseGlyphList.BaseGlyphPaintRecord:
# no more COLRv1 glyphs: downgrade to version 0
self.version = 0
del self.table
return bool(layersV0)
- if layersV0:
- populateCOLRv0(
- self.table,
- {
- g: [(layer.name, layer.colorID) for layer in layersV0[g]]
- for g in layersV0
- },
- )
+ populateCOLRv0(
+ self.table,
+ {
+ g: [(layer.name, layer.colorID) for layer in layersV0[g]]
+ for g in layersV0
+ },
+ )
del self.ColorLayers
# TODO: also prune ununsed varIndices in COLR.VarStore
@@ -2070,11 +2195,11 @@ def prune_post_subset(self, font, options):
colors_by_index = defaultdict(list)
def collect_colors_by_index(paint):
- if hasattr(paint, "Color"): # either solid colors...
- colors_by_index[paint.Color.PaletteIndex].append(paint.Color)
+ if hasattr(paint, "PaletteIndex"): # either solid colors...
+ colors_by_index[paint.PaletteIndex].append(paint)
elif hasattr(paint, "ColorLine"): # ... or gradient color stops
for stop in paint.ColorLine.ColorStop:
- colors_by_index[stop.Color.PaletteIndex].append(stop.Color)
+ colors_by_index[stop.PaletteIndex].append(stop)
if colr.version == 0:
for layers in colr.ColorLayers.values():
@@ -2084,10 +2209,12 @@ def prune_post_subset(self, font, options):
if colr.table.LayerRecordArray:
for layer in colr.table.LayerRecordArray.LayerRecord:
colors_by_index[layer.PaletteIndex].append(layer)
- for record in colr.table.BaseGlyphV1List.BaseGlyphV1Record:
+ for record in colr.table.BaseGlyphList.BaseGlyphPaintRecord:
record.Paint.traverse(colr.table, collect_colors_by_index)
- retained_palette_indices = set(colors_by_index.keys())
+ # don't remap palette entry index 0xFFFF, this is always the foreground color
+ # https://github.com/fonttools/fonttools/issues/2257
+ retained_palette_indices = set(colors_by_index.keys()) - {0xFFFF}
for palette in self.palettes:
palette[:] = [c for i, c in enumerate(palette) if i in retained_palette_indices]
assert len(palette) == len(retained_palette_indices)
@@ -2201,7 +2328,7 @@ def subset_glyphs(self, s):
def remapComponentsFast(self, glyphidmap):
if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
return # Not composite
- data = array.array("B", self.data)
+ data = self.data = bytearray(self.data)
i = 10
more = 1
while more:
@@ -2221,8 +2348,6 @@ def remapComponentsFast(self, glyphidmap):
elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
more = flags & 0x0020 # MORE_COMPONENTS
- self.data = data.tobytes()
-
@_add_method(ttLib.getTableClass('glyf'))
def closure_glyphs(self, s):
glyphSet = self.glyphs
@@ -2245,7 +2370,7 @@ def prune_pre_subset(self, font, options):
g = self[self.glyphOrder[0]]
# Yay, easy!
g.__dict__.clear()
- g.data = ""
+ g.data = b''
return True
@_add_method(ttLib.getTableClass('glyf'))
@@ -2260,7 +2385,7 @@ def subset_glyphs(self, s):
Glyph = ttLib.getTableModule('glyf').Glyph
for g in s.glyphs_emptied:
self.glyphs[g] = Glyph()
- self.glyphs[g].data = ''
+ self.glyphs[g].data = b''
self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs or g in s.glyphs_emptied]
# Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset.
return True
@@ -2454,7 +2579,7 @@ class Options(object):
# spaces in tag names (e.g. "SVG ", "cvt ") are stripped by the argument parser
_drop_tables_default = ['BASE', 'JSTF', 'DSIG', 'EBDT', 'EBLC',
- 'EBSC', 'SVG', 'PCLT', 'LTSH']
+ 'EBSC', 'PCLT', 'LTSH']
_drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite
_no_subset_tables_default = ['avar', 'fvar',
'gasp', 'head', 'hhea', 'maxp',
@@ -2521,6 +2646,7 @@ class Options(object):
self.timing = False
self.xml = False
self.font_number = -1
+ self.pretty_svg = False
self.set(**kwargs)
@@ -2659,7 +2785,7 @@ class Subsetter(object):
def _closure_glyphs(self, font):
realGlyphs = set(font.getGlyphOrder())
- glyph_order = font.getGlyphOrder()
+ self.orig_glyph_order = glyph_order = font.getGlyphOrder()
self.glyphs_requested = set()
self.glyphs_requested.update(self.glyph_names_requested)
@@ -2739,6 +2865,7 @@ class Subsetter(object):
log.info("Closed glyph list over '%s': %d glyphs after",
table, len(self.glyphs))
log.glyphs(self.glyphs, font=font)
+ setattr(self, f"glyphs_{table.lower()}ed", frozenset(self.glyphs))
if 'glyf' in font:
with timer("close glyph list over 'glyf'"):
@@ -2778,6 +2905,24 @@ class Subsetter(object):
self.reverseEmptiedGlyphMap = {g:order[g] for g in self.glyphs_emptied}
+ if not self.options.retain_gids:
+ new_glyph_order = [
+ g for g in glyph_order if g in self.glyphs_retained
+ ]
+ else:
+ new_glyph_order = [
+ g for g in glyph_order
+ if font.getGlyphID(g) <= self.last_retained_order
+ ]
+ # We'll call font.setGlyphOrder() at the end of _subset_glyphs when all
+ # tables have been subsetted. Below, we use the new glyph order to get
+ # a map from old to new glyph indices, which can be useful when
+ # subsetting individual tables (e.g. SVG) that refer to GIDs.
+ self.new_glyph_order = new_glyph_order
+ self.glyph_index_map = {
+ order[new_glyph_order[i]]: i
+ for i in range(len(new_glyph_order))
+ }
log.info("Retaining %d glyphs", len(self.glyphs_retained))
@@ -2807,14 +2952,7 @@ class Subsetter(object):
del font[tag]
with timer("subset GlyphOrder"):
- glyphOrder = font.getGlyphOrder()
- if not self.options.retain_gids:
- glyphOrder = [g for g in glyphOrder if g in self.glyphs_retained]
- else:
- glyphOrder = [g for g in glyphOrder if font.getGlyphID(g) <= self.last_retained_order]
-
- font.setGlyphOrder(glyphOrder)
- font._buildReverseGlyphOrderDict()
+ font.setGlyphOrder(self.new_glyph_order)
def _prune_post_subset(self, font):
@@ -2863,13 +3001,11 @@ class Subsetter(object):
@timer("load font")
def load_font(fontFile,
options,
- allowVID=False,
checkChecksums=0,
dontLoadGlyphNames=False,
lazy=True):
font = ttLib.TTFont(fontFile,
- allowVID=allowVID,
checkChecksums=checkChecksums,
recalcBBoxes=options.recalc_bounds,
recalcTimestamp=options.recalc_timestamp,