[config] Add new config module and use it for GPOS compression level
This commit is contained in:
parent
e530c2fa1c
commit
abc0441957
41
Lib/fontTools/config/__init__.py
Normal file
41
Lib/fontTools/config/__init__.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""
|
||||
Define all configuration options that can affect the working of fontTools
|
||||
modules. E.g. optimization levels of varLib IUP, otlLib GPOS compression level,
|
||||
etc. If this file gets too big, split it into smaller files per-module.
|
||||
|
||||
An instance of the Config class can be attached to a TTFont object, so that
|
||||
the various modules can access their configuration options from it.
|
||||
"""
|
||||
from textwrap import dedent
|
||||
|
||||
from fontTools.misc.configTools import *
|
||||
|
||||
|
||||
class Config(AbstractConfig):
|
||||
options = Options()
|
||||
|
||||
OPTIONS = Config.options
|
||||
|
||||
Config.register_option(
|
||||
name="fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL",
|
||||
help=dedent(
|
||||
"""\
|
||||
GPOS Lookup type 2 (PairPos) compression level:
|
||||
0 = do not attempt to compact PairPos lookups;
|
||||
1 to 8 = create at most 1 to 8 new subtables for each existing
|
||||
subtable, provided that it would yield a 50%% file size saving;
|
||||
9 = create as many new subtables as needed to yield a file size saving.
|
||||
Default: 0.
|
||||
|
||||
This compaction aims to save file size, by splitting large class
|
||||
kerning subtables (Format 2) that contain many zero values into
|
||||
smaller and denser subtables. It's a trade-off between the overhead
|
||||
of several subtables versus the sparseness of one big subtable.
|
||||
|
||||
See the pull request: https://github.com/fonttools/fonttools/pull/2326
|
||||
"""
|
||||
),
|
||||
default=0,
|
||||
parse=int,
|
||||
validate=lambda v: v in range(10),
|
||||
)
|
@ -11,11 +11,7 @@ from fontTools.ttLib.tables.otBase import (
|
||||
)
|
||||
from fontTools.ttLib.tables import otBase
|
||||
from fontTools.feaLib.ast import STATNameStatement
|
||||
from fontTools.otlLib.optimize.gpos import (
|
||||
GPOS_COMPACT_MODE_DEFAULT,
|
||||
GPOS_COMPACT_MODE_ENV_KEY,
|
||||
compact_lookup,
|
||||
)
|
||||
from fontTools.otlLib.optimize.gpos import compact_lookup
|
||||
from fontTools.otlLib.error import OpenTypeLibError
|
||||
from functools import reduce
|
||||
import logging
|
||||
@ -1414,10 +1410,10 @@ class PairPosBuilder(LookupBuilder):
|
||||
# Compact the lookup
|
||||
# This is a good moment to do it because the compaction should create
|
||||
# smaller subtables, which may prevent overflows from happening.
|
||||
mode = os.environ.get(GPOS_COMPACT_MODE_ENV_KEY, GPOS_COMPACT_MODE_DEFAULT)
|
||||
if mode and mode != "0":
|
||||
level = self.font.cfg["fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL"]
|
||||
if level and level != 0:
|
||||
log.info("Compacting GPOS...")
|
||||
compact_lookup(self.font, mode, lookup)
|
||||
compact_lookup(self.font, level, lookup)
|
||||
|
||||
return lookup
|
||||
|
||||
|
@ -1,39 +1,30 @@
|
||||
from argparse import RawTextHelpFormatter
|
||||
from textwrap import dedent
|
||||
from fontTools.config import OPTIONS
|
||||
|
||||
from fontTools.otlLib.optimize.gpos import compact
|
||||
from fontTools.ttLib import TTFont
|
||||
from fontTools.otlLib.optimize.gpos import compact, GPOS_COMPACT_MODE_DEFAULT
|
||||
|
||||
|
||||
def main(args=None):
|
||||
"""Optimize the layout tables of an existing font."""
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from fontTools import configLogger
|
||||
|
||||
parser = ArgumentParser(prog="otlLib.optimize", description=main.__doc__, formatter_class=RawTextHelpFormatter)
|
||||
parser = ArgumentParser(
|
||||
prog="otlLib.optimize",
|
||||
description=main.__doc__,
|
||||
formatter_class=RawTextHelpFormatter,
|
||||
)
|
||||
parser.add_argument("font")
|
||||
parser.add_argument(
|
||||
"-o", metavar="OUTPUTFILE", dest="outfile", default=None, help="output file"
|
||||
)
|
||||
COMPRESSION_LEVEL = OPTIONS["fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL"]
|
||||
parser.add_argument(
|
||||
"--gpos-compact-mode",
|
||||
help=dedent(
|
||||
f"""\
|
||||
GPOS Lookup type 2 (PairPos) compaction mode:
|
||||
0 = do not attempt to compact PairPos lookups;
|
||||
1 to 8 = create at most 1 to 8 new subtables for each existing
|
||||
subtable, provided that it would yield a 50%% file size saving;
|
||||
9 = create as many new subtables as needed to yield a file size saving.
|
||||
Default: {GPOS_COMPACT_MODE_DEFAULT}.
|
||||
|
||||
This compaction aims to save file size, by splitting large class
|
||||
kerning subtables (Format 2) that contain many zero values into
|
||||
smaller and denser subtables. It's a trade-off between the overhead
|
||||
of several subtables versus the sparseness of one big subtable.
|
||||
|
||||
See the pull request: https://github.com/fonttools/fonttools/pull/2326
|
||||
"""
|
||||
),
|
||||
default=int(GPOS_COMPACT_MODE_DEFAULT),
|
||||
"--gpos-compression-level",
|
||||
help=COMPRESSION_LEVEL.help,
|
||||
default=COMPRESSION_LEVEL.default,
|
||||
choices=list(range(10)),
|
||||
type=int,
|
||||
)
|
||||
@ -51,12 +42,10 @@ def main(args=None):
|
||||
)
|
||||
|
||||
font = TTFont(options.font)
|
||||
# TODO: switch everything to have type(mode) = int when using the Config class
|
||||
compact(font, str(options.gpos_compact_mode))
|
||||
compact(font, options.gpos_compression_level)
|
||||
font.save(options.outfile or options.font)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
@ -65,4 +54,3 @@ if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
sys.exit(doctest.testmod().failed)
|
||||
|
||||
|
@ -9,16 +9,10 @@ from fontTools.misc.intTools import bit_count, bit_indices
|
||||
from fontTools.ttLib import TTFont
|
||||
from fontTools.ttLib.tables import otBase, otTables
|
||||
|
||||
# NOTE: activating this optimization via the environment variable is
|
||||
# experimental and may not be supported once an alternative mechanism
|
||||
# is in place. See: https://github.com/fonttools/fonttools/issues/2349
|
||||
GPOS_COMPACT_MODE_ENV_KEY = "FONTTOOLS_GPOS_COMPACT_MODE"
|
||||
GPOS_COMPACT_MODE_DEFAULT = "0"
|
||||
|
||||
log = logging.getLogger("fontTools.otlLib.optimize.gpos")
|
||||
|
||||
|
||||
def compact(font: TTFont, mode: str) -> TTFont:
|
||||
def compact(font: TTFont, level: int) -> TTFont:
|
||||
# Ideal plan:
|
||||
# 1. Find lookups of Lookup Type 2: Pair Adjustment Positioning Subtable
|
||||
# https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable
|
||||
@ -35,21 +29,21 @@ def compact(font: TTFont, mode: str) -> TTFont:
|
||||
gpos = font["GPOS"]
|
||||
for lookup in gpos.table.LookupList.Lookup:
|
||||
if lookup.LookupType == 2:
|
||||
compact_lookup(font, mode, lookup)
|
||||
compact_lookup(font, level, lookup)
|
||||
elif lookup.LookupType == 9 and lookup.SubTable[0].ExtensionLookupType == 2:
|
||||
compact_ext_lookup(font, mode, lookup)
|
||||
compact_ext_lookup(font, level, lookup)
|
||||
return font
|
||||
|
||||
|
||||
def compact_lookup(font: TTFont, mode: str, lookup: otTables.Lookup) -> None:
|
||||
new_subtables = compact_pair_pos(font, mode, lookup.SubTable)
|
||||
def compact_lookup(font: TTFont, level: int, lookup: otTables.Lookup) -> None:
|
||||
new_subtables = compact_pair_pos(font, level, lookup.SubTable)
|
||||
lookup.SubTable = new_subtables
|
||||
lookup.SubTableCount = len(new_subtables)
|
||||
|
||||
|
||||
def compact_ext_lookup(font: TTFont, mode: str, lookup: otTables.Lookup) -> None:
|
||||
def compact_ext_lookup(font: TTFont, level: int, lookup: otTables.Lookup) -> None:
|
||||
new_subtables = compact_pair_pos(
|
||||
font, mode, [ext_subtable.ExtSubTable for ext_subtable in lookup.SubTable]
|
||||
font, level, [ext_subtable.ExtSubTable for ext_subtable in lookup.SubTable]
|
||||
)
|
||||
new_ext_subtables = []
|
||||
for subtable in new_subtables:
|
||||
@ -62,7 +56,7 @@ def compact_ext_lookup(font: TTFont, mode: str, lookup: otTables.Lookup) -> None
|
||||
|
||||
|
||||
def compact_pair_pos(
|
||||
font: TTFont, mode: str, subtables: Sequence[otTables.PairPos]
|
||||
font: TTFont, level: int, subtables: Sequence[otTables.PairPos]
|
||||
) -> Sequence[otTables.PairPos]:
|
||||
new_subtables = []
|
||||
for subtable in subtables:
|
||||
@ -70,12 +64,12 @@ def compact_pair_pos(
|
||||
# Not doing anything to Format 1 (yet?)
|
||||
new_subtables.append(subtable)
|
||||
elif subtable.Format == 2:
|
||||
new_subtables.extend(compact_class_pairs(font, mode, subtable))
|
||||
new_subtables.extend(compact_class_pairs(font, level, subtable))
|
||||
return new_subtables
|
||||
|
||||
|
||||
def compact_class_pairs(
|
||||
font: TTFont, mode: str, subtable: otTables.PairPos
|
||||
font: TTFont, level: int, subtable: otTables.PairPos
|
||||
) -> List[otTables.PairPos]:
|
||||
from fontTools.otlLib.builder import buildPairPosClassesSubtable
|
||||
|
||||
@ -95,17 +89,9 @@ def compact_class_pairs(
|
||||
getattr(class2, "Value1", None),
|
||||
getattr(class2, "Value2", None),
|
||||
)
|
||||
|
||||
if len(mode) == 1 and mode in "123456789":
|
||||
grouped_pairs = cluster_pairs_by_class2_coverage_custom_cost(
|
||||
font, all_pairs, int(mode)
|
||||
)
|
||||
for pairs in grouped_pairs:
|
||||
subtables.append(
|
||||
buildPairPosClassesSubtable(pairs, font.getReverseGlyphMap())
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Bad {GPOS_COMPACT_MODE_ENV_KEY}={mode}")
|
||||
grouped_pairs = cluster_pairs_by_class2_coverage_custom_cost(font, all_pairs, level)
|
||||
for pairs in grouped_pairs:
|
||||
subtables.append(buildPairPosClassesSubtable(pairs, font.getReverseGlyphMap()))
|
||||
return subtables
|
||||
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
from fontTools.config import Config
|
||||
from fontTools.misc import xmlWriter
|
||||
from fontTools.misc.configTools import AbstractConfig
|
||||
from fontTools.misc.textTools import Tag, byteord, tostr
|
||||
from fontTools.misc.loggingTools import deprecateArgument
|
||||
from fontTools.ttLib import TTLibError
|
||||
@ -89,7 +91,7 @@ class TTFont(object):
|
||||
sfntVersion="\000\001\000\000", flavor=None, checkChecksums=0,
|
||||
verbose=None, recalcBBoxes=True, allowVID=NotImplemented, ignoreDecompileErrors=False,
|
||||
recalcTimestamp=True, fontNumber=-1, lazy=None, quiet=None,
|
||||
_tableCache=None):
|
||||
_tableCache=None, cfg={}):
|
||||
for name in ("verbose", "quiet"):
|
||||
val = locals().get(name)
|
||||
if val is not None:
|
||||
@ -101,6 +103,7 @@ class TTFont(object):
|
||||
self.recalcTimestamp = recalcTimestamp
|
||||
self.tables = {}
|
||||
self.reader = None
|
||||
self.cfg = cfg.copy() if isinstance(cfg, AbstractConfig) else Config(cfg)
|
||||
self.ignoreDecompileErrors = ignoreDecompileErrors
|
||||
|
||||
if not file:
|
||||
|
@ -15,11 +15,7 @@ from fontTools.varLib.models import nonNone, allNone, allEqual, allEqualTo
|
||||
from fontTools.varLib.varStore import VarStoreInstancer
|
||||
from functools import reduce
|
||||
from fontTools.otlLib.builder import buildSinglePos
|
||||
from fontTools.otlLib.optimize.gpos import (
|
||||
compact_pair_pos,
|
||||
GPOS_COMPACT_MODE_DEFAULT,
|
||||
GPOS_COMPACT_MODE_ENV_KEY,
|
||||
)
|
||||
from fontTools.otlLib.optimize.gpos import compact_pair_pos
|
||||
|
||||
log = logging.getLogger("fontTools.varLib.merger")
|
||||
|
||||
@ -850,10 +846,10 @@ def merge(merger, self, lst):
|
||||
# Compact the merged subtables
|
||||
# This is a good moment to do it because the compaction should create
|
||||
# smaller subtables, which may prevent overflows from happening.
|
||||
mode = os.environ.get(GPOS_COMPACT_MODE_ENV_KEY, GPOS_COMPACT_MODE_DEFAULT)
|
||||
if mode and mode != "0":
|
||||
level = merger.font.cfg["fontTools.otlLib.optimize.gpos:COMPRESSION_LEVEL"]
|
||||
if level and level != 0:
|
||||
log.info("Compacting GPOS...")
|
||||
self.SubTable = compact_pair_pos(merger.font, mode, self.SubTable)
|
||||
self.SubTable = compact_pair_pos(merger.font, level, self.SubTable)
|
||||
self.SubTableCount = len(self.SubTable)
|
||||
|
||||
elif isSinglePos and flattened:
|
||||
|
Loading…
x
Reference in New Issue
Block a user