Merge pull request #2275 from fonttools/subset-device

[subset] Fix hint-dropping
This commit is contained in:
Behdad Esfahbod 2021-04-22 12:37:48 -06:00 committed by GitHub
commit 9959916c64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 13 deletions

View File

@ -14,6 +14,7 @@ import struct
import array import array
import logging import logging
from collections import Counter, defaultdict from collections import Counter, defaultdict
from functools import reduce
from types import MethodType from types import MethodType
__usage__ = "pyftsubset font-file [glyph...] [--option=value]..." __usage__ = "pyftsubset font-file [glyph...] [--option=value]..."
@ -527,6 +528,17 @@ def subset_glyphs(self, s):
else: else:
assert 0, "unknown format: %s" % self.Format 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) @_add_method(otTables.SinglePos)
def subset_glyphs(self, s): def subset_glyphs(self, s):
if self.Format == 1: if self.Format == 1:
@ -543,15 +555,24 @@ def subset_glyphs(self, s):
@_add_method(otTables.SinglePos) @_add_method(otTables.SinglePos)
def prune_post_subset(self, font, options): def prune_post_subset(self, font, options):
if not options.hinting: # Shrink ValueFormat
# Drop device tables if self.Format == 1:
self.ValueFormat &= ~0x00F0 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 # 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): if self.Format == 2 and all(v == self.Value[0] for v in self.Value):
self.Format = 1 self.Format = 1
self.Value = self.Value[0] if self.ValueFormat != 0 else None self.Value = self.Value[0] if self.ValueFormat != 0 else None
del self.ValueCount del self.ValueCount
return True
return bool(self.ValueFormat)
@_add_method(otTables.PairPos) @_add_method(otTables.PairPos)
def subset_glyphs(self, s): def subset_glyphs(self, s):
@ -587,10 +608,22 @@ def subset_glyphs(self, s):
@_add_method(otTables.PairPos) @_add_method(otTables.PairPos)
def prune_post_subset(self, font, options): def prune_post_subset(self, font, options):
if not options.hinting: if not options.hinting:
# Drop device tables attr1, attr2 = {
self.ValueFormat1 &= ~0x00F0 1: ('PairSet', 'PairValueRecord'),
self.ValueFormat2 &= ~0x00F0 2: ('Class1Record', 'Class2Record'),
return True }[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) @_add_method(otTables.CursivePos)
def subset_glyphs(self, s): def subset_glyphs(self, s):
@ -606,9 +639,15 @@ def subset_glyphs(self, s):
@_add_method(otTables.Anchor) @_add_method(otTables.Anchor)
def prune_hints(self): def prune_hints(self):
# Drop device tables / contour anchor point if self.Format == 2:
self.ensureDecompiled() self.Format = 1
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) @_add_method(otTables.CursivePos)
def prune_post_subset(self, font, options): def prune_post_subset(self, font, options):
@ -713,7 +752,6 @@ def subset_glyphs(self, s):
@_add_method(otTables.MarkMarkPos) @_add_method(otTables.MarkMarkPos)
def prune_post_subset(self, font, options): def prune_post_subset(self, font, options):
if not options.hinting: if not options.hinting:
# Drop device tables or contour anchor point
for m in self.Mark1Array.MarkRecord: for m in self.Mark1Array.MarkRecord:
if m.MarkAnchor: if m.MarkAnchor:
m.MarkAnchor.prune_hints() m.MarkAnchor.prune_hints()

View File

@ -970,6 +970,13 @@ class ValueRecord(object):
format = format | valueRecordFormatDict[name][0] format = format | valueRecordFormatDict[name][0]
return format return format
def getEffectiveFormat(self):
format = 0
for name,value in self.__dict__.items():
if value:
format = format | valueRecordFormatDict[name][0]
return format
def toXML(self, xmlWriter, font, valueName, attrs=None): def toXML(self, xmlWriter, font, valueName, attrs=None):
if attrs is None: if attrs is None:
simpleItems = [] simpleItems = []

View File

@ -289,7 +289,7 @@ def merge(merger, self, lst):
# Merge everything else; though, there shouldn't be anything else. :) # Merge everything else; though, there shouldn't be anything else. :)
merger.mergeObjects(self, lst, merger.mergeObjects(self, lst,
exclude=('Format', 'Coverage', 'Value', 'ValueCount')) exclude=('Format', 'Coverage', 'Value', 'ValueCount'))
self.ValueFormat = reduce(int.__or__, [v.getFormat() for v in self.Value], 0) self.ValueFormat = reduce(int.__or__, [v.getEffectiveFormat() for v in self.Value], 0)
@AligningMerger.merger(ot.PairSet) @AligningMerger.merger(ot.PairSet)
def merge(merger, self, lst): def merge(merger, self, lst):