diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py index f8b3ce3b0..a087b2d3e 100644 --- a/Lib/fontTools/otlLib/builder.py +++ b/Lib/fontTools/otlLib/builder.py @@ -1,4 +1,5 @@ from __future__ import print_function, division, absolute_import +from collections import namedtuple from fontTools import ttLib from fontTools.ttLib.tables import otTables as ot from fontTools.ttLib.tables.otBase import ValueRecord, valueRecordFormatDict @@ -488,17 +489,25 @@ def _getSinglePosValueKey(valueRecord): return tuple(result) +_DeviceTuple = namedtuple("_DeviceTuple", "DeltaFormat StartSize EndSize DeltaValue") + + def _makeDeviceTuple(device): """otTables.Device --> tuple, for making device tables unique""" - return (device.DeltaFormat, device.StartSize, device.EndSize, - tuple(device.DeltaValue)) + return _DeviceTuple( + device.DeltaFormat, + device.StartSize, + device.EndSize, + () if device.DeltaFormat & 0x8000 else 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]): - count += len(k[1][3]) + 3 + for _, v in valueKey[1:]: + if isinstance(v, _DeviceTuple): + count += len(v.DeltaValue) + 3 else: count += 1 return count diff --git a/Lib/fontTools/varLib/merger.py b/Lib/fontTools/varLib/merger.py index d92eb7fa3..cd16ace45 100644 --- a/Lib/fontTools/varLib/merger.py +++ b/Lib/fontTools/varLib/merger.py @@ -3,6 +3,7 @@ Merge OpenType Layout tables (GDEF / GPOS / GSUB). """ from __future__ import print_function, division, absolute_import import copy +from operator import ior from fontTools.misc.py23 import * from fontTools.misc.fixedTools import otRound from fontTools.misc import classifyTools @@ -13,6 +14,7 @@ from fontTools.varLib import builder, models, varStore 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 class Merger(object): @@ -727,6 +729,30 @@ def _Lookup_PairPos_subtables_canonicalize(lst, font): return lst +def _Lookup_SinglePos_subtables_flatten(lst, font, min_inclusive_rec_format): + glyphs, _ = _merge_GlyphOrders(font, + [v.Coverage.glyphs for v in lst], None) + num_glyphs = len(glyphs) + new = ot.SinglePos() + new.Format = 2 + new.ValueFormat = min_inclusive_rec_format + new.Coverage = ot.Coverage() + new.Coverage.glyphs = glyphs + new.ValueCount = num_glyphs + new.Value = [None] * num_glyphs + for singlePos in lst: + if singlePos.Format == 1: + val_rec = singlePos.Value + for gname in singlePos.Coverage.glyphs: + i = glyphs.index(gname) + new.Value[i] = copy.deepcopy(val_rec) + elif singlePos.Format == 2: + for j, gname in enumerate(singlePos.Coverage.glyphs): + val_rec = singlePos.Value[j] + i = glyphs.index(gname) + new.Value[i] = copy.deepcopy(val_rec) + return [new] + @AligningMerger.merger(ot.Lookup) def merge(merger, self, lst): subtables = merger.lookup_subtables = [l.SubTable for l in lst] @@ -746,12 +772,28 @@ def merge(merger, self, lst): isPairPos = self.SubTable and isinstance(self.SubTable[0], ot.PairPos) if isPairPos: - # AFDKO and feaLib sometimes generate two Format1 subtables instead of one. # Merge those before continuing. # https://github.com/fonttools/fonttools/issues/719 self.SubTable = _Lookup_PairPos_subtables_canonicalize(self.SubTable, merger.font) subtables = merger.lookup_subtables = [_Lookup_PairPos_subtables_canonicalize(st, merger.font) for st in subtables] + else: + isSinglePos = self.SubTable and isinstance(self.SubTable[0], ot.SinglePos) + if isSinglePos: + numSubtables = [len(st) for st in subtables] + if not all([nums == numSubtables[0] for nums in numSubtables]): + # Flatten list of SinglePos subtables to single Format 2 subtable, + # with all value records set to the rec format type. + # We use buildSinglePos() to optimize the lookup after merging. + valueFormatList = [t.ValueFormat for st in subtables for t in st] + # Find the minimum value record that can accomodate all the singlePos subtables. + mirf = reduce(ior, valueFormatList) + self.SubTable = _Lookup_SinglePos_subtables_flatten(self.SubTable, merger.font, mirf) + subtables = merger.lookup_subtables = [ + _Lookup_SinglePos_subtables_flatten(st, merger.font, mirf) for st in subtables] + flattened = True + else: + flattened = False merger.mergeLists(self.SubTable, subtables) self.SubTableCount = len(self.SubTable) @@ -769,6 +811,16 @@ def merge(merger, self, lst): self.SubTable.pop(-1) self.SubTableCount -= 1 + elif isSinglePos and flattened: + singlePosTable = self.SubTable[0] + glyphs = singlePosTable.Coverage.glyphs + # We know that singlePosTable is Format 2, as this is set + # in _Lookup_SinglePos_subtables_flatten. + singlePosMapping = { + gname: valRecord + for gname, valRecord in zip(glyphs, singlePosTable.Value) + } + self.SubTable = buildSinglePos(singlePosMapping, merger.font.getReverseGlyphMap()) merger.mergeObjects(self, lst, exclude=['SubTable', 'SubTableCount']) del merger.lookup_subtables diff --git a/Tests/varLib/data/master_vpal_test/master_vpal_test_0.ttx b/Tests/varLib/data/master_vpal_test/master_vpal_test_0.ttx new file mode 100644 index 000000000..1fbb45d35 --- /dev/null +++ b/Tests/varLib/data/master_vpal_test/master_vpal_test_0.ttx @@ -0,0 +1,485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright © 2018 Adobe systems Co., Ltd. All Rights Reserved. + + + MasterSet_KanjiFullEX + + + Regular + + + 1.004;UKWN;MasterSet_KanjiFullEX-w0.00 + + + MasterSet_KanjiFullEX + + + Version 1.004;hotconv 1.0.109;makeotfexe 2.5.65596 + + + MasterSet_KanjiFullEX-w0.00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -282 endchar + + + 291 -43 rmoveto + 29 24 rlineto + -74 84 -84 83 -73 58 rrcurveto + -25 -24 rlineto + 72 -56 87 -85 68 -84 rrcurveto + endchar + + + 706 699 rmoveto + -32 -10 rlineto + 50 -192 56 -132 112 -115 rrcurveto + 24 26 rlineto + -143 124 -45 182 -22 117 rrcurveto + -592 -57 rmoveto + 3 -38 rlineto + 18 2 18 3 17 2 rrcurveto + 43 6 101 12 63 13 rrcurveto + -83 -80 -128 -166 0 -210 rrcurveto + 0 -150 92 -88 154 0 rrcurveto + 294 0 65 313 -48 302 rrcurveto + -29 79 rlineto + 64 -402 -96 -263 -249 0 rrcurveto + -109 0 -110 48 0 166 rrcurveto + 0 219 171 196 58 42 rrcurveto + 14 5 30 6 12 3 rrcurveto + -9 30 rlineto + -52 -21 -170 -26 -83 -3 rrcurveto + -18 -1 -21 0 -12 1 rrcurveto + 677 114 rmoveto + -26 -11 rlineto + 22 -34 33 -63 20 -42 rrcurveto + 30 14 rlineto + -21 41 -37 62 -21 33 rrcurveto + 97 40 rmoveto + -26 -13 rlineto + 24 -33 31 -59 21 -44 rrcurveto + 30 14 rlineto + -23 43 -36 60 -21 32 rrcurveto + endchar + + + 706 699 rmoveto + -32 -10 rlineto + 50 -192 56 -132 112 -115 rrcurveto + 24 26 rlineto + -143 124 -45 182 -22 117 rrcurveto + -592 -57 rmoveto + 3 -38 rlineto + 18 2 18 3 17 2 rrcurveto + 43 6 101 12 63 13 rrcurveto + -83 -80 -128 -166 0 -210 rrcurveto + 0 -150 92 -88 154 0 rrcurveto + 294 0 65 313 -48 302 rrcurveto + -29 79 rlineto + 64 -402 -96 -263 -249 0 rrcurveto + -109 0 -110 48 0 166 rrcurveto + 0 219 171 196 58 42 rrcurveto + 14 5 30 6 12 3 rrcurveto + -9 30 rlineto + -52 -21 -170 -26 -83 -3 rrcurveto + -18 -1 -21 0 -12 1 rrcurveto + 681 51 rmoveto + 0 42 33 33 41 0 rrcurveto + 41 0 33 -33 0 -42 rrcurveto + 0 -41 -33 -33 -41 0 rrcurveto + -42 0 -32 33 0 41 rrcurveto + -30 0 rmoveto + 0 -57 46 -47 58 0 rrcurveto + 57 0 48 47 0 57 rrcurveto + 0 58 -48 47 -57 0 rrcurveto + -58 0 -46 -47 0 -58 rrcurveto + endchar + + + 703 676 rmoveto + -28 0 rlineto + 0 -63 0 -106 0 -61 rrcurveto + 0 -103 14 -162 0 -52 rrcurveto + 0 -59 -17 -50 -97 0 rrcurveto + -91 0 -58 31 0 63 rrcurveto + 0 53 62 37 87 0 rrcurveto + 125 0 119 -56 94 -98 rrcurveto + 19 31 rlineto + -90 81 -113 69 -154 0 rrcurveto + -130 0 -47 -64 0 -53 rrcurveto + 0 -80 70 -42 105 0 rrcurveto + 85 0 58 36 0 74 rrcurveto + 0 68 -13 166 0 113 rrcurveto + 0 61 0 96 0 70 rrcurveto + -284 -213 rmoveto + 0 -31 rlineto + 168 -10 194 12 125 13 rrcurveto + 0 31 rlineto + -129 -15 -187 -13 -171 13 rrcurveto + 16 230 rmoveto + 0 -30 rlineto + 139 -7 200 11 108 11 rrcurveto + 0 30 rlineto + -111 -15 -197 -11 -139 11 rrcurveto + -207 51 rmoveto + -37 4 rlineto + 0 -11 -1 -14 -3 -19 rrcurveto + -13 -88 -37 -183 0 -143 rrcurveto + 0 -135 16 -105 20 -74 rrcurveto + 28 3 rlineto + -1 7 -2 12 -1 9 rrcurveto + -1 13 2 16 3 14 rrcurveto + 9 43 39 105 22 55 rrcurveto + -20 17 rlineto + -20 -48 -32 -89 -18 -56 rrcurveto + -10 78 -5 58 0 78 rrcurveto + 0 124 26 166 25 119 rrcurveto + 3 18 4 13 4 13 rrcurveto + endchar + + + 500 465 rmoveto + -47 0 -38 -38 0 -47 rrcurveto + 0 -47 38 -38 47 0 rrcurveto + 47 0 38 38 0 47 rrcurveto + 0 47 -38 38 -47 0 rrcurveto + endchar + + + 500 572 rmoveto + 28 0 28 19 0 37 rrcurveto + 0 37 -28 19 -28 0 rrcurveto + -28 0 -28 -19 0 -37 rrcurveto + 0 -37 28 -19 28 0 rrcurveto + 0 -502 rmoveto + 28 0 28 19 0 37 rrcurveto + 0 37 -28 19 -28 0 rrcurveto + -28 0 -28 -19 0 -37 rrcurveto + 0 -37 28 -19 28 0 rrcurveto + endchar + + + 180 0 rmoveto + 35 0 rlineto + 0 502 rlineto + 0 57 -1 74 -2 59 rrcurveto + 5 0 rlineto + 67 -160 rlineto + 196 -451 rlineto + 39 0 rlineto + 195 451 rlineto + 69 160 rlineto + 5 0 rlineto + -2 -59 -3 -74 0 -57 rrcurveto + 0 -502 rlineto + 37 0 rlineto + 0 726 rlineto + -59 0 rlineto + -191 -438 rlineto + -23 -54 -20 -55 -24 -55 rrcurveto + -4 0 rlineto + -24 55 -23 55 -23 54 rrcurveto + -190 438 rlineto + -59 0 rlineto + 0 -726 rlineto + endchar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/data/master_vpal_test/master_vpal_test_1.ttx b/Tests/varLib/data/master_vpal_test/master_vpal_test_1.ttx new file mode 100644 index 000000000..bb439586a --- /dev/null +++ b/Tests/varLib/data/master_vpal_test/master_vpal_test_1.ttx @@ -0,0 +1,473 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright © 2018 Adobe systems Co., Ltd. All Rights Reserved. + + + MasterSet_KanjiFullEX + + + Regular + + + 1.004;UKWN;MasterSet_KanjiFullEX-w1000.00 + + + MasterSet_KanjiFullEX + + + Version 1.004;hotconv 1.0.109;makeotfexe 2.5.65596 + + + MasterSet_KanjiFullEX-w1000.00 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -455 endchar + + + 245 -76 rmoveto + 129 111 rlineto + -44 56 -100 103 -70 58 rrcurveto + -127 -109 rlineto + 69 -61 84 -86 59 -72 rrcurveto + endchar + + + 729 763 rmoveto + -152 -42 rlineto + 75 -225 86 -176 109 -123 rrcurveto + 97 140 rlineto + -155 146 -41 152 -19 128 rrcurveto + -677 -48 rmoveto + 11 -156 rlineto + 24 5 15 2 22 3 rrcurveto + 26 3 53 6 31 1 rrcurveto + -96 -130 -50 -109 0 -149 rrcurveto + 0 -190 149 -88 163 0 rrcurveto + 344 0 43 303 -58 260 rrcurveto + -142 223 rlineto + 96 -376 -59 -246 -221 0 rrcurveto + -92 0 -67 46 0 103 rrcurveto + 0 184 125 143 71 54 rrcurveto + 16 10 18 8 15 6 rrcurveto + -46 134 rlineto + -69 -25 -166 -20 -98 -5 rrcurveto + -19 -1 -20 0 -19 1 rrcurveto + 768 103 rmoveto + -88 -27 rlineto + 22 -45 16 -54 15 -49 rrcurveto + 87 28 rlineto + -11 41 -23 62 -18 44 rrcurveto + 110 37 rmoveto + -85 -28 rlineto + 21 -44 19 -57 14 -46 rrcurveto + 88 28 rlineto + -13 40 -22 63 -22 44 rrcurveto + endchar + + + 729 763 rmoveto + -152 -42 rlineto + 75 -225 86 -176 109 -123 rrcurveto + 97 140 rlineto + -155 146 -41 152 -19 128 rrcurveto + -677 -48 rmoveto + 11 -156 rlineto + 24 5 15 2 22 3 rrcurveto + 26 3 53 6 31 1 rrcurveto + -96 -130 -50 -109 0 -149 rrcurveto + 0 -190 149 -88 163 0 rrcurveto + 344 0 43 303 -58 260 rrcurveto + -142 223 rlineto + 96 -376 -59 -246 -221 0 rrcurveto + -92 0 -67 46 0 103 rrcurveto + 0 184 125 143 71 54 rrcurveto + 16 10 18 8 15 6 rrcurveto + -46 134 rlineto + -69 -25 -166 -20 -98 -5 rrcurveto + -19 -1 -20 0 -19 1 rrcurveto + 772 16 rmoveto + 0 27 22 22 27 0 rrcurveto + 27 0 22 -22 0 -27 rrcurveto + 0 -27 -22 -22 -27 0 rrcurveto + -27 0 -22 22 0 27 rrcurveto + -70 0 rmoveto + 0 -66 53 -53 66 0 rrcurveto + 66 0 53 53 0 66 rrcurveto + 0 66 -53 53 -66 0 rrcurveto + -66 0 -53 -53 0 -66 rrcurveto + endchar + + + 758 687 rmoveto + -141 0 rlineto + 0 -42 0 -111 0 -88 rrcurveto + 0 -104 8 -126 0 -63 rrcurveto + 0 -45 -23 -24 -49 0 rrcurveto + -37 0 -24 16 0 29 rrcurveto + 0 28 23 18 48 0 rrcurveto + 107 0 121 -81 97 -98 rrcurveto + 79 131 rlineto + -51 45 -135 115 -213 0 rrcurveto + -137 0 -76 -72 0 -97 rrcurveto + 0 -116 100 -52 119 0 rrcurveto + 142 0 55 65 0 93 rrcurveto + 0 97 -13 99 0 147 rrcurveto + 0 64 0 96 0 76 rrcurveto + -360 -167 rmoveto + 1 -139 rlineto + 195 -9 191 7 133 16 rrcurveto + 0 139 rlineto + -148 -18 -178 -11 -194 15 rrcurveto + 15 235 rmoveto + 0 -134 rlineto + 197 -9 162 8 126 14 rrcurveto + 0 134 rlineto + -127 -19 -163 -9 -195 15 rrcurveto + -117 26 rmoveto + -168 14 rlineto + -1 -37 -6 -47 -4 -29 rrcurveto + -11 -74 -26 -192 0 -153 rrcurveto + 0 -136 20 -117 21 -68 rrcurveto + 139 10 rlineto + -1 16 0 18 0 11 rrcurveto + 0 10 3 23 3 14 rrcurveto + 12 58 30 101 30 90 rrcurveto + -73 59 rlineto + -13 -30 -14 -23 -12 -29 rrcurveto + -1 4 0 20 0 3 rrcurveto + 0 93 33 237 12 52 rrcurveto + 4 18 14 62 9 22 rrcurveto + endchar + + + 500 520 rmoveto + -77 0 -63 -63 0 -77 rrcurveto + 0 -77 63 -63 77 0 rrcurveto + 77 0 63 63 0 77 rrcurveto + 0 77 -63 63 -77 0 rrcurveto + endchar + + + 500 500 rmoveto + 60 0 46 46 0 58 rrcurveto + 0 60 -46 46 -60 0 rrcurveto + -60 0 -46 -46 0 -60 rrcurveto + 0 -58 46 -46 60 0 rrcurveto + 0 -470 rmoveto + 60 0 46 46 0 58 rrcurveto + 0 60 -46 46 -60 0 rrcurveto + -60 0 -46 -46 0 -60 rrcurveto + 0 -58 46 -46 60 0 rrcurveto + endchar + + + 113 0 rmoveto + 160 0 rlineto + 0 255 rlineto + 0 75 -12 113 -9 74 rrcurveto + 4 0 rlineto + 72 -182 rlineto + 121 -272 rlineto + 98 0 rlineto + 120 272 rlineto + 73 182 rlineto + 5 0 rlineto + -10 -74 -12 -113 0 -75 rrcurveto + 0 -255 rlineto + 163 0 rlineto + 0 748 rlineto + -194 0 rlineto + -135 -325 rlineto + -17 -43 -18 -47 -18 -46 rrcurveto + -5 0 rlineto + -16 46 -19 47 -16 43 rrcurveto + -142 325 rlineto + -193 0 rlineto + 0 -748 rlineto + endchar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/data/test_results/test_vpal.ttx b/Tests/varLib/data/test_results/test_vpal.ttx new file mode 100644 index 000000000..334ced550 --- /dev/null +++ b/Tests/varLib/data/test_results/test_vpal.ttx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/data/test_vpal.designspace b/Tests/varLib/data/test_vpal.designspace new file mode 100644 index 000000000..a736927ed --- /dev/null +++ b/Tests/varLib/data/test_vpal.designspace @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py index 3be5c2e5f..e29befbf4 100644 --- a/Tests/varLib/varLib_test.py +++ b/Tests/varLib/varLib_test.py @@ -294,6 +294,28 @@ class BuildTest(unittest.TestCase): tables = ["fvar", "CFF2"] self.expect_ttx(varfont, expected_ttx_path, tables) + def test_varlib_build_vpal(self): + ds_path = self.get_test_input('test_vpal.designspace') + ttx_dir = self.get_test_input("master_vpal_test") + expected_ttx_path = self.get_test_output("test_vpal.ttx") + + self.temp_dir() + for path in self.get_file_list(ttx_dir, '.ttx', 'master_vpal_test_'): + self.compile_font(path, ".otf", self.tempdir) + + ds = DesignSpaceDocument.fromfile(ds_path) + for source in ds.sources: + source.path = os.path.join( + self.tempdir, os.path.basename(source.filename).replace(".ufo", ".otf") + ) + ds.updatePaths() + + varfont, _, _ = build(ds) + varfont = reload_font(varfont) + + tables = ["GPOS"] + self.expect_ttx(varfont, expected_ttx_path, tables) + def test_varlib_main_ttf(self): """Mostly for testing varLib.main() """