From 06ed6997854ca8c500a8af1771a06c3cca574fe5 Mon Sep 17 00:00:00 2001 From: Martin Hosken Date: Fri, 15 Mar 2019 10:50:13 +0700 Subject: [PATCH 1/3] Compress type 1 GPOS tables better --- Lib/fontTools/otlLib/builder.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py index efe66d5340..27334d4460 100644 --- a/Lib/fontTools/otlLib/builder.py +++ b/Lib/fontTools/otlLib/builder.py @@ -408,7 +408,7 @@ def buildSinglePos(mapping, glyphMap): # If a ValueRecord is shared between multiple glyphs, we generate # a SinglePos format 1 subtable; that is the most compact form. for key, glyphs in coverages.items(): - if len(glyphs) > 1: + if len(glyphs) * _getSinglePosValueSize(key) > 5: format1Mapping = {g: values[key] for g in glyphs} result.append(buildSinglePosSubtable(format1Mapping, glyphMap)) handled.add(key) @@ -419,7 +419,9 @@ def buildSinglePos(mapping, glyphMap): for valueFormat, keys in masks.items(): f2 = [k for k in keys if k not in handled] if len(f2) > 1: - format2Mapping = {coverages[k][0]: values[k] for k in f2} + format2Mapping = {} + for k in f2: + format2Mapping.update((g, values[k]) for g in coverages[k]) result.append(buildSinglePosSubtable(format2Mapping, glyphMap)) handled.update(f2) @@ -428,8 +430,8 @@ def buildSinglePos(mapping, glyphMap): # is unique as well. We encode these in format 1 again. for key, glyphs in coverages.items(): if key not in handled: - assert len(glyphs) == 1, glyphs - st = buildSinglePosSubtable({glyphs[0]: values[key]}, glyphMap) + for g in glyphs: + st = buildSinglePosSubtable({g: values[key]}, glyphMap) result.append(st) # When the OpenType layout engine traverses the subtables, it will @@ -491,6 +493,14 @@ def _makeDeviceTuple(device): return (device.DeltaFormat, device.StartSize, device.EndSize, tuple(device.DeltaValue)) +def _getSinglePosValueSize(valueKey): + count = 0 + for k in valueKey[1:]: + if hasattr(k[1], '__len__') and len(k[1]): + count += len(k[1][3]) + 3 + else: + count += 1 + return count def buildValue(value): self = ValueRecord() From 27d8d1bda0e3fb84c8d6fb9864804781dde0e414 Mon Sep 17 00:00:00 2001 From: Martin Hosken Date: Fri, 15 Mar 2019 11:16:51 +0700 Subject: [PATCH 2/3] Update tests --- Tests/feaLib/data/GPOS_1.ttx | 47 +++++++++++++++++------------------- Tests/otlLib/builder_test.py | 19 ++++++--------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/Tests/feaLib/data/GPOS_1.ttx b/Tests/feaLib/data/GPOS_1.ttx index bd320136e8..1a24f0b221 100644 --- a/Tests/feaLib/data/GPOS_1.ttx +++ b/Tests/feaLib/data/GPOS_1.ttx @@ -40,39 +40,36 @@ - - - - - - - - - - - - + + + + + - + - + + + + - + - - - + + + + - - + + - + @@ -84,7 +81,7 @@ - + @@ -96,7 +93,7 @@ - + @@ -123,13 +120,13 @@ - + - + diff --git a/Tests/otlLib/builder_test.py b/Tests/otlLib/builder_test.py index 63a35d6184..0829176c97 100644 --- a/Tests/otlLib/builder_test.py +++ b/Tests/otlLib/builder_test.py @@ -913,24 +913,21 @@ def test_buildSinglePos(self): "six": builder.buildValue({"YPlacement": -6}), }, self.GLYPHMAP) self.assertEqual(sum([getXML(t.toXML) for t in subtables], []), - ['', + ['', ' ', ' ', ' ', - ' ', - ' ', - ' ', - ' ', - '', - '', - ' ', ' ', ' ', + ' ', ' ', ' ', - ' ', - ' ', - ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', '', '', ' ', From 1a92b3c21d547045cc876e9ad112a8a415d25b8e Mon Sep 17 00:00:00 2001 From: Martin Hosken Date: Mon, 15 Apr 2019 14:24:25 +0700 Subject: [PATCH 3/3] Comment changes to help clarify --- Lib/fontTools/otlLib/builder.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py index 27334d4460..f8b3ce3b0a 100644 --- a/Lib/fontTools/otlLib/builder.py +++ b/Lib/fontTools/otlLib/builder.py @@ -408,6 +408,7 @@ def buildSinglePos(mapping, glyphMap): # If a ValueRecord is shared between multiple glyphs, we generate # a SinglePos format 1 subtable; that is the most compact form. for key, glyphs in coverages.items(): + # 5 ushorts is the length of introducing another sublookup if len(glyphs) * _getSinglePosValueSize(key) > 5: format1Mapping = {g: values[key] for g in glyphs} result.append(buildSinglePosSubtable(format1Mapping, glyphMap)) @@ -425,9 +426,8 @@ def buildSinglePos(mapping, glyphMap): result.append(buildSinglePosSubtable(format2Mapping, glyphMap)) handled.update(f2) - # The remaining ValueRecords are singletons in the sense that - # they are only used by a single glyph, and their valueFormat - # is unique as well. We encode these in format 1 again. + # The remaining ValueRecords are only used by a few glyphs, normally + # one. We encode these in format 1 again. for key, glyphs in coverages.items(): if key not in handled: for g in glyphs: @@ -494,6 +494,7 @@ def _makeDeviceTuple(device): tuple(device.DeltaValue)) def _getSinglePosValueSize(valueKey): + """Returns how many ushorts this valueKey (short form of ValueRecord) takes up""" count = 0 for k in valueKey[1:]: if hasattr(k[1], '__len__') and len(k[1]):