diff options
Diffstat (limited to 'Lib/fontTools/subset/__init__.py')
-rw-r--r-- | Lib/fontTools/subset/__init__.py | 890 |
1 files changed, 377 insertions, 513 deletions
diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index 53b440da..f687b056 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -8,15 +8,12 @@ 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]..." @@ -24,100 +21,82 @@ __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. - -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 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 --glyph-names? Current setting for 'glyph-names' is: False $ ./pyftsubset --name-IDs=? @@ -126,299 +105,239 @@ Examples:: 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', '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:: +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: $ pyftsubset font.ttf --unicodes="U+0020-0025" \\ --layout-features='*' --glyph-names --symbol-cmap --legacy-cmap \\ @@ -443,6 +362,26 @@ 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} @@ -588,17 +527,6 @@ 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: @@ -615,27 +543,14 @@ def subset_glyphs(self, s): @_add_method(otTables.SinglePos) def prune_post_subset(self, font, options): - 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) - + if not options.hinting: + # Drop device tables + self.ValueFormat &= ~0x00F0 # 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) @@ -672,22 +587,10 @@ def subset_glyphs(self, s): @_add_method(otTables.PairPos) def prune_post_subset(self, font, options): if not options.hinting: - 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) + # Drop device tables + self.ValueFormat1 &= ~0x00F0 + self.ValueFormat2 &= ~0x00F0 + return True @_add_method(otTables.CursivePos) def subset_glyphs(self, s): @@ -703,15 +606,9 @@ def subset_glyphs(self, s): @_add_method(otTables.Anchor) def prune_hints(self): - 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 + # Drop device tables / contour anchor point + self.ensureDecompiled() + self.Format = 1 @_add_method(otTables.CursivePos) def prune_post_subset(self, font, options): @@ -816,6 +713,7 @@ 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() @@ -1078,7 +976,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 is not None and x.intersect(s.glyphs) for x in c.RuleData(self)): + if not all(x.intersect(s.glyphs) for x in c.RuleData(self)): return [] r = self input_coverages = getattr(r, c.Input) @@ -1173,7 +1071,7 @@ def subset_glyphs(self, s): return bool(rss) elif self.Format == 3: - return all(x is not None and x.subset(s.glyphs) for x in c.RuleData(self)) + return all(x.subset(s.glyphs) for x in c.RuleData(self)) else: assert 0, "unknown format: %s" % self.Format @@ -1368,13 +1266,7 @@ def subset_lookups(self, lookup_indices): self.LookupListIndex = [lookup_indices.index(l) for l in self.LookupListIndex] self.LookupCount = len(self.LookupListIndex) - # 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) - ) + return self.LookupCount or self.FeatureParams @_add_method(otTables.FeatureList) def subset_lookups(self, lookup_indices): @@ -2115,7 +2007,7 @@ def closure_glyphs(self, s): self.ColorLayers = self._decompileColorLayersV0(self.table) self.ColorLayersV1 = { rec.BaseGlyph: rec.Paint - for rec in self.table.BaseGlyphList.BaseGlyphPaintRecord + for rec in self.table.BaseGlyphV1List.BaseGlyphV1Record } decompose = s.glyphs @@ -2139,48 +2031,31 @@ 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.LayerList, self.table.BaseGlyphList) - self.table.LayerList, self.table.BaseGlyphList = buildColrV1( + colorGlyphsV1 = unbuildColrV1(self.table.LayerV1List, self.table.BaseGlyphV1List) + self.table.LayerV1List, self.table.BaseGlyphV1List = 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.BaseGlyphList.BaseGlyphPaintRecord: + if not self.table.BaseGlyphV1List.BaseGlyphV1Record: # no more COLRv1 glyphs: downgrade to version 0 self.version = 0 del self.table return bool(layersV0) - populateCOLRv0( - self.table, - { - g: [(layer.name, layer.colorID) for layer in layersV0[g]] - for g in layersV0 - }, - ) + if 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 @@ -2195,11 +2070,11 @@ def prune_post_subset(self, font, options): colors_by_index = defaultdict(list) def collect_colors_by_index(paint): - if hasattr(paint, "PaletteIndex"): # either solid colors... - colors_by_index[paint.PaletteIndex].append(paint) + if hasattr(paint, "Color"): # either solid colors... + colors_by_index[paint.Color.PaletteIndex].append(paint.Color) elif hasattr(paint, "ColorLine"): # ... or gradient color stops for stop in paint.ColorLine.ColorStop: - colors_by_index[stop.PaletteIndex].append(stop) + colors_by_index[stop.Color.PaletteIndex].append(stop.Color) if colr.version == 0: for layers in colr.ColorLayers.values(): @@ -2209,12 +2084,10 @@ 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.BaseGlyphList.BaseGlyphPaintRecord: + for record in colr.table.BaseGlyphV1List.BaseGlyphV1Record: record.Paint.traverse(colr.table, collect_colors_by_index) - # 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} + retained_palette_indices = set(colors_by_index.keys()) 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) @@ -2328,7 +2201,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 = self.data = bytearray(self.data) + data = array.array("B", self.data) i = 10 more = 1 while more: @@ -2348,6 +2221,8 @@ 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 @@ -2370,7 +2245,7 @@ def prune_pre_subset(self, font, options): g = self[self.glyphOrder[0]] # Yay, easy! g.__dict__.clear() - g.data = b'' + g.data = "" return True @_add_method(ttLib.getTableClass('glyf')) @@ -2385,7 +2260,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 = b'' + self.glyphs[g].data = '' 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 @@ -2579,7 +2454,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', 'PCLT', 'LTSH'] + 'EBSC', 'SVG', 'PCLT', 'LTSH'] _drop_tables_default += ['Feat', 'Glat', 'Gloc', 'Silf', 'Sill'] # Graphite _no_subset_tables_default = ['avar', 'fvar', 'gasp', 'head', 'hhea', 'maxp', @@ -2646,7 +2521,6 @@ class Options(object): self.timing = False self.xml = False self.font_number = -1 - self.pretty_svg = False self.set(**kwargs) @@ -2785,7 +2659,7 @@ class Subsetter(object): def _closure_glyphs(self, font): realGlyphs = set(font.getGlyphOrder()) - self.orig_glyph_order = glyph_order = font.getGlyphOrder() + glyph_order = font.getGlyphOrder() self.glyphs_requested = set() self.glyphs_requested.update(self.glyph_names_requested) @@ -2865,7 +2739,6 @@ 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'"): @@ -2905,24 +2778,6 @@ 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)) @@ -2952,7 +2807,14 @@ class Subsetter(object): del font[tag] with timer("subset GlyphOrder"): - font.setGlyphOrder(self.new_glyph_order) + 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() def _prune_post_subset(self, font): @@ -3001,11 +2863,13 @@ 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, |