From 5b3db36670f0878fab36a3971cfb588b10d59d04 Mon Sep 17 00:00:00 2001 From: Read Roberts Date: Fri, 26 Apr 2019 09:33:52 -0700 Subject: [PATCH] Sparse cff2vf support (#1591) * Added getter (in the form of a property decorator) for T2Charstring.vsindex. Fixes endless compile loop in some circumstances. Fixed bug in mutator: need to remove vsindex from snapshotted charstrings, plus formatting clean up * Fix for subsetting HVAR tables that have an AdvanceWidthMap when the --retain-gid option is used. Needed to make subset_test.py::test_retain_gids_cff2 tests pass. * in varLib/cffLib.py, add support for sparse sources, and sources with more than one model, and hence more than one VarData element in the VarStore. CFF2 source fonts with multiple FontDicts in the FDArray need some extra work. With sparse fonts, some of the source fonts may have a fewer FontDicts than the default font. The getfd_map function() builds a map from the FontDict indices in the default font to those in each region font. This is needed when building up the blend value lists in the master font FontDict PrivateDicts, in order to fetch PrivateDict values from the correct FontDict in each region font. In specializer.py, add support for CFF2 CharStrings with blend operators. 1) In generalizeCommands, convert a blend op to a list of args that are blend lists for the following regular operator. A blend list as a default font value, followed by the delta tuple. 2) In specializeCommands(), convert these back to blend ops, combining as many successive blend lists as allowed by the stack limit. Add test case for sparse CFF2 sources. The test font has 55 glyphs. 2 glyphs use only 2 sources (weight = 0 and 100). The rest use 4 source fonts: the two end points of the weight axis, and two intermediate masters. The intermediate masters are only 1 design space unit apart, and are used to change glyph design at the point in design space. For the rest, at most 2 glyphs use the same set of source fonts. There are 12 source fonts. Add test case for specializer programToCommands() and commandsToProgram by converting each CharString.program in the font to a command list, and back again, and comparing original and final versions. --- Lib/fontTools/cffLib/specializer.py | 203 ++- Lib/fontTools/misc/psCharStrings.py | 10 + Lib/fontTools/varLib/__init__.py | 3 +- Lib/fontTools/varLib/cff.py | 522 ++++--- Lib/fontTools/varLib/mutator.py | 31 +- Tests/cffLib/data/TestSparseCFF2VF.otf | Bin 0 -> 13496 bytes Tests/cffLib/specializer_test.py | 43 +- .../varLib/data/TestSparseCFF2VF.designspace | 229 +++ .../MasterSet_Kanji-w0.00.otf | Bin 0 -> 6364 bytes .../MasterSet_Kanji-w1000.00.otf | Bin 0 -> 6496 bytes .../MasterSet_Kanji-w439.00.otf | Bin 0 -> 2600 bytes .../MasterSet_Kanji-w440.00.otf | Bin 0 -> 2596 bytes .../MasterSet_Kanji-w599.00.otf | Bin 0 -> 2652 bytes .../MasterSet_Kanji-w600.00.otf | Bin 0 -> 2656 bytes .../MasterSet_Kanji-w669.00.otf | Bin 0 -> 2672 bytes .../MasterSet_Kanji-w670.00.otf | Bin 0 -> 2668 bytes .../MasterSet_Kanji-w799.00.otf | Bin 0 -> 2220 bytes .../MasterSet_Kanji-w800.00.otf | Bin 0 -> 2220 bytes .../MasterSet_Kanji-w889.00.otf | Bin 0 -> 2556 bytes .../MasterSet_Kanji-w890.00.otf | Bin 0 -> 2556 bytes .../data/test_results/BuildTestCFF2.ttx | 18 +- .../data/test_results/TestSparseCFF2VF.ttx | 1301 +++++++++++++++++ Tests/varLib/varLib_test.py | 17 + 23 files changed, 2150 insertions(+), 227 deletions(-) mode change 100644 => 100755 Lib/fontTools/varLib/cff.py create mode 100644 Tests/cffLib/data/TestSparseCFF2VF.otf create mode 100644 Tests/varLib/data/TestSparseCFF2VF.designspace create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w0.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w1000.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w439.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w440.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w599.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w600.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w669.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w670.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w799.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w800.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w889.00.otf create mode 100644 Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w890.00.otf create mode 100644 Tests/varLib/data/test_results/TestSparseCFF2VF.ttx diff --git a/Lib/fontTools/cffLib/specializer.py b/Lib/fontTools/cffLib/specializer.py index 02e092003..db6e5f3d7 100644 --- a/Lib/fontTools/cffLib/specializer.py +++ b/Lib/fontTools/cffLib/specializer.py @@ -4,6 +4,7 @@ from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * +from fontTools.cffLib import maxStackLimit def stringToProgram(string): @@ -26,14 +27,21 @@ def programToString(program): return ' '.join(str(x) for x in program) -def programToCommands(program): - r"""Takes a T2CharString program list and returns list of commands. +def programToCommands(program, getNumRegions=None): + """Takes a T2CharString program list and returns list of commands. Each command is a two-tuple of commandname,arg-list. The commandname might be empty string if no commandname shall be emitted (used for glyph width, hintmask/cntrmask argument, as well as stray arguments at the end of the - program (¯\_(ツ)_/¯).""" + program (¯\_(ツ)_/¯). + 'getNumRegions' may be None, or a callable object. It must return the + number of regions. 'getNumRegions' takes a single argument, vsindex. If + the vsindex argument is None, getNumRegions returns the default number + of regions for the charstring, else it returns the numRegions for + the vsindex.""" width = None + seenWidthOp = False + vsIndex = None commands = [] stack = [] it = iter(program) @@ -42,10 +50,37 @@ def programToCommands(program): stack.append(token) continue - if width is None and token in {'hstem', 'hstemhm', 'vstem', 'vstemhm', - 'cntrmask', 'hintmask', - 'hmoveto', 'vmoveto', 'rmoveto', - 'endchar'}: + if token == 'blend': + assert getNumRegions is not None + numSourceFonts = 1 + getNumRegions(vsIndex) + # replace the blend op args on the stack with a single list + # containing all the blend op args. + numBlendOps = stack[-1] * numSourceFonts + 1 + # replace first blend op by a list of the blend ops. + stack[-numBlendOps:] = [stack[-numBlendOps:]] + + # Check for width. + if not seenWidthOp: + seenWidthOp = True + widthLen = len(stack) - numBlendOps + if widthLen and (widthLen % 2): + stack.pop(0) + elif width is not None: + commands.pop(0) + width = None + # We do NOT add the width to the command list if a blend is seen: + # if a blend op exists, this is or will be a CFF2 charstring. + continue + + elif token == 'vsindex': + vsIndex = stack[-1] + assert type(vsIndex) is int + + elif (not seenWidthOp) and token in {'hstem', 'hstemhm', 'vstem', 'vstemhm', + 'cntrmask', 'hintmask', + 'hmoveto', 'vmoveto', 'rmoveto', + 'endchar'}: + seenWidthOp = True parity = token in {'hmoveto', 'vmoveto'} if stack and (len(stack) % 2) ^ parity: width = stack.pop(0) @@ -64,11 +99,23 @@ def programToCommands(program): return commands +def _flattenBlendArgs(args): + token_list = [] + for arg in args: + if isinstance(arg, list): + token_list.extend(arg) + token_list.append('blend') + else: + token_list.append(arg) + return token_list + def commandsToProgram(commands): """Takes a commands list as returned by programToCommands() and converts it back to a T2CharString program list.""" program = [] for op,args in commands: + if any(isinstance(arg, list) for arg in args): + args = _flattenBlendArgs(args) program.extend(args) if op: program.append(op) @@ -203,11 +250,58 @@ class _GeneralizerDecombinerCommandsMap(object): yield ('rlineto', args) yield ('rrcurveto', last_args) +def _convertBlendOpToArgs(blendList): + # args is list of blend op args. Since we are supporting + # recursive blend op calls, some of these args may also + # be a list of blend op args, and need to be converted before + # we convert the current list. + if any([isinstance(arg, list) for arg in blendList]): + args = [i for e in blendList for i in + (_convertBlendOpToArgs(e) if isinstance(e,list) else [e]) ] + else: + args = blendList + + # We now know that blendList contains a blend op argument list, even if + # some of the args are lists that each contain a blend op argument list. + # Convert from: + # [default font arg sequence x0,...,xn] + [delta tuple for x0] + ... + [delta tuple for xn] + # to: + # [ [x0] + [delta tuple for x0], + # ..., + # [xn] + [delta tuple for xn] ] + numBlends = args[-1] + # Can't use args.pop() when the args are being used in a nested list + # comprehension. See calling context + args = args[:-1] + + numRegions = len(args)//numBlends - 1 + if not (numBlends*(numRegions + 1) == len(args)): + raise ValueError(blendList) + + defaultArgs = [[arg] for arg in args[:numBlends]] + deltaArgs = args[numBlends:] + numDeltaValues = len(deltaArgs) + deltaList = [ deltaArgs[i:i + numRegions] for i in range(0, numDeltaValues, numRegions) ] + blend_args = [ a + b for a, b in zip(defaultArgs,deltaList)] + return blend_args def generalizeCommands(commands, ignoreErrors=False): result = [] mapping = _GeneralizerDecombinerCommandsMap - for op,args in commands: + for op, args in commands: + # First, generalize any blend args in the arg list. + if any([isinstance(arg, list) for arg in args]): + try: + args = [n for arg in args for n in (_convertBlendOpToArgs(arg) if isinstance(arg, list) else [arg])] + except ValueError: + if ignoreErrors: + # Store op as data, such that consumers of commands do not have to + # deal with incorrect number of arguments. + result.append(('', args)) + result.append(('', [op])) + else: + raise + func = getattr(mapping, op, None) if not func: result.append((op,args)) @@ -225,8 +319,8 @@ def generalizeCommands(commands, ignoreErrors=False): raise return result -def generalizeProgram(program, **kwargs): - return commandsToProgram(generalizeCommands(programToCommands(program), **kwargs)) +def generalizeProgram(program, getNumRegions=None, **kwargs): + return commandsToProgram(generalizeCommands(programToCommands(program, getNumRegions), **kwargs)) def _categorizeVector(v): @@ -267,6 +361,70 @@ def _negateCategory(a): assert a in '0r' return a +def _convertToBlendCmds(args): + # return a list of blend commands, and + # the remaining non-blended args, if any. + num_args = len(args) + stack_use = 0 + new_args = [] + i = 0 + while i < num_args: + arg = args[i] + if not isinstance(arg, list): + new_args.append(arg) + i += 1 + stack_use += 1 + else: + prev_stack_use = stack_use + # The arg is a tuple of blend values. + # These are each (master 0,delta 1..delta n) + # Combine as many successive tuples as we can, + # up to the max stack limit. + num_sources = len(arg) + blendlist = [arg] + i += 1 + stack_use += 1 + num_sources # 1 for the num_blends arg + while (i < num_args) and isinstance(args[i], list): + blendlist.append(args[i]) + i += 1 + stack_use += num_sources + if stack_use + num_sources > maxStackLimit: + # if we are here, max stack is the CFF2 max stack. + # I use the CFF2 max stack limit here rather than + # the 'maxstack' chosen by the client, as the default + # maxstack may have been used unintentionally. For all + # the other operators, this just produces a little less + # optimization, but here it puts a hard (and low) limit + # on the number of source fonts that can be used. + break + # blendList now contains as many single blend tuples as can be + # combined without exceeding the CFF2 stack limit. + num_blends = len(blendlist) + # append the 'num_blends' default font values + blend_args = [] + for arg in blendlist: + blend_args.append(arg[0]) + for arg in blendlist: + blend_args.extend(arg[1:]) + blend_args.append(num_blends) + new_args.append(blend_args) + stack_use = prev_stack_use + num_blends + + return new_args + +def _addArgs(a, b): + if isinstance(b, list): + if isinstance(a, list): + if len(a) != len(b): + raise ValueError() + return [_addArgs(va, vb) for va,vb in zip(a, b)] + else: + a, b = b, a + if isinstance(a, list): + return [_addArgs(a[0], b)] + a[1:] + return a + b + + def specializeCommands(commands, ignoreErrors=False, generalizeFirst=True, @@ -302,6 +460,8 @@ def specializeCommands(commands, # I have convinced myself that this produces optimal bytecode (except for, possibly # one byte each time maxstack size prohibits combining.) YMMV, but you'd be wrong. :-) # A dynamic-programming approach can do the same but would be significantly slower. + # + # 7. For any args which are blend lists, convert them to a blend command. # 0. Generalize commands. @@ -417,12 +577,18 @@ def specializeCommands(commands, continue # Merge adjacent hlineto's and vlineto's. + # In CFF2 charstrings from variable fonts, each + # arg item may be a list of blendable values, one from + # each source font. if (i and op in {'hlineto', 'vlineto'} and - (op == commands[i-1][0]) and - (not isinstance(args[0], list))): + (op == commands[i-1][0])): _, other_args = commands[i-1] assert len(args) == 1 and len(other_args) == 1 - commands[i-1] = (op, [other_args[0]+args[0]]) + try: + new_args = [_addArgs(args[0], other_args[0])] + except ValueError: + continue + commands[i-1] = (op, new_args) del commands[i] continue @@ -534,10 +700,16 @@ def specializeCommands(commands, commands[i] = op0+op1+'curveto', args continue + # 7. For any series of args which are blend lists, convert the series to a single blend arg. + for i in range(len(commands)): + op, args = commands[i] + if any(isinstance(arg, list) for arg in args): + commands[i] = op, _convertToBlendCmds(args) + return commands -def specializeProgram(program, **kwargs): - return commandsToProgram(specializeCommands(programToCommands(program), **kwargs)) +def specializeProgram(program, getNumRegions=None, **kwargs): + return commandsToProgram(specializeCommands(programToCommands(program, getNumRegions), **kwargs)) if __name__ == '__main__': @@ -554,4 +726,3 @@ if __name__ == '__main__': assert program == program2 print("Generalized program:"); print(programToString(generalizeProgram(program))) print("Specialized program:"); print(programToString(specializeProgram(program))) - diff --git a/Lib/fontTools/misc/psCharStrings.py b/Lib/fontTools/misc/psCharStrings.py index 7fc7a26fd..a97ec96dd 100644 --- a/Lib/fontTools/misc/psCharStrings.py +++ b/Lib/fontTools/misc/psCharStrings.py @@ -944,6 +944,16 @@ class T2CharString(object): self.program = program self.private = private self.globalSubrs = globalSubrs if globalSubrs is not None else [] + self._cur_vsindex = None + + def getNumRegions(self, vsindex=None): + pd = self.private + assert(pd is not None) + if vsindex is not None: + self._cur_vsindex = vsindex + elif self._cur_vsindex is None: + self._cur_vsindex = pd.vsindex if hasattr(pd, 'vsindex') else 0 + return pd.getNumRegions(self._cur_vsindex) def __repr__(self): if self.bytecode is None: diff --git a/Lib/fontTools/varLib/__init__.py b/Lib/fontTools/varLib/__init__.py index 38ee201c0..edc51dbf0 100644 --- a/Lib/fontTools/varLib/__init__.py +++ b/Lib/fontTools/varLib/__init__.py @@ -759,12 +759,11 @@ _DesignSpaceData = namedtuple( def _add_CFF2(varFont, model, master_fonts): - from .cff import (convertCFFtoCFF2, addCFFVarStore, merge_region_fonts) + from .cff import (convertCFFtoCFF2, merge_region_fonts) glyphOrder = varFont.getGlyphOrder() convertCFFtoCFF2(varFont) ordered_fonts_list = model.reorderMasters(master_fonts, model.reverseMapping) # re-ordering the master list simplifies building the CFF2 data item lists. - addCFFVarStore(varFont, model) # Add VarStore to the CFF2 font. merge_region_fonts(varFont, model, ordered_fonts_list, glyphOrder) diff --git a/Lib/fontTools/varLib/cff.py b/Lib/fontTools/varLib/cff.py old mode 100644 new mode 100755 index b6febe2e0..a8da21456 --- a/Lib/fontTools/varLib/cff.py +++ b/Lib/fontTools/varLib/cff.py @@ -1,7 +1,5 @@ +from collections import namedtuple import os -from fontTools.misc.py23 import BytesIO -from fontTools.misc.psCharStrings import T2CharString, T2OutlineExtractor -from fontTools.pens.t2CharStringPen import T2CharStringPen, t2c_round from fontTools.cffLib import ( maxStackLimit, TopDictIndex, @@ -14,20 +12,21 @@ from fontTools.cffLib import ( FontDict, VarStoreData ) -from fontTools.cffLib.specializer import (commandsToProgram, specializeCommands) +from fontTools.misc.py23 import BytesIO +from fontTools.cffLib.specializer import ( + specializeCommands, commandsToProgram) from fontTools.ttLib import newTable from fontTools import varLib from fontTools.varLib.models import allEqual +from fontTools.misc.psCharStrings import T2CharString, T2OutlineExtractor +from fontTools.pens.t2CharStringPen import T2CharStringPen, t2c_round -def addCFFVarStore(varFont, varModel): - supports = varModel.supports[1:] +def addCFFVarStore(varFont, varModel, varDataList, masterSupports): fvarTable = varFont['fvar'] axisKeys = [axis.axisTag for axis in fvarTable.axes] - varTupleList = varLib.builder.buildVarRegionList(supports, axisKeys) - varTupleIndexes = list(range(len(supports))) - varDeltasCFFV = varLib.builder.buildVarData(varTupleIndexes, None, False) - varStoreCFFV = varLib.builder.buildVarStore(varTupleList, [varDeltasCFFV]) + varTupleList = varLib.builder.buildVarRegionList(masterSupports, axisKeys) + varStoreCFFV = varLib.builder.buildVarStore(varTupleList, varDataList) topDict = varFont['CFF2'].cff.topDictIndex[0] topDict.VarStore = VarStoreData(otVarStore=varStoreCFFV) @@ -143,16 +142,61 @@ pd_blend_fields = ("BlueValues", "OtherBlues", "FamilyBlues", "StemSnapV") -def merge_PrivateDicts(topDict, region_top_dicts, num_masters, var_model): +def get_private(regionFDArrays, fd_index, ri, fd_map): + region_fdArray = regionFDArrays[ri] + region_fd_map = fd_map[fd_index] + if ri in region_fd_map: + region_fdIndex = region_fd_map[ri] + private = region_fdArray[region_fdIndex].Private + else: + private = None + return private + + +def merge_PrivateDicts(top_dicts, vsindex_dict, var_model, fd_map): + """ + I step through the FontDicts in the FDArray of the varfont TopDict. + For each varfont FontDict: + step through each key in FontDict.Private. + For each key, step through each relevant source font Private dict, and + build a list of values to blend. + The 'relevant' source fonts are selected by first getting the right + submodel using model_keys[vsindex]. The indices of the + subModel.locations are mapped to source font list indices by + assuming the latter order is the same as the order of the + var_model.locations. I can then get the index of each subModel + location in the list of var_model.locations. + """ + + topDict = top_dicts[0] + region_top_dicts = top_dicts[1:] if hasattr(region_top_dicts[0], 'FDArray'): regionFDArrays = [fdTopDict.FDArray for fdTopDict in region_top_dicts] else: regionFDArrays = [[fdTopDict] for fdTopDict in region_top_dicts] for fd_index, font_dict in enumerate(topDict.FDArray): private_dict = font_dict.Private - pds = [private_dict] + [ - regionFDArray[fd_index].Private for regionFDArray in regionFDArrays - ] + vsindex = getattr(private_dict, 'vsindex', 0) + # At the moment, no PrivateDict has a vsindex key, but let's support + # how it should work. See comment at end of + # merge_charstrings() - still need to optimize use of vsindex. + sub_model, model_keys = vsindex_dict[vsindex] + master_indices = [] + for loc in sub_model.locations[1:]: + i = var_model.locations.index(loc) - 1 + master_indices.append(i) + pds = [private_dict] + last_pd = private_dict + for ri in master_indices: + pd = get_private(regionFDArrays, fd_index, ri, fd_map) + # If the region font doesn't have this FontDict, just reference + # the last one used. + if pd is None: + pd = last_pd + else: + last_pd = pd + pds.append(pd) + num_masters = len(pds) for key, value in private_dict.rawDict.items(): if key not in pd_blend_fields: continue @@ -192,7 +236,7 @@ def merge_PrivateDicts(topDict, region_top_dicts, num_masters, var_model): if (not any_points_differ) and not allEqual(rel_list): any_points_differ = True prev_val_list = val_list - deltas = var_model.getDeltas(rel_list) + deltas = sub_model.getDeltas(rel_list) # Convert numbers with no decimal part to an int. deltas = [conv_to_int(delta) for delta in deltas] # For PrivateDict BlueValues, the default font @@ -206,61 +250,159 @@ def merge_PrivateDicts(topDict, region_top_dicts, num_masters, var_model): else: values = [pd.rawDict[key] for pd in pds] if not allEqual(values): - dataList = var_model.getDeltas(values) + dataList = sub_model.getDeltas(values) else: dataList = values[0] private_dict.rawDict[key] = dataList +def getfd_map(varFont, fonts_list): + """ Since a subset source font may have fewer FontDicts in their + FDArray than the default font, we have to match up the FontDicts in + the different fonts . We do this with the FDSelect array, and by + assuming that the same glyph will reference matching FontDicts in + each source font. We return a mapping from fdIndex in the default + font to a dictionary which maps each master list index of each + region font to the equivalent fdIndex in the region font.""" + fd_map = {} + default_font = fonts_list[0] + region_fonts = fonts_list[1:] + num_regions = len(region_fonts) + topDict = default_font['CFF '].cff.topDictIndex[0] + if not hasattr(topDict, 'FDSelect'): + fd_map[0] = [0]*num_regions + return fd_map + + gname_mapping = {} + default_fdSelect = topDict.FDSelect + glyphOrder = default_font.getGlyphOrder() + for gid, fdIndex in enumerate(default_fdSelect): + gname_mapping[glyphOrder[gid]] = fdIndex + if fdIndex not in fd_map: + fd_map[fdIndex] = {} + for ri, region_font in enumerate(region_fonts): + region_glyphOrder = region_font.getGlyphOrder() + region_topDict = region_font['CFF '].cff.topDictIndex[0] + if not hasattr(region_topDict, 'FDSelect'): + # All the glyphs share the same FontDict. Pick any glyph. + default_fdIndex = gname_mapping[region_glyphOrder[0]] + fd_map[default_fdIndex][ri] = 0 + else: + region_fdSelect = region_topDict.FDSelect + for gid, fdIndex in enumerate(region_fdSelect): + default_fdIndex = gname_mapping[region_glyphOrder[gid]] + region_map = fd_map[default_fdIndex] + if ri not in region_map: + region_map[ri] = fdIndex + return fd_map + + +CVarData = namedtuple('CVarData', 'varDataList masterSupports vsindex_dict') def merge_region_fonts(varFont, model, ordered_fonts_list, glyphOrder): topDict = varFont['CFF2'].cff.topDictIndex[0] - default_charstrings = topDict.CharStrings - region_fonts = ordered_fonts_list[1:] - region_top_dicts = [ - ttFont['CFF '].cff.topDictIndex[0] for ttFont in region_fonts - ] + top_dicts = [topDict] + [ + ttFont['CFF '].cff.topDictIndex[0] + for ttFont in ordered_fonts_list[1:] + ] num_masters = len(model.mapping) - merge_PrivateDicts(topDict, region_top_dicts, num_masters, model) - merge_charstrings(default_charstrings, - glyphOrder, - num_masters, - region_top_dicts, model) + cvData = merge_charstrings(glyphOrder, num_masters, top_dicts, model) + fd_map = getfd_map(varFont, ordered_fonts_list) + merge_PrivateDicts(top_dicts, cvData.vsindex_dict, model, fd_map) + addCFFVarStore(varFont, model, cvData.varDataList, + cvData.masterSupports) -def merge_charstrings(default_charstrings, - glyphOrder, - num_masters, - region_top_dicts, - var_model): - for gname in glyphOrder: - default_charstring = default_charstrings[gname] +def _get_cs(charstrings, glyphName): + if glyphName not in charstrings: + return None + return charstrings[glyphName] + + +def merge_charstrings(glyphOrder, num_masters, top_dicts, masterModel): + + vsindex_dict = {} + vsindex_by_key = {} + varDataList = [] + masterSupports = [] + default_charstrings = top_dicts[0].CharStrings + for gid, gname in enumerate(glyphOrder): + all_cs = [ + _get_cs(td.CharStrings, gname) + for td in top_dicts] + if len([gs for gs in all_cs if gs is not None]) == 1: + continue + model, model_cs = masterModel.getSubModel(all_cs) + # create the first pass CFF2 charstring, from + # the default charstring. + default_charstring = model_cs[0] var_pen = CFF2CharStringMergePen([], gname, num_masters, 0) - default_charstring.outlineExtractor = CFFToCFF2OutlineExtractor + # We need to override outlineExtractor because these + # charstrings do have widths in the 'program'; we need to drop these + # values rather than post assertion error for them. + default_charstring.outlineExtractor = MergeOutlineExtractor default_charstring.draw(var_pen) - for region_idx, region_td in enumerate(region_top_dicts, start=1): - region_charstrings = region_td.CharStrings - region_charstring = region_charstrings[gname] + + # Add the coordinates from all the other regions to the + # blend lists in the CFF2 charstring. + region_cs = model_cs[1:] + for region_idx, region_charstring in enumerate(region_cs, start=1): var_pen.restart(region_idx) + region_charstring.outlineExtractor = MergeOutlineExtractor region_charstring.draw(var_pen) - new_charstring = var_pen.getCharString( + + # Collapse each coordinate list to a blend operator and its args. + new_cs = var_pen.getCharString( private=default_charstring.private, globalSubrs=default_charstring.globalSubrs, - var_model=var_model, optimize=True) - default_charstrings[gname] = new_charstring + var_model=model, optimize=True) + default_charstrings[gname] = new_cs + + if (not var_pen.seen_moveto) or ('blend' not in new_cs.program): + # If this is not a marking glyph, or if there are no blend + # arguments, then we can use vsindex 0. No need to + # check if we need a new vsindex. + continue + + # If the charstring required a new model, create + # a VarData table to go with, and set vsindex. + try: + key = tuple(v is not None for v in all_cs) + vsindex = vsindex_by_key[key] + except KeyError: + varTupleIndexes = [] + for support in model.supports[1:]: + if support not in masterSupports: + masterSupports.append(support) + varTupleIndexes.append(masterSupports.index(support)) + var_data = varLib.builder.buildVarData(varTupleIndexes, None, False) + vsindex = len(vsindex_dict) + vsindex_by_key[key] = vsindex + vsindex_dict[vsindex] = (model, [key]) + varDataList.append(var_data) + # We do not need to check for an existing new_cs.private.vsindex, + # as we know it doesn't exist yet. + if vsindex != 0: + new_cs.program[:0] = [vsindex, 'vsindex'] + cvData = CVarData(varDataList=varDataList, masterSupports=masterSupports, + vsindex_dict=vsindex_dict) + # XXX To do: optimize use of vsindex between the PrivateDicts and + # charstrings + return cvData class MergeTypeError(TypeError): def __init__(self, point_type, pt_index, m_index, default_type, glyphName): - self.error_msg = [ - "In glyph '{gname}' " - "'{point_type}' at point index {pt_index} in master " - "index {m_index} differs from the default font point " - "type '{default_type}'" - "".format(gname=glyphName, - point_type=point_type, pt_index=pt_index, - m_index=m_index, default_type=default_type) - ][0] - super(MergeTypeError, self).__init__(self.error_msg) + self.error_msg = [ + "In glyph '{gname}' " + "'{point_type}' at point index {pt_index} in master " + "index {m_index} differs from the default font point " + "type '{default_type}'" + "".format( + gname=glyphName, + point_type=point_type, pt_index=pt_index, + m_index=m_index, default_type=default_type) + ][0] + super(MergeTypeError, self).__init__(self.error_msg) def makeRoundNumberFunc(tolerance): @@ -274,10 +416,9 @@ def makeRoundNumberFunc(tolerance): class CFFToCFF2OutlineExtractor(T2OutlineExtractor): - """ This class is used to remove the initial width - from the CFF charstring without adding the width - to self.nominalWidthX, which is None. - """ + """ This class is used to remove the initial width from the CFF + charstring without trying to add the width to self.nominalWidthX, + which is None. """ def popallWidth(self, evenOdd=0): args = self.popall() if not self.gotWidth: @@ -288,60 +429,127 @@ class CFFToCFF2OutlineExtractor(T2OutlineExtractor): return args +class MergeOutlineExtractor(CFFToCFF2OutlineExtractor): + """ Used to extract the charstring commands - including hints - from a + CFF charstring in order to merge it as another set of region data + into a CFF2 variable font charstring.""" + + def __init__(self, pen, localSubrs, globalSubrs, + nominalWidthX, defaultWidthX, private=None): + super(CFFToCFF2OutlineExtractor, self).__init__(pen, localSubrs, + globalSubrs, nominalWidthX, defaultWidthX, private) + + def countHints(self): + args = self.popallWidth() + self.hintCount = self.hintCount + len(args) // 2 + return args + + def _hint_op(self, type, args): + self.pen.add_hint(type, args) + + def op_hstem(self, index): + args = self.countHints() + self._hint_op('hstem', args) + + def op_vstem(self, index): + args = self.countHints() + self._hint_op('vstem', args) + + def op_hstemhm(self, index): + args = self.countHints() + self._hint_op('hstemhm', args) + + def op_vstemhm(self, index): + args = self.countHints() + self._hint_op('vstemhm', args) + + def _get_hintmask(self, index): + if not self.hintMaskBytes: + args = self.countHints() + if args: + self._hint_op('vstemhm', args) + self.hintMaskBytes = (self.hintCount + 7) // 8 + hintMaskBytes, index = self.callingStack[-1].getBytes(index, + self.hintMaskBytes) + return index, hintMaskBytes + + def op_hintmask(self, index): + index, hintMaskBytes = self._get_hintmask(index) + self.pen.add_hintmask('hintmask', [hintMaskBytes]) + return hintMaskBytes, index + + def op_cntrmask(self, index): + index, hintMaskBytes = self._get_hintmask(index) + self.pen.add_hintmask('cntrmask', [hintMaskBytes]) + return hintMaskBytes, index + + class CFF2CharStringMergePen(T2CharStringPen): """Pen to merge Type 2 CharStrings. """ - def __init__(self, default_commands, - glyphName, num_masters, master_idx, roundTolerance=0.5): + def __init__( + self, default_commands, glyphName, num_masters, master_idx, + roundTolerance=0.5): super( CFF2CharStringMergePen, - self).__init__(width=None, - glyphSet=None, CFF2=True, - roundTolerance=roundTolerance) + self).__init__( + width=None, + glyphSet=None, CFF2=True, + roundTolerance=roundTolerance) self.pt_index = 0 self._commands = default_commands self.m_index = master_idx self.num_masters = num_masters self.prev_move_idx = 0 + self.seen_moveto = False self.glyphName = glyphName self.roundNumber = makeRoundNumberFunc(roundTolerance) - def _p(self, pt): - """ Unlike T2CharstringPen, this class stores absolute values. - This is to allow the logic in check_and_fix_closepath() to work, - where the current or previous absolute point has to be compared to - the path start-point. - """ - self._p0 = pt - return list(self._p0) - def add_point(self, point_type, pt_coords): if self.m_index == 0: self._commands.append([point_type, [pt_coords]]) else: cmd = self._commands[self.pt_index] if cmd[0] != point_type: - # Fix some issues that show up in some - # CFF workflows, even when fonts are - # topologically merge compatible. - success, pt_coords = self.check_and_fix_flat_curve( - cmd, point_type, pt_coords) - if not success: - success = self.check_and_fix_closepath( - cmd, point_type, pt_coords) - if success: - # We may have incremented self.pt_index - cmd = self._commands[self.pt_index] - if cmd[0] != point_type: - success = False - if not success: - raise MergeTypeError(point_type, - self.pt_index, len(cmd[1]), - cmd[0], self.glyphName) + raise MergeTypeError( + point_type, + self.pt_index, len(cmd[1]), + cmd[0], self.glyphName) cmd[1].append(pt_coords) self.pt_index += 1 + def add_hint(self, hint_type, args): + if self.m_index == 0: + self._commands.append([hint_type, [args]]) + else: + cmd = self._commands[self.pt_index] + if cmd[0] != hint_type: + raise MergeTypeError(hint_type, self.pt_index, len(cmd[1]), + cmd[0], self.glyphName) + cmd[1].append(args) + self.pt_index += 1 + + def add_hintmask(self, hint_type, abs_args): + # For hintmask, fonttools.cffLib.specializer.py expects + # each of these to be represented by two sequential commands: + # first holding only the operator name, with an empty arg list, + # second with an empty string as the op name, and the mask arg list. + if self.m_index == 0: + self._commands.append([hint_type, []]) + self._commands.append(["", [abs_args]]) + else: + cmd = self._commands[self.pt_index] + if cmd[0] != hint_type: + raise MergeTypeError(hint_type, self.pt_index, len(cmd[1]), + cmd[0], self.glyphName) + self.pt_index += 1 + cmd = self._commands[self.pt_index] + cmd[1].append(abs_args) + self.pt_index += 1 + def _moveTo(self, pt): + if not self.seen_moveto: + self.seen_moveto = True pt_coords = self._p(pt) self.add_point('rmoveto', pt_coords) # I set prev_move_idx here because add_point() @@ -371,7 +579,7 @@ class CFF2CharStringMergePen(T2CharStringPen): def getCommands(self): return self._commands - def reorder_blend_args(self, commands): + def reorder_blend_args(self, commands, get_delta_func, round_func): """ We first re-order the master coordinate values. For a moveto to lineto, the args are now arranged as: @@ -380,9 +588,13 @@ class CFF2CharStringMergePen(T2CharStringPen): [ [master_0 x, master_1 x, master_2 x], [master_0 y, master_1 y, master_2 y] ] - We also make the value relative. If the master values are all the same, we collapse the list to as single value instead of a list. + + We then convert this to: + [ [master_0 x] + [x delta tuple] + [numBlends=1] + [master_0 y] + [y delta tuple] + [numBlends=1] + ] """ for cmd in commands: # arg[i] is the set of arguments for this operator from master i. @@ -390,108 +602,46 @@ class CFF2CharStringMergePen(T2CharStringPen): m_args = zip(*args) # m_args[n] is now all num_master args for the i'th argument # for this operation. - cmd[1] = m_args - - # Now convert from absolute to relative - x0 = [0]*self.num_masters - y0 = [0]*self.num_masters - for cmd in self._commands: - is_x = True - coords = cmd[1] - rel_coords = [] - for coord in coords: - prev_coord = x0 if is_x else y0 - rel_coord = [pt[0] - pt[1] for pt in zip(coord, prev_coord)] - - if allEqual(rel_coord): - rel_coord = rel_coord[0] - rel_coords.append(rel_coord) - if is_x: - x0 = coord - else: - y0 = coord - is_x = not is_x - cmd[1] = rel_coords - return commands - - @staticmethod - def mergeCommandsToProgram(commands, var_model, round_func): - """ - Takes a commands list as returned by programToCommands() and - converts it back to a T2CharString or CFF2Charstring program list. I - need to use this rather than specialize.commandsToProgram, as the - commands produced by CFF2CharStringMergePen initially contains a - list of coordinate values, one for each master, wherever a single - coordinate value is expected by the regular logic. The problem with - doing using the specialize.py functions is that a commands list is - expected to be a op name with its associated argument list. For the - commands list here, some of the arguments may need to be converted - to a new argument list and opcode. - This version will convert each list of master arguments to a blend - op and its arguments, and will also combine successive blend ops up - to the stack limit. - """ - program = [] - for op, args in commands: - num_args = len(args) - # some of the args may be blend lists, and some may be - # single coordinate values. - i = 0 - stack_use = 0 - while i < num_args: - arg = args[i] - if not isinstance(arg, list): - program.append(arg) - i += 1 - stack_use += 1 - else: - prev_stack_use = stack_use - """ The arg is a tuple of blend values. - These are each (master 0,master 1..master n) - Combine as many successive tuples as we can, - up to the max stack limit. - """ - num_masters = len(arg) - blendlist = [arg] - i += 1 - stack_use += 1 + num_masters # 1 for the num_blends arg - while (i < num_args) and isinstance(args[i], list): - blendlist.append(args[i]) - i += 1 - stack_use += num_masters - if stack_use + num_masters > maxStackLimit: - # if we are here, max stack is is the CFF2 max stack. - break - num_blends = len(blendlist) - # append the 'num_blends' default font values - for arg in blendlist: - if round_func: - arg[0] = round_func(arg[0]) - program.append(arg[0]) - for arg in blendlist: - deltas = var_model.getDeltas(arg) + cmd[1] = list(m_args) + lastOp = None + for cmd in commands: + op = cmd[0] + # masks are represented by two cmd's: first has only op names, + # second has only args. + if lastOp in ['hintmask', 'cntrmask']: + coord = list(cmd[1]) + assert allEqual(coord), ( + "hintmask values cannot differ between source fonts.") + cmd[1] = [coord[0][0]] + else: + coords = cmd[1] + new_coords = [] + for coord in coords: + if allEqual(coord): + new_coords.append(coord[0]) + else: + # convert to deltas + deltas = get_delta_func(coord)[1:] if round_func: deltas = [round_func(delta) for delta in deltas] - # First item in 'deltas' is the default master value; - # for CFF2 data, that has already been written. - program.extend(deltas[1:]) - program.append(num_blends) - program.append('blend') - stack_use = prev_stack_use + num_blends - if op: - program.append(op) - return program + coord = [coord[0]] + deltas + new_coords.append(coord) + cmd[1] = new_coords + lastOp = op + return commands - - def getCharString(self, private=None, globalSubrs=None, - var_model=None, optimize=True): + def getCharString( + self, private=None, globalSubrs=None, + var_model=None, optimize=True): commands = self._commands - commands = self.reorder_blend_args(commands) + commands = self.reorder_blend_args(commands, var_model.getDeltas, + self.roundNumber) if optimize: - commands = specializeCommands(commands, generalizeFirst=False, - maxstack=maxStackLimit) - program = self.mergeCommandsToProgram(commands, var_model=var_model, - round_func=self.roundNumber) - charString = T2CharString(program=program, private=private, - globalSubrs=globalSubrs) + commands = specializeCommands( + commands, generalizeFirst=False, + maxstack=maxStackLimit) + program = commandsToProgram(commands) + charString = T2CharString( + program=program, private=private, + globalSubrs=globalSubrs) return charString diff --git a/Lib/fontTools/varLib/mutator.py b/Lib/fontTools/varLib/mutator.py index de612365f..04ab35778 100644 --- a/Lib/fontTools/varLib/mutator.py +++ b/Lib/fontTools/varLib/mutator.py @@ -65,24 +65,29 @@ def interpolate_cff2_charstrings(topDict, interpolateFromDeltas, glyphOrder): charstrings = topDict.CharStrings for gname in glyphOrder: # Interpolate charstring + # e.g replace blend op args with regular args, + # and use and discard vsindex op. charstring = charstrings[gname] - pd = charstring.private - vsindex = pd.vsindex if (hasattr(pd, 'vsindex')) else 0 - num_regions = pd.getNumRegions(vsindex) - numMasters = num_regions + 1 new_program = [] + vsindex = 0 last_i = 0 for i, token in enumerate(charstring.program): - if token == 'blend': + if token == 'vsindex': + vsindex = charstring.program[i - 1] + if last_i != 0: + new_program.extend(charstring.program[last_i:i - 1]) + last_i = i + 1 + elif token == 'blend': + num_regions = charstring.getNumRegions(vsindex) + numMasters = 1 + num_regions num_args = charstring.program[i - 1] - """ The stack is now: - ..args for following operations - num_args values from the default font - num_args tuples, each with numMasters-1 delta values - num_blend_args - 'blend' - """ - argi = i - (num_args*numMasters + 1) + # The program list starting at program[i] is now: + # ..args for following operations + # num_args values from the default font + # num_args tuples, each with numMasters-1 delta values + # num_blend_args + # 'blend' + argi = i - (num_args * numMasters + 1) end_args = tuplei = argi + num_args while argi < end_args: next_ti = tuplei + num_regions diff --git a/Tests/cffLib/data/TestSparseCFF2VF.otf b/Tests/cffLib/data/TestSparseCFF2VF.otf new file mode 100644 index 0000000000000000000000000000000000000000..8390c8301ade4ad4cd146b7ce971e486e07dd8b8 GIT binary patch literal 13496 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w0.00.otf b/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w0.00.otf new file mode 100644 index 0000000000000000000000000000000000000000..e67649026f855500c69463f00955a4281e6125f4 GIT binary patch literal 6364 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 literal 0 HcmV?d00001 diff --git a/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w1000.00.otf b/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w1000.00.otf new file mode 100644 index 0000000000000000000000000000000000000000..3f40985309bda37b1ac6025517c8669c3b72def5 GIT binary patch literal 6496 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 M7k<5yO#aA!0p%#Ya{vGU literal 0 HcmV?d00001 diff --git a/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w439.00.otf b/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w439.00.otf new file mode 100644 index 0000000000000000000000000000000000000000..f21baf4990d89add23c7600f3dcf1c66d2454d54 GIT binary patch literal 2600 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 e)ni;d2(kDjb{pR+k#8w|@F(; zX~Z&Qj7s_gv7TH6U?Evv+0FWKE z^RjmR;z~S#P=NUHI!8^J_#|o&pN11{Y;*da3BS1uKztc-uvu}sl4*$@^-DN%n$bX; zOdn!?GUBx6wqS?&C3Il^T*S1^+2I4R8dYHdPH`S*o04{Q@fhMAIKJ|E1Hp+ykcItG z^vfaSBg4~fV*XDNr-hKuJQZdlUWS;0fU^M4ynd|VV#7T@y9uH=iWWGRt80e5Wc~u; zC&J>8=tC@miCgi)JahQgaWsuWJV@e@!4&hwW{ki-MFgTa2xCCNY}#PrRV!jrsK-7p z^I#ry`{Hfiz6$L{CCGBBSE`4sG<0V)jM z7VIGXTa0x^4*~Rev9kOhoXuzpLZjgceeKJ@7#rkar8Z<;h50<{36vc;_)2BKd>lDA z&&RkNwArz0nwa=w_U2I|8Uw(>CnJ_iJmJ-+VuMGt+#4HcpFE}$2l(^#-q5o&av4tY&PyI(57qm{mPP}%@DK{lQvV-WboQ8iuREQJq7m5m9-j&KjPM_10 znVFZDo1SH}<<6C}Y?)cI;gW0DU_)&Ix!lv}_4~YjXHaov$c3$~a*eyGIT(;@lz`$7 zabtzIvwf9Qu5x=Co4v9-AS-TsN%>``>{puH{Js>I9P~R~N}JQaO7{Bi1W){rda~Oi zBa^(;*!H#=byrJaq;=9@m-Et&y{p#`~N!O1=s*hL? zUDqvA4+lN5u&E^ zDPgSp#z^blFNbs0s`kN=Qy+blq^5lSw@dYh4qpqMT`_1^OBW(g6V5dZTD|+%suv^g zOH6;6u>99T7Z|lt3ftJnY#XB|*=qxR!_>O>m(c#=s$X|*q2b!~>)ILZW)Ai=qh7ZD z1-6SawU1;)OC^>|B4)gYVKSboJ0zAvA`20Y5Nn+uqA#iMV3{Fu4NdA0Q8PE(38Q7| zXA&zXY96}FiO$7?M17VAuONq+WQ{i!-0t!squbbdww-r4u(NN3-tMlZw+FlHWR<)- z5ASX_cJ~soP7&7DGlPFojGqhS56AeUj`0?$zJ9*4AV7C?z1h5V4ISS1_hTQPq$)fu z{k3VN_JBe^IZ7VepZ!7UJ{o;D5|$p-=PuTNoHK{^Ke@ZMFF?OgEmzgbKdE-A7wSC{ z8_y=6BYR{Md2SQml!a&1%(K_dKlO?#oX=NF^&eiz`(z((l3ky5IO(A3nRjiTTHb%z zdBF3=A3m)=^xo)(;nO24PFqcha4;fCER$GGW>8Nc=M`cV=73(Vx+Hch-&oPHd}7lF zB)>lWVSRB$I^7x^P&WJM;J^N&KDJlQrUT_t!*a5F<%u~1%jnV1NY~CqH$wwd%~H2X zR_FoTJM91?)&g0^{fibyUzga=iJFbo&JrsWRO`#tDY#)8`IIl2M&8q2z(hHb>9fcv zCMFjoGIbWwi}gGVJ4P<@uw!HxSN$8LCP&kydUrwnazB0BL5}YH_^tQ%Q|lYBG5R}= zN-Upf4$RX966F%%E>5$Z=)L;0NHj=T-uA#*Jfv$=bi2azE|8L>}n+NmtoA>WK6r^%*Ri z+N&=s)~C}QJ#YE?yXXaV_HWb>Q#0@kogoQe5(LZlwON+gyZPI5Pm-2unLWVO<4OAS zj4P!qj#;K|jNdqQqh;Dycd~Xvx{C+j5B$p#5BEU|2=>y-CHUvWzd69aD8_|;{)dEr fLoSR<1|bpu#%|%4CH6mM%$(z!OE7Fdf|>sY%|j-7 literal 0 HcmV?d00001 diff --git a/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w669.00.otf b/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w669.00.otf new file mode 100644 index 0000000000000000000000000000000000000000..0f297c14f79290be889bc980edb6281bcce41d32 GIT binary patch literal 2672 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 literal 0 HcmV?d00001 diff --git a/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w800.00.otf b/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w800.00.otf new file mode 100644 index 0000000000000000000000000000000000000000..9f86e12506ed1eaf15754faca1fe8ede2a898a6b GIT binary patch literal 2220 zcmb6adu$VR_`7Sb9#F;}oIzlCjzTbOr5kJm5#mO-A#2^-#!zIAqrI(V>%+Dy8=DiE z;=&D$h6qAmrE3!lgo;Ttsf!Jg$njlx)<5~Zq81X_D|kfHpG#vX??<@f_XR^^XCVjsv!I<1V?Jge1tyf@JPk>(H^J+`3da+?{vjU* z+Tibm-T-E*5te}u{Llgca6>(~AcXafBIJMtvSA@|1-LgpSO+5VLG%owj|d*5ycTVS zOyq2+b%I&*Z-hpq6%R5J&kBqQA_O3en0Qvgas&aqs9%8#Sq;VLw*hNf@HK0gMAbtR z<`Xb7JE6^i#n_b<3UOZ+gB@-0o&uOp#6D`FRLi>_JIHz@W3|>p5G_8e%>OH=3w0rI zXqNB;n;Cs9um~%8Flz(G6R$d?%?M#Bm-wtwm~%4cPg2T_v!bO&lk!>$u$Uk3wh?}YzaT~#s998Dfz!86Tp{=cZ+7;gMfq7d7E696 zpJU0+;WZOq*@E48g8YkKhcDpw1%#03H1h?GjeLc>-W3Y+6=F~fgo(1$x3Q@~;O%a& z!{y`ML0)u+Tw;J1_<&gNCLbj_`A|S`iXI`*!21FZ3w8fdk9T`{%*3zsy0JQ68Nw!l zyx?`t!yG=XhQrt74F%j{(3}xpKOPs5*A|dZEa0R1qGs&HMn2*(E1p#%dc}a-@i?=V z9k+17%ZUMq2Y?2eta2?iq@=6HzR2(&ulA)5_eIM4(#{U6MlNznVx(vrGlEnx#cDbA zl9I(lk|jnx8LQ^fuE<1|MlLWTmUWya$d_VSC`E=*L{23rDcWYe=|9|e&2=YqJy*`{ z7?8UL-6z^lr(V1_D1+&9`CHlW+ljPeVdcxneomUt$Y-$dAQMZCwQ^D}qc~8!&cv3= zb2%xWk#C_mz{Enad`_CnC^t~N$;5Wb(>N)QQP!Y%jmeAvC$%!m)T!#KIqE70+Z{T% zc88x$gUz!0E7>8_H)R`}R%9qR$+T=?)cqBl+0EQgGvxW1@)`%b>&VeFJ>9HYc|=`s zWr<9)JLM^ySWx(!N|&=et)Dq}2UvO1<*P&A+-B8|SREI(*V}3Ywx|7+e_tzm?u)aR z5B|!JLuVik*{2r!x0Y6*6RrcUCddgjd)4eCssmsZ_&Co-G^R+RVQ5?xxPSCAM8^ms+;5dpz4~y1eXxtv%A7ZEW`k`###egFV*$ zNw4%HD_v;+$^I*T=kg<0PssYigUGUr&oM3UF-bsE)VLJo8RP6jQ6W{yFwQ;@ZB13* zm+%fU#gaiXPUsrnHKEIx5^bBT+~yu*(Iy7qWSR~$fU*|bs({tz7m&H6Jvd#+-xmL@ dPMnQ6VUqFBK8jN+{%_Xs{Rxza&~#4$`~&^LOcVeB literal 0 HcmV?d00001 diff --git a/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w889.00.otf b/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w889.00.otf new file mode 100644 index 0000000000000000000000000000000000000000..ea969dc217de94cb2ab3c4fe95de06b01d120cea GIT binary patch literal 2556 zcmb7Ge^6A{6+Um@KJp+N0xU1p5Xr+};*5X`t-1_N%5NLR;;;y6Cm|2^0SoLd?himh zI*g5t&2u`YvC&LSG;vgHY>S=5+IE<9YU|kk(PX64#x&KI=_IL>4rztCZ+T0$-+fCG zn~t67W9~iY`_8%No_o*T%WZCLZKgYDoJ87M*Vw3fUR!VyIad=&@r}N1we>?yaiYv) zM5H$R)>d2*=`fK{4g6SJbIV3?7c|QJ1v;$MRTERut>_%P17@8b)gKA=L%7T>WWrETdR9>`M!5XH;1KoU6}Pdl~)Z%#|$ zQRMkI?#!J&`FPr&d^Pp84Mcle_D+a{seN?Gxleddbu@jFoMH_BCq#KQqPgv+ImA4axN;UOC8O$SfN>X*Fa9tO7asDN3c% zrzw>hogCd^d&7ZTyt93|#0lTql5Vy>0UZtAOUE?zI{$tB_b1?hlOPDowrJ6q9`Wh1 zj?G%AFHkz{^;VX7Jls}o7MS-u<0>>)2xg(fyjL_;=V{Be5L$3bD{9YOcqzNEkD6Wg zt{~c(=UQoQz0=(0S_LGnb`_hC<&umkup}4#o<5|ckRhxRYHR%APJNR$sD;YQD=RmY zR(L!cO4JHZd4+15RNrnS6pX45hq}U%!Ei*2>HadcW?(>V3H0>FqH2pC)gwck+#DW_ z_iO5wK&Y!XtOla09>66PQ8hK9_XPO5biW#lXnsAYMf%lndAr+C-ZJ_{W zSA8)g5mmL2e=TB!?K!%_@lY%h(4%E5(_5%w4^?{))ulcBJl`;aT$=bIZn5d58}*PL z33T1cY)3~emzTG9F5PYdue8Ybxx?{**kZ1*TvLhJ_jXL>%}yn@PPxy|S}rB=njskr zW6~m|S)A-ptjlJpl*ln8>&MA9#r-zp*jT(MEqXc?8?ejCQlP{vAT*12*$ZRd59g-i zFaO}h-6>MWDV4fDPFLICI5l(P>e*|DSYFY55v$7kEz9a+nFraj9XC2BXPC;a^y9Sw2~u=;gu3?fLA3qWm0m+d|h!<5gAsq#FiPH zlnn;`>jrpL{*0Gx-Ys4{SY`c4`8z`BQYlupA z=l^`+S6Ad8KB5@kkak*vrM6lRj>s>@U(uf3CA(F)w7tsKPXCU5Np`J>PvG zE?+;yGLBt4BcqPLnVxv_)SIWKC;mD)bA)A-TrXj3@|esrH!&r~@;A-R&o#5Vo?r!D zmg8RhB26XEDn?1VVAoNM1GHFqL2)~BP@sn-md^|HT{0MS9}47fr1`rzzsKg?uBPz@ z>!iX6OY4>_2&-pOnYU39fIg|*u)%$HiE^;?ZYl9SP^?>We^f-tza-ZvCJJbTr6d;{ z(iVqQ zkR?7YpB(;4$IB6U{x~Z*H}f+&n;e4R`hr>Hy8i^f*y{7lm9G0=;p(G#)^`l9G#VL( z>-POw`)}Xx%3T=CH*YAnu<&b`-}+f}58fd{ePdHAUN!vx0e(un#|Jxqz2K$d$IT6| Y9K4i1#;u=z>)1Vy@!2QXTmcpQ8v^6muK)l5 literal 0 HcmV?d00001 diff --git a/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w890.00.otf b/Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w890.00.otf new file mode 100644 index 0000000000000000000000000000000000000000..d3a3585aa4c073146722946bbf5defe970df2269 GIT binary patch literal 2556 zcmb7GeNa@_6+ds^KKdXV0xT~jA(Dqhd@fW!?jg>rOhK`o#cUH?iZ@Da%LKqwMTo~1I> zC&RA}BA;|6dj{j*0xlZl`9#VGo(7glKrhg@R{itZ&)e_#(hP~>2Vm&!m5KXp{hsq* zRFK>!z9S9*i{#)e8pb*IeS8ko2UI|r;yZSxlrPmI0(FW2qIi*JNg}7?F$Z>z)}^$t z4R!vN2lJp$8ZY~kR$W(9Pqepr@2EJC+(#Fj`-D}hgAQ}QFyb(wRD0gf(N-i8oatbO zAt_s+BA}G*piFvk-WI8jPR-lS8)76Hp^xVIH1Y`jR7)Wmpq&&ZKXp?NMKRw=NEvyl zl*(aMlMgW+q{EINGJ+VL0??{G+C#;#8o~O=W5@SXKXmh_8F^$g#zY`t8br?{Tj&9Z zMnUjvv5{@mfVdr)vjTH_AYOY^H^q=o!^qNkwu~xKRUOq(J*@TcrD_TzpN=}(X`P+7 z7ZsG;l(EgOAp%bbGpqlRvj<$1I&IJ3r#=s2yr`)XYu2a_<9SpU^bmyal!ul>R-j*w zxE0{)P+1$Kk(#iw^{~E5t^a4GxPJCI<4!|!yOEz9bP;rhjh(avIt^Zd9Q+m~ld0R3 zOb(9?Z?U5hKrY_dv0USZ?`=+rZI8l+AbS3qCST{jum5fWE;tE-khfloMD=is9&KNz z1$+G^JIX7(9)r)K zTXS3s%?)>$8(oWlq$RE*^Py~#F$I?7qTka$C?TW?i-ekLU#LT0s|{$u($b2Gl_h0f z@5*Ae%v)Nf+Ag(aCkhHg)HT7*PS2H9 zt=x8YoaJ4mn>yxSH-VqD$oIM3ai7?1F0fqV@u~N>jOR>^$2W|-&rVq`CH^BrGG<4l zIcT#e(XLn*%@Qe|Wk}W!6K#t7ZN{-NcV3$Fb|^Mr7ZW8w@hL!97Vk1=N6J6EJ`sEA z)faXqN!dloW9*25qw zC6)o=UCE8T<(p%G$>t%Il3NyN&SO<79b2p0Eul|7JTlsIFeh3mRmVFJl21B zQ2xWXwBvBs@xJHfLzk7>T8TY_X(yz~H&E7Q$+&Mcz-~9>qsHHiV}?9wymI{DhjQ;z z4<3pd--B083 z(~41?Iiv*ey69ha<(`$@$$j?{+ors<)0SjIgvW z$%3-F$CMcx6$1FilxsF90?1%lSbCQf|1K!jCAmK;q{N>SOBEBRX@sN%SF2Mfm_ls( zjZRCMQjCNk#mfviviXKb14dAqcL|n}x2$$N0lmmt!t;EqQBH=?rerSvgg S{nWRP-SZruHNoccDgWP5)7-28 literal 0 HcmV?d00001 diff --git a/Tests/varLib/data/test_results/BuildTestCFF2.ttx b/Tests/varLib/data/test_results/BuildTestCFF2.ttx index a4e859f14..0fd46aa76 100644 --- a/Tests/varLib/data/test_results/BuildTestCFF2.ttx +++ b/Tests/varLib/data/test_results/BuildTestCFF2.ttx @@ -1,5 +1,5 @@ - + @@ -67,29 +67,29 @@ - + - + - + - + - + - + - + - + diff --git a/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx b/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx new file mode 100644 index 000000000..4be193025 --- /dev/null +++ b/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx @@ -0,0 +1,1301 @@ + + + + + + + + wght + 0x0 + 200.0 + 200.0 + 900.0 + 256 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -120 50 859 91 -50 50 hstemhm + 100 50 700 50 vstemhm + hintmask 10111000 + 100 -120 rmoveto + 800 1000 -800 hlineto + 400 -459 rmoveto + -318 409 rlineto + 636 hlineto + -286 -450 rmoveto + hintmask 11011000 + 318 409 rlineto + -818 vlineto + -668 -41 rmoveto + 318 409 318 -409 rlineto + -668 859 rmoveto + 318 -409 -318 -409 rlineto + + + -12 83 1 126 2 blend + hstemhm + 74 73 -57 40 -26 129 -125 122 4 blend + vstemhm + hintmask 10100000 + 134 755 107 18 2 blend + rmoveto + -48 -137 1 blend + hlineto + 8 -565 16 50 2 blend + rlineto + 32 106 1 blend + hlineto + hintmask 11000000 + -17 -202 -52 -67 2 blend + rmoveto + 24 14 18 22 25 -14 18 -22 -23 -14 -21 -24 -18 13 -20 22 38 25 26 38 37 -28 25 -37 -34 -30 -22 -38 -40 27 -26 39 16 blend + hvcurveto + + + + + 5 vsindex + 121 30 -22 22 148 30 -30 136 23 30 129 30 116 30 -21 4 -29 52 3 92 -32 23 -21 32 -23 21 -54 9 -83 50 4 90 -50 -4 -90 22 27 62 -2 -43 -47 41 0 69 -44 0 -74 37 0 62 -50 0 -84 36 0 61 14 blend + hstemhm + 167 30 129 30 -16 16 123 30 48 30 -6 29 -29 111 -30 30 -16 16 201 30 1 29 -29 0 -49 64 0 108 -34 0 -57 51 0 85 -29 0 -48 29 0 48 -72 -2 -123 60 2 103 -69 0 -115 46 0 77 -42 0 -70 42 0 70 -42 0 -70 67 0 111 -51 0 -85 51 0 85 -29 0 -48 29 0 48 -79 0 -132 47 0 79 -45 0 -75 42 0 70 22 blend + vstemhm + hintmask 011011111011001010000000 + 326 793 1 0 2 17 0 29 2 blend + rmoveto + -280 24 0 40 1 blend + vlineto + -47 16 -8 59 -31 0 -53 6 0 10 -13 0 -21 20 0 33 4 blend + vhcurveto + hintmask 000010000000100000000000 + 13 120 4 0 6 -46 0 -76 2 blend + 0 13 4 0 7 1 blend + hhcurveto + 49 10 20 82 4 12 0 19 12 0 20 3 0 5 2 0 3 4 0 8 5 blend + hvcurveto + hintmask 101010101000010000000000 + -10 2 -11 5 -8 6 -12 0 -21 3 0 5 -21 0 -35 6 0 11 -9 0 -14 7 0 11 6 blend + rrcurveto + -75 19 0 32 1 blend + -3 -5 -10 -29 -24 -102 1 0 1 1 0 2 7 0 12 9 0 14 42 0 70 5 blend + 0 -18 6 0 10 1 blend + hhcurveto + -38 -6 4 21 10 0 18 2 0 3 0 0 -1 4 0 7 4 blend + hvcurveto + 280 -25 0 -41 1 blend + vlineto + -41 -464 -40 -8 -74 10 20 41 2 blend + rmoveto + -30 617 30 -50 -4 -90 -5 12 5 50 4 90 3 blend + vlineto + -661 -178 11 -4 12 4 -13 -7 2 blend + rmoveto + -30 689 30 -52 -3 -92 -11 0 -18 52 3 92 3 blend + vlineto + hintmask 010101100111001000000000 + -481 284 -27 -2 -48 -32 36 -21 2 blend + rmoveto + -306 30 306 0 -13 0 60 2 103 0 13 0 3 blend + vlineto + 218 0 -61 0 -102 -1 0 -1 2 blend + rmoveto + -306 30 306 0 -13 0 61 1 104 0 13 0 3 blend + vlineto + -417 358 -17 -1 -30 19 -43 -12 2 blend + rmoveto + -30 217 -116 -217 -30 247 176 -36 0 -61 -52 0 -87 50 0 84 52 0 87 -37 0 -62 -6 0 -10 23 0 39 7 blend + vlineto + 75 -26 0 -44 1 blend + hmoveto + hintmask 000010100000001001000000 + -280 24 0 40 1 blend + vlineto + -47 17 -8 60 -31 0 -53 5 0 9 -13 0 -21 20 0 33 4 blend + vhcurveto + 12 125 5 0 8 -47 0 -78 2 blend + 0 14 4 0 7 1 blend + hhcurveto + 49 11 20 82 3 12 0 20 12 0 19 3 1 6 2 1 6 5 0 9 5 blend + hvcurveto + -9 2 -12 4 -8 7 -14 1 -22 3 0 5 -19 -1 -34 7 0 12 -9 0 -14 6 0 10 6 blend + rrcurveto + -75 19 -1 29 1 blend + -3 -5 -10 -30 -25 -105 1 -1 1 8 0 13 8 0 14 42 0 70 4 blend + 0 -18 6 0 9 1 blend + hhcurveto + -40 -6 4 21 11 0 19 2 0 3 0 0 -1 4 1 8 4 blend + hvcurveto + 280 -25 -1 -42 1 blend + vlineto + hintmask 000001110000000110000000 + -16 -29 0 -48 1 blend + hmoveto + -30 217 -116 -217 -30 247 176 -36 0 -61 -50 0 -84 50 0 84 50 0 84 -37 0 -62 -3 0 -5 23 0 39 7 blend + vlineto + -424 -714 -19 0 -32 -12 0 -21 2 blend + rmoveto + -52 -54 -91 -49 -81 -33 8 -5 11 -13 4 -6 80 36 94 56 56 58 7 0 11 9 0 15 5 0 9 11 0 18 -2 0 -3 9 0 15 13 0 22 -11 0 -18 24 0 39 -22 0 -36 11 0 19 -12 0 -21 4 0 7 -4 0 -6 2 0 2 -2 0 -4 -1 0 -1 3 0 5 18 blend + rrcurveto + 200 -7 -92 0 -154 -5 0 -8 2 blend + rmoveto + 76 -41 90 -62 46 -42 -6 0 -10 5 0 8 -5 0 -7 6 0 10 -4 0 -7 4 0 7 6 blend + rrcurveto + 22 23 -46 42 -91 60 -75 39 60 0 100 29 0 48 0 0 -1 -3 0 -5 3 0 5 -7 0 -11 6 0 11 -7 0 -11 8 blend + rlinecurve + -499 750 -48 0 -81 6 0 10 2 blend + rmoveto + -54 -167 -87 -164 -96 -108 7 -6 11 -12 4 -6 98 116 88 165 58 175 7 0 13 15 0 25 10 0 16 14 0 22 11 0 19 10 0 17 9 0 15 -20 0 -33 15 0 24 -44 0 -73 4 0 7 -18 0 -30 4 0 6 4 0 6 3 0 6 19 0 32 0 0 -1 1 0 1 18 blend + rrcurveto + -113 -214 -60 0 -100 -23 0 -37 2 blend + rmoveto + -691 30 718 20 0 33 64 0 108 43 0 72 3 blend + vlineto + -1 -1 0 -3 1 blend + 2 rlineto + + + 1 vsindex + -72 30 253 30 94 30 92 30 65 30 131 45 -30 112 -99 17 -8 0 -12 59 -2 85 -64 2 -92 39 -1 56 -44 2 -63 35 -1 51 -43 1 -62 39 -1 56 -21 0 -31 56 -2 81 -56 2 -81 44 -2 63 -57 3 -81 31 -1 45 -18 0 -27 44 -2 63 16 blend + hstemhm + 193 30 83 30 147 30 173 30 66 30 -20 1 -28 75 -3 107 -74 3 -106 78 -4 111 -99 5 -141 81 -3 116 -79 2 -114 64 -2 92 -81 4 -115 80 -4 114 10 blend + vstemhm + hintmask 1111100011111000 + 306 142 -19 1 -27 7 0 10 2 blend + rmoveto + -156 45 -2 64 1 blend + vlineto + -50 22 -8 79 -42 2 -59 8 -1 11 -18 0 -27 43 -1 62 4 blend + vhcurveto + 17 186 8 -1 11 -68 3 -97 2 blend + 0 18 8 0 12 1 blend + hhcurveto + 70 13 25 114 5 22 -1 31 17 -1 24 2 1 4 0 -1 -1 7 0 10 5 blend + hvcurveto + -9 3 -12 4 -9 6 -20 1 -28 3 0 4 -31 1 -45 10 0 15 -13 0 -19 9 -1 13 6 blend + rrcurveto + -109 -4 -7 -13 -49 -38 -156 33 -1 47 -1 0 -1 1 0 1 2 0 3 10 0 15 9 0 13 60 -3 85 7 blend + 0 -27 5 0 8 1 blend + hhcurveto + -59 -10 5 22 11 0 16 2 -1 2 -1 0 -2 4 0 6 4 blend + hvcurveto + 157 -47 2 -67 1 blend + vlineto + 63 34 -74 3 -106 -25 1 -36 2 blend + rmoveto + 65 -30 74 -47 37 -37 -7 1 -10 5 -1 7 -3 0 -4 5 0 7 -3 0 -4 6 0 9 6 blend + rrcurveto + 20 22 -37 36 -75 47 -65 28 48 -2 68 47 -2 67 -1 0 -1 -6 1 -8 2 0 3 -8 0 -11 8 -1 11 -6 0 -9 8 blend + rlinecurve + 320 -64 -49 3 -69 -32 1 -46 2 blend + rmoveto + 76 -49 83 -75 38 -12 0 -18 -6 1 -8 -13 0 -19 -4 0 -6 -9 1 -12 5 blend + -54 rrcurveto + 23 19 -38 54 -84 73 -76 49 69 -3 98 35 -2 50 5 0 8 2 0 3 11 0 16 2 0 2 13 -1 18 2 0 4 8 blend + rlinecurve + -557 -5 -85 4 -121 -6 0 -9 2 blend + rmoveto + -28 -68 -50 -72 -77 -40 5 -1 6 1 0 1 3 1 5 6 0 9 13 -1 18 1 0 1 6 blend + rrcurveto + 24 -17 79 42 47 74 31 71 62 -3 89 -42 2 -60 -7 1 -10 5 -1 7 -6 0 -8 1 0 1 -2 -1 -4 4 1 7 8 blend + rlinecurve + -117 625 -26 2 -36 42 -3 59 2 blend + rmoveto + -30 775 30 -57 3 -81 -3 0 -5 57 -3 81 3 blend + vlineto + -818 -176 -1 0 -2 12 0 18 2 blend + rmoveto + -30 869 30 -56 2 -81 3 0 5 56 -2 81 3 blend + vlineto + hintmask 0000001000100000 + -455 258 -40 2 -57 -38 2 -54 2 blend + rmoveto + hintmask 0000000100100000 + -99 30 -18 0 -27 81 -3 116 2 blend + vlineto + hintmask 0000001000100000 + 99 18 0 27 1 blend + vlineto + hintmask 0111010010001000 + -236 -127 -60 2 -86 -18 0 -27 2 blend + rmoveto + 26 -40 25 -53 9 -36 -12 1 -17 10 0 15 -10 -1 -15 13 0 19 -4 0 -6 11 -1 15 6 blend + rrcurveto + 29 12 -10 35 -25 53 -27 39 76 -3 109 12 0 18 3 1 5 -10 0 -15 10 -1 14 -15 1 -21 11 0 16 -11 0 -16 8 blend + rlinecurve + 393 2 -112 4 -161 -1 0 -2 2 blend + rmoveto + -16 -38 -31 -57 -23 -35 7 0 11 12 -1 17 13 -1 18 19 0 28 10 0 14 8 -1 11 6 blend + rrcurveto + 27 -12 24 36 26 48 23 46 70 -2 101 -10 1 -14 -8 0 -12 -13 1 -18 -6 -1 -9 -17 0 -25 -1 1 -1 -10 1 -14 8 blend + rlinecurve + -504 -378 27 -1 39 -8 0 -12 2 blend + rmoveto + 559 -94 -559 -110 5 -157 44 -2 63 110 -5 157 3 blend + hlineto + 216 -52 2 -74 1 blend + vmoveto + 559 -92 -559 -110 5 -157 43 -1 62 110 -5 157 3 blend + hlineto + -30 122 -75 3 -107 -4 0 -6 2 blend + rmoveto + -276 619 276 -26 0 -38 45 -2 64 26 0 38 3 blend + vlineto + + + 5 vsindex + -67 29 219 30 154 30 -16 16 150 30 -30 122 -85 30 -18 18 87 30 -30 140 -122 12 -14 0 -22 46 0 78 -59 -3 -106 46 0 77 -53 -9 -92 46 2 81 -18 20 -1 18 -20 1 -54 13 -80 46 2 81 -46 -2 -81 25 31 61 -14 -34 -48 60 0 100 -64 0 -107 64 0 107 -55 0 -92 54 0 90 -54 0 -90 36 0 59 -19 0 -31 37 0 62 22 blend + hstemhm + 51 188 -30 30 -30 149 21 30 -18 18 -13 13 66 30 -12 12 135 30 41 30 172 30 -6 28 -8 0 -14 30 0 50 -62 0 -103 62 0 103 -62 0 -103 32 0 53 -5 0 -7 59 0 98 -24 0 -41 24 0 41 -16 0 -27 16 0 27 -32 0 -53 53 0 88 -33 0 -56 33 0 56 -87 0 -146 63 0 106 -42 0 -70 54 0 90 -99 0 -165 55 0 91 -42 0 -70 45 0 75 24 blend + vstemhm + hintmask 000000100001000000000000 + 51 612 -8 0 -14 29 0 49 2 blend + rmoveto + -30 -60 0 -100 1 blend + vlineto + hintmask 000000100000010000000000 + 307 30 60 0 100 1 blend + hlineto + hintmask 000000010010100100000000 + -149 228 -32 0 -53 -20 0 -34 2 blend + rmoveto + -918 30 918 -19 0 -32 62 0 103 19 0 32 3 blend + vlineto + -36 -238 -55 0 -91 -32 0 -53 2 blend + rmoveto + -31 -160 -74 -193 -68 -95 7 -5 10 -11 6 -8 70 101 74 203 33 160 6 0 10 25 0 42 13 0 21 23 0 37 4 0 7 1 0 2 8 0 14 -18 0 -30 13 0 21 -27 0 -44 4 0 7 -19 0 -32 1 0 2 6 0 10 -12 0 -20 -2 0 -3 -2 0 -4 -1 0 -2 18 blend + rrcurveto + 4 -143 19 0 32 77 0 128 2 blend + rmoveto + -21 -16 25 -26 72 -92 21 -33 -23 0 -38 -34 0 -57 1 0 2 -15 0 -24 -12 0 -21 -6 0 -11 2 0 3 -18 0 -29 8 blend + rlinecurve + 24 24 -18 25 -81 96 -22 22 28 0 48 63 0 105 2 0 2 -1 0 -2 1 0 3 10 0 16 1 0 1 1 0 2 8 blend + rlinecurve + 157 278 1 0 1 -14 0 -23 2 blend + rmoveto + hintmask 000000001000000100000000 + -30 559 -54 0 -90 -17 3 -23 2 blend + vlineto + hintmask 010000000010000000100000 + 30 54 0 90 1 blend + vlineto + -457 -518 29 -3 43 -9 -3 -20 2 blend + rmoveto + -30 176 30 -46 0 -77 -17 0 -27 46 0 77 3 blend + vlineto + hintmask 000000000100000001010000 + -194 120 -3 0 -5 -42 37 -35 2 blend + rmoveto + -365 30 365 38 -29 45 53 0 88 -38 29 -45 3 blend + vlineto + 135 508 -87 0 -146 33 -34 24 2 blend + rmoveto + hintmask 000000000010000000010000 + -122 30 -19 0 -31 63 0 106 2 blend + vlineto + hintmask 000101000100000000010000 + 122 19 0 31 1 blend + vlineto + -115 -172 -60 0 -100 -27 34 -19 2 blend + rmoveto + -288 30 288 11 -24 18 50 0 83 -11 24 -18 3 blend + vlineto + 148 -62 -2 -106 1 blend + hmoveto + -288 30 288 11 -24 18 50 0 83 -11 24 -18 3 blend + vlineto + 156 -394 -30 2 -47 19 -34 6 2 blend + rmoveto + -52 -36 -89 -48 -61 -29 7 0 12 2 0 4 14 0 23 3 0 4 11 0 18 4 0 8 6 blend + rrcurveto + 15 -21 62 28 86 41 57 44 25 0 42 -39 0 -66 -10 0 -17 -4 0 -6 -12 0 -19 -3 0 -5 -6 0 -11 -5 0 -9 8 blend + rlinecurve + hintmask 101010000000000010001100 + -541 323 10 0 17 44 5 84 2 blend + rmoveto + -30 517 -150 -517 -30 547 210 -46 -2 -81 -74 0 -123 54 -13 80 74 0 123 -46 -2 -81 -19 0 -32 38 17 82 7 blend + vlineto + -232 -242 -10 0 -16 -28 29 -27 2 blend + rmoveto + -344 58 -32 71 1 blend + vlineto + -47 15 -9 54 -33 -2 -58 3 0 4 -15 0 -25 22 0 37 4 blend + vhcurveto + hintmask 100000000010001000001010 + 12 100 3 0 5 -47 0 -78 2 blend + 0 12 4 0 6 1 blend + hhcurveto + 48 10 25 102 3 12 0 20 11 0 19 4 1 9 11 -1 16 5 0 8 5 blend + hvcurveto + -9 3 -11 4 -8 6 -14 0 -23 3 -1 5 -23 1 -37 8 1 15 -8 -1 -15 8 0 12 6 blend + rrcurveto + -97 11 1 20 1 blend + -3 -4 -14 -29 -21 -84 0 0 1 1 -1 1 10 0 16 10 1 17 43 -1 71 5 blend + 0 -16 7 0 12 1 blend + hhcurveto + -33 -6 5 22 13 0 22 3 0 5 -1 0 -2 4 0 7 4 blend + hvcurveto + 344 -59 34 -71 1 blend + vlineto + -346 -371 -24 0 -41 65 -34 78 2 blend + rmoveto + 10 -31 77 16 100 22 99 21 3 0 5 -54 0 -90 -2 0 -3 -3 0 -5 -9 0 -15 -6 0 -10 -10 0 -17 -5 0 -8 8 blend + rlinecurve + -2 29 -108 -22 -104 -22 -72 -13 -3 0 -5 52 0 86 9 0 16 6 0 10 8 0 13 6 0 11 4 0 6 4 0 6 8 blend + rlinecurve + -16 767 -44 0 -72 -13 0 -21 2 blend + rmoveto + -316 -6 0 -11 1 blend + vlineto + -142 -7 -194 -74 -141 2 0 2 -2 0 -2 2 0 4 6 0 9 4 blend + vhcurveto + 8 -3 13 -7 5 -6 13 0 21 -7 0 -11 25 0 43 -20 0 -34 11 0 17 -10 0 -17 6 blend + rrcurveto + 75 143 10 205 145 4 0 7 3 0 5 2 0 4 21 0 35 9 0 15 5 blend + vvcurveto + 316 6 0 11 1 blend + vlineto + + + 3 vsindex + -71 30 427 30 153 30 33 111 -30 30 -30 126 -6 0 -13 45 0 102 -58 0 -132 38 0 87 -48 0 -111 38 0 87 -4 -2 -13 21 2 53 -43 0 -99 43 0 99 -43 0 -99 24 0 55 12 blend + hstemhm + 159 30 -19 19 126 30 -6 30 281 30 160 30 18 31 -7 0 -16 50 0 114 -18 0 -42 18 0 42 -71 0 -161 50 0 114 -26 0 -61 48 0 111 -66 0 -150 51 0 115 -68 0 -154 50 0 114 -36 -1 -84 44 1 101 14 blend + vstemhm + hintmask 1110100101110000 + 58 743 -1 0 -2 26 0 60 2 blend + rmoveto + -30 887 30 -43 0 -99 2 0 5 43 0 99 3 blend + vlineto + hintmask 0000010010000000 + -630 96 -29 0 -66 -19 0 -44 2 blend + rmoveto + hintmask 0001000010000000 + -207 30 -2 -2 -9 50 0 114 2 blend + vlineto + hintmask 0000010010100000 + 207 2 2 9 1 blend + vlineto + 305 -44 0 -100 1 blend + hmoveto + hintmask 0001000000100000 + -207 30 -2 -2 -9 51 0 115 2 blend + vlineto + hintmask 0010011000100000 + 207 2 2 9 1 blend + vlineto + -521 -240 -36 0 -82 2 0 4 2 blend + rmoveto + -206 -5 0 -10 1 blend + vlineto + -137 -15 -184 -109 -136 5 0 11 3 0 6 5 0 10 -1 0 -1 8 0 19 5 blend + vhcurveto + 7 -3 12 -9 5 -6 12 0 27 -6 0 -13 22 0 51 -15 0 -35 10 0 21 -8 0 -19 6 blend + rrcurveto + hintmask 1110000101010000 + 112 139 18 194 141 3 0 7 -4 0 -8 1 0 3 11 0 24 4 0 10 5 blend + vvcurveto + 207 5 0 11 1 blend + vlineto + -19 -18 0 -42 1 blend + hmoveto + -30 670 -153 -670 -30 700 213 -38 0 -87 -64 0 -144 48 0 111 64 0 144 -38 0 -87 -14 0 -30 28 0 63 7 blend + vlineto + -531 -249 -15 0 -36 -23 0 -51 2 blend + rmoveto + -343 50 0 112 1 blend + vlineto + -66 31 -12 105 -29 0 -66 6 0 14 -13 0 -28 29 0 66 4 blend + vhcurveto + 23 278 5 0 12 -59 0 -134 2 blend + 0 24 6 0 14 1 blend + hhcurveto + hintmask 1000000001001000 + 96 15 31 123 8 20 0 44 11 0 26 4 0 8 14 0 32 5 0 11 5 blend + hvcurveto + -9 3 -13 4 -9 7 -13 0 -30 2 0 5 -21 0 -48 8 0 17 -10 -1 -23 6 0 15 6 blend + rrcurveto + -117 -6 -11 -21 -69 -56 -236 8 0 18 -1 1 -1 1 -1 1 3 0 7 3 1 9 7 0 15 49 0 112 7 blend + 0 -41 4 0 8 1 blend + hhcurveto + -84 -16 11 37 4 0 10 2 0 5 -3 0 -7 1 0 2 4 blend + hvcurveto + 343 -51 0 -115 1 blend + vlineto + 444 -47 -59 0 -135 26 0 59 2 blend + rmoveto + -101 -52 -195 -56 -169 -40 4 -7 5 -10 3 -7 172 40 193 54 120 56 4 0 8 3 0 7 18 0 43 9 0 19 12 0 26 8 0 19 5 0 12 -10 0 -22 7 0 15 -18 0 -41 1 0 3 -11 0 -25 -8 0 -19 -9 0 -21 -8 0 -18 -8 0 -19 5 0 11 0 0 1 18 blend + rrcurveto + + + 2 vsindex + 64 30 77 30 76 30 74 30 72 30 109 30 25 84 -30 30 -30 108 -2 0 -2 42 0 47 -48 0 -54 38 0 43 -48 0 -54 38 0 43 -46 0 -52 42 0 47 -43 0 -48 56 1 63 -72 -1 -81 57 1 64 -8 -32 -41 30 32 65 -65 -1 -73 65 1 73 -65 -1 -73 43 0 49 18 blend + hstemhm + 135 30 21 30 102 30 14 30 205 30 17 30 113 30 19 30 -19 0 -21 87 2 98 -86 -2 -97 99 1 111 -125 -1 -141 98 1 111 -79 -1 -89 75 1 84 -99 -1 -111 75 1 84 -77 -1 -86 100 1 112 -127 -1 -143 105 1 118 -102 -1 -114 94 1 105 16 blend + vstemhm + hintmask 111111010011001100000000 + 53 761 -3 0 -3 36 0 40 2 blend + rmoveto + -30 896 30 -65 -1 -73 5 0 5 65 1 73 3 blend + vlineto + hintmask 000000001001000000000000 + -631 78 -46 0 -52 -22 0 -24 2 blend + rmoveto + hintmask 000000100001000000000000 + -162 30 -8 -32 -41 98 1 111 2 blend + vlineto + hintmask 000000001001001000000000 + 162 8 32 41 1 blend + vlineto + 296 -105 -1 -118 1 blend + hmoveto + hintmask 000000100000001000000000 + -162 30 -8 -32 -41 100 1 112 2 blend + vlineto + hintmask 000000001000001000000000 + 162 8 32 41 1 blend + vlineto + hintmask 000011000100110010000000 + -47 -217 -23 0 -26 -57 -1 -64 2 blend + rmoveto + 209 -109 -209 -101 -1 -113 72 1 81 101 1 113 3 blend + hlineto + -235 109 24 0 27 -72 -1 -81 2 blend + rmoveto + 205 -109 -205 -99 -1 -111 72 1 81 99 1 111 3 blend + hlineto + -227 109 18 1 21 -72 -1 -81 2 blend + rmoveto + 197 -109 -197 -93 -2 -105 72 1 81 93 2 105 3 blend + hlineto + -30 139 -87 -2 -98 -15 0 -17 2 blend + rmoveto + -169 731 169 -41 0 -46 38 0 42 41 0 46 3 blend + vlineto + hintmask 111100000010000100000000 + -650 -375 62 1 70 -32 0 -36 2 blend + rmoveto + 571 -76 -571 -159 -1 -179 48 0 54 159 1 179 3 blend + hlineto + -30 -38 0 -43 1 blend + vmoveto + 571 -77 -571 -159 -1 -179 48 0 54 159 1 179 3 blend + hlineto + 287 -66 -1 -74 1 blend + vmoveto + 571 -74 -571 -159 -1 -179 46 0 52 159 1 179 3 blend + hlineto + -30 104 -99 -1 -111 -4 0 -5 2 blend + rmoveto + -347 631 347 -18 0 -20 45 0 50 18 0 20 3 blend + vlineto + -216 -389 -86 -1 -96 -31 0 -35 2 blend + rmoveto + 127 -34 121 -39 72 -31 -17 0 -19 2 0 2 -13 0 -15 -2 0 -2 -13 0 -15 3 0 3 6 blend + rrcurveto + 31 22 -78 32 -126 39 -121 136 1 153 39 0 44 1 0 1 -3 0 -3 -8 0 -9 4 0 5 9 0 10 7 blend + 31 rlinecurve + -258 -1 -67 -1 -75 0 0 -1 2 blend + rmoveto + -81 -39 -128 -36 -107 -23 8 -6 12 -12 5 -6 103 25 130 41 86 43 9 0 10 6 0 7 3 0 4 7 0 8 -4 0 -5 7 0 8 19 0 22 -14 0 -16 32 0 36 -32 0 -36 17 0 19 -19 0 -21 3 0 3 -1 0 -1 5 0 6 2 0 2 1 0 1 4 0 5 18 blend + rrcurveto + + + 2 vsindex + -60 30 203 30 -9 9 67 7 -7 14 -14 30 -20 20 80 30 59 30 121 30 18 93 -30 30 -30 108 -23 0 -26 67 2 76 -98 -2 -111 42 0 47 -13 0 -14 13 0 14 -33 0 -37 11 0 13 -11 0 -13 8 0 9 -7 0 -8 53 0 60 -32 0 -36 32 0 36 -52 0 -59 57 1 65 -33 0 -38 53 0 60 -83 -1 -93 54 0 60 -6 -19 -24 33 19 55 -76 -1 -86 76 1 86 -76 -1 -86 59 1 67 26 blend + hstemhm + 77 30 42 30 139 30 23 30 71 10 74 30 15 30 16 30 158 30 28 30 -4 29 -14 0 -16 88 1 99 -82 -1 -92 87 1 98 -130 -1 -146 102 1 114 -73 -1 -82 74 2 84 -112 -2 -126 27 0 30 13 0 15 90 1 101 -126 -1 -142 75 1 84 -68 -1 -76 102 1 115 -144 -1 -162 94 1 105 -79 -1 -88 95 1 106 -81 -1 -91 74 1 83 22 blend + vstemhm + hintmask 110001011101011101101101 + 53 761 -3 0 -3 31 0 35 2 blend + rmoveto + -30 896 30 -76 -1 -86 5 0 5 76 1 86 3 blend + vlineto + -802 -461 2 0 2 -23 0 -26 2 blend + rmoveto + -30 703 30 -53 0 -60 3 0 4 53 0 60 3 blend + vlineto + hintmask 000000000000100100000000 + -532 539 -58 -1 -65 6 0 7 2 blend + rmoveto + hintmask 000000000010000100000000 + -171 30 -16 -19 -36 102 1 114 2 blend + vlineto + hintmask 000000000000100100001000 + 171 16 19 36 1 blend + vlineto + 299 -100 -1 -112 1 blend + hmoveto + hintmask 000000000010000000001000 + -171 30 -16 -19 -36 102 1 115 2 blend + vlineto + hintmask 000000000000100000001000 + 171 16 19 36 1 blend + vlineto + hintmask 000000111100011010010100 + -46 -219 -34 0 -39 -64 -1 -72 2 blend + rmoveto + 204 -121 -204 -110 -1 -123 83 1 93 110 1 123 3 blend + hlineto + -230 121 33 1 38 -83 -1 -93 2 blend + rmoveto + 200 -121 -200 -108 -2 -122 83 1 93 108 2 122 3 blend + hlineto + -222 121 27 -1 30 -83 -1 -93 2 blend + rmoveto + 192 -121 -192 -101 -1 -114 83 1 93 101 1 114 3 blend + hlineto + -30 151 -87 -1 -98 -29 0 -33 2 blend + rmoveto + -181 716 181 -24 0 -27 11 0 12 24 0 27 3 blend + vlineto + -788 -240 -17 0 -19 9 0 11 2 blend + rmoveto + -130 30 100 -37 0 -42 88 1 99 -20 0 -23 3 blend + vlineto + hintmask 000000110000000000000010 + 786 -100 30 130 -150 -1 -168 20 0 23 95 1 106 37 0 42 4 blend + hlineto + hintmask 000010000000000100000000 + -610 -123 -56 -1 -63 -44 0 -50 2 blend + rmoveto + -50 -62 -93 -73 -118 -54 8 -4 10 -9 6 -7 9 0 11 13 0 15 19 0 21 29 0 32 9 0 10 22 0 25 12 0 14 -11 0 -12 19 0 21 -26 0 -30 7 0 8 -16 0 -18 12 blend + rrcurveto + hintmask 010000000000000001000000 + 121 58 92 75 59 70 3 0 3 -13 0 -14 -10 0 -11 -19 0 -21 -2 0 -2 8 0 8 6 blend + rrcurveto + 124 -78 -89 -1 -100 32 0 36 2 blend + rmoveto + -7 -6 0 -6 1 blend + vlineto + -65 -139 -176 -81 -162 -31 6 -6 8 -12 3 -8 16 0 17 30 0 34 36 0 41 26 0 29 -7 0 -8 12 0 13 12 0 14 -16 0 -18 15 0 16 -30 0 -33 5 0 6 -18 0 -21 12 blend + rrcurveto + hintmask 001000000000000001000000 + 168 37 178 84 72 154 26 0 29 -5 0 -5 -23 0 -26 -12 0 -14 -5 0 -5 6 0 7 6 blend + rrcurveto + hintmask 110100000000000001100001 + -19 11 -6 -2 -47 0 -53 13 0 15 -13 0 -15 0 0 -1 4 blend + rlineto + -333 -72 75 1 85 -55 0 -61 2 blend + rmoveto + 65 -25 75 -46 38 -35 -35 0 -40 8 0 9 -38 0 -42 15 0 17 -18 0 -21 14 0 15 6 blend + rrcurveto + 26 19 -39 34 -76 45 -64 25 49 0 56 31 0 35 19 0 21 -14 0 -16 39 0 44 -18 0 -20 32 0 36 -9 0 -10 8 blend + rlinecurve + 72 55 -55 0 -62 28 0 31 2 blend + rmoveto + -30 -30 -42 0 -47 -42 0 -47 2 blend + rlineto + 269 30 -14 0 -16 42 0 47 2 blend + hlineto + 74 74 13 0 15 -22 0 -24 2 blend + rmoveto + -276 80 1 90 1 blend + vlineto + -52 21 -9 77 -48 0 -54 8 0 9 -21 0 -24 44 0 49 4 blend + vhcurveto + 16 182 8 0 9 -90 -1 -101 2 blend + 0 18 8 0 9 1 blend + hhcurveto + 62 12 21 88 4 25 0 28 20 0 22 6 0 7 10 0 11 9 0 10 5 blend + hvcurveto + -9 2 -12 5 -8 6 -24 0 -26 4 0 5 -34 0 -39 12 0 13 -16 0 -18 10 0 11 6 blend + rrcurveto + -81 25 0 28 1 blend + -4 -6 -11 -41 -37 -154 -1 0 -1 0 1 1 11 -1 12 15 1 17 79 1 89 5 blend + 0 -26 9 0 10 1 blend + hhcurveto + -56 -9 6 25 17 0 19 2 0 3 -1 -1 -2 4 0 5 4 blend + hvcurveto + 276 -81 -1 -91 1 blend + vlineto + 278 -62 -114 -1 -128 32 0 36 2 blend + rmoveto + -66 -32 -126 -33 -107 -23 5 -7 5 -10 2 -7 110 22 126 32 81 36 10 0 11 7 0 8 30 0 34 11 0 12 21 0 23 9 0 10 7 0 8 -14 0 -16 9 0 10 -27 0 -30 3 0 4 -15 0 -17 -15 0 -17 -10 0 -11 -12 0 -14 -11 0 -12 3 0 4 -3 0 -4 18 blend + rrcurveto + + + 3 vsindex + -58 30 100 30 70 22 -22 30 94 30 19 31 -17 28 152 20 -20 30 -12 12 66 30 -30 89 -5 30 -30 121 -11 0 -24 36 0 81 -32 0 -74 22 0 52 -17 0 -39 16 1 37 -16 -1 -37 21 0 48 -27 0 -63 21 0 49 -11 0 -26 41 0 93 -47 0 -107 24 0 56 -34 0 -78 11 0 26 -11 0 -26 17 0 39 -15 0 -35 15 0 35 -19 0 -43 12 0 26 -12 0 -26 4 0 8 -5 0 -11 28 0 65 -28 0 -65 23 0 52 28 blend + hstemhm + 127 30 -18 18 199 30 -20 20 -20 30 -24 14 97 30 -11 11 72 31 202 30 87 29 -12 0 -27 44 1 101 -19 -1 -45 19 1 45 -46 -1 -106 37 0 85 -31 0 -71 31 0 71 -31 0 -71 40 0 91 -27 0 -62 18 0 42 -47 0 -108 51 0 117 -27 0 -62 27 0 62 -53 0 -122 43 0 99 -60 -1 -138 52 1 120 -32 0 -73 32 0 72 22 blend + vstemhm + hintmask 00011000000000000000000100000000 + 193 296 41 0 93 -8 0 -19 2 blend + rmoveto + 625 -94 -625 -84 -1 -192 27 0 63 84 1 192 3 blend + hlineto + -30 124 -48 0 -110 -6 0 -14 2 blend + rmoveto + -154 685 154 -15 0 -34 16 0 38 15 0 34 3 blend + vlineto + hintmask 00100000000000000000100000000000 + -365 -132 -33 0 -76 1 1 3 2 blend + rmoveto + -232 -7 -1 -16 1 blend + vlineto + 30 -5 51 0 117 -11 0 -27 2 blend + rlineto + 237 18 1 43 1 blend + vlineto + hintmask 01000000000010010000010000000000 + -11 -92 -27 0 -62 1 -1 2 2 blend + rmoveto + -30 397 30 -22 0 -52 -12 0 -27 22 0 52 3 blend + vlineto + -760 647 25 0 56 -4 0 -9 2 blend + rmoveto + -30 811 30 -28 0 -65 -12 0 -27 28 0 65 3 blend + vlineto + hintmask 00000000000010100000000000000000 + -823 -13 0 -29 1 blend + hmoveto + -143 12 0 27 1 blend + vlineto + -83 -13 -107 -75 -82 4 0 9 3 0 6 5 1 12 -1 0 -1 5 -1 11 5 blend + vhcurveto + 7 -4 11 -9 5 -6 10 0 21 -5 0 -12 20 0 46 -17 0 -38 6 0 15 -8 0 -18 6 blend + rrcurveto + 79 5 0 11 1 blend + 85 16 118 88 1 1 3 9 0 19 6 0 15 3 blend + vvcurveto + 143 -11 0 -25 1 blend + vlineto + hintmask 00000000010100001000000000000000 + 199 -25 -46 -1 -106 -23 0 -54 2 blend + rmoveto + -167 vlineto + hintmask 00000000010100000100000000000000 + 30 37 0 85 1 blend + 167 hlineto + hintmask 00000000101000000001000000000000 + -14 -59 -18 0 -42 8 0 18 2 blend + rmoveto + -30 185 30 -12 0 -26 -4 0 -9 12 0 26 3 blend + vlineto + -365 -96 10 0 22 7 0 17 2 blend + rmoveto + -30 392 30 -17 0 -39 -4 0 -9 17 0 39 3 blend + vlineto + hintmask 00000011000000000100000000000000 + -218 -10 -15 0 -33 -6 0 -13 2 blend + rmoveto + -160 23 0 51 1 blend + vlineto + -8 -2 0 0 -1 1 blend + -3 -11 -1 1 0 3 0 0 1 2 blend + vhcurveto + -11 -1 -30 2 0 4 1 0 1 4 0 10 3 blend + 0 -47 13 0 30 1 blend + 1 5 -9 6 -10 2 -9 4 0 8 -6 0 -13 6 0 13 -11 0 -25 2 0 6 -8 0 -19 6 blend + rrcurveto + hintmask 00000011000001000010001000000000 + 50 30 -5 0 -11 1 0 2 2 blend + 0 6 17 3 0 8 5 1 12 2 blend + hvcurveto + 17 5 4 9 21 6 -1 12 4 0 9 1 0 3 4 0 8 11 0 25 5 blend + vvcurveto + 159 -21 0 -46 1 blend + vlineto + -132 -50 -39 0 -88 1 0 1 2 blend + rmoveto + -25 -42 -40 -39 -44 -30 8 -4 13 -10 5 -4 41 6 0 12 3 0 8 7 0 16 3 0 5 5 0 13 1 0 4 6 0 13 -3 0 -8 10 0 22 -6 0 -14 5 0 12 -5 0 -10 -3 0 -8 13 blend + 30 45 -7 0 -14 1 blend + 47 26 45 -3 0 -8 1 0 1 2 blend + rrcurveto + 153 -7 -13 0 -30 -1 0 -2 2 blend + rmoveto + 35 -27 38 -39 18 -28 -8 3 -11 3 -5 -3 -9 3 -14 6 -7 -3 -5 1 -9 4 -4 0 6 blend + rrcurveto + 24 18 -18 27 -39 39 -34 25 23 1 55 6 -1 12 4 -1 8 -3 4 1 9 -3 13 -6 7 2 7 -3 9 -4 5 4 8 blend + rlinecurve + 115 330 -53 -1 -124 9 1 21 2 blend + rmoveto + hintmask 10000101000001000000001010000000 + 14 -286 131 -209 160 0 50 1 18 34 6 108 -9 3 -11 5 -9 7 -4 -92 -9 -34 -31 -1 -137 -2 -126 185 -12 281 3 0 8 6 0 14 5 0 10 -10 0 -22 -3 0 -6 0 0 -1 14 0 33 -1 0 -1 11 0 23 -3 0 -8 5 0 12 10 0 24 -10 0 -23 3 0 7 -14 0 -32 8 0 18 -8 0 -17 8 0 17 0 0 -1 11 0 26 0 0 1 4 0 9 5 0 11 1 0 1 29 0 67 0 1 2 8 0 17 0 -1 -1 -2 0 -4 -37 0 -85 30 blend + rrcurveto + 207 -169 -37 0 -85 -4 0 -9 2 blend + rmoveto + -61 -129 -111 -108 -121 -69 7 -5 12 -11 5 -6 119 74 113 110 66 136 4 15 19 8 14 33 4 28 29 8 5 22 2 28 27 6 2 17 8 1 20 -6 0 -14 14 1 34 -13 -1 -31 6 0 15 -7 0 -17 0 -28 -23 -4 -2 -10 0 -27 -20 -1 -3 -5 -1 -16 -12 -1 -15 -19 18 blend + rrcurveto + -156 153 -20 -2 -49 -2 0 -3 2 blend + rmoveto + 52 -15 63 -26 34 -1 0 -3 -1 0 -1 0 0 1 0 0 -2 0 0 -2 5 blend + -21 rrcurveto + 15 27 -34 20 -64 24 -51 14 21 0 48 20 0 47 -1 0 -1 1 0 1 0 0 -1 0 0 1 1 0 3 -1 0 -2 8 blend + rlinecurve + -453 -763 1 0 2 12 0 27 2 blend + rmoveto + -25 -16 -31 0 -71 -7 0 -17 2 blend + rlineto + -100 89 146 -18 233 -21 0 -46 -5 0 -12 -13 0 -29 -4 0 -9 -8 0 -18 5 blend + hhcurveto + 249 23 0 53 1 blend + hlineto + 2 8 6 14 6 8 -35 0 -207 2 -1 3 11 0 25 5 1 12 17 0 38 4 0 10 8 0 18 -16 0 -37 -1 0 -3 -1 0 -2 9 blend + 0 -22 -14 0 -32 1 blend + 0 -214 0 -150 15 -78 89 24 0 55 0 0 1 18 0 40 -2 0 -5 12 0 28 -1 0 -2 6 blend + rrcurveto + 5 62 -50 0 -114 -10 0 -22 2 blend + rmoveto + -30 -97 -92 -60 -107 -36 8 -6 12 -11 4 -6 105 41 99 65 32 106 5 0 12 7 0 15 15 0 34 1 0 3 7 0 16 1 0 2 10 0 22 -6 0 -15 18 0 41 -17 0 -37 8 0 18 -8 0 -19 -2 0 -5 4 0 9 -12 0 -27 7 0 16 -1 0 -2 6 0 14 18 blend + rrcurveto + + + 1 vsindex + -80 27 95 49 -48 48 -45 45 -30 30 -16 16 -13 13 49 30 48 30 47 19 -19 30 53 30 -18 18 51 11 -11 30 -22 22 62 30 60 30 15 81 -30 30 -30 102 -10 1 -14 41 -2 59 -53 2 -76 27 -1 38 -26 1 -37 26 -1 37 -27 1 -39 27 -1 39 -27 1 -39 27 -1 39 -13 0 -19 13 0 19 -14 0 -20 14 0 20 -19 1 -27 13 -1 19 -18 1 -26 13 0 19 -18 0 -26 18 0 26 -18 0 -26 23 -1 33 -21 1 -30 42 -2 60 -29 1 -42 29 -1 42 -19 1 -27 7 0 10 -7 0 -10 26 -1 37 -24 1 -34 24 -1 34 -27 1 -39 24 -1 34 -26 1 -37 26 -1 37 -40 1 -45 53 -2 66 -44 2 -62 44 -2 62 -44 2 -62 18 0 23 42 blend + hstemhm + 193 30 -1 30 -15 15 106 29 96 30 142 30 109 30 5 10 -28 1 -40 71 -2 102 -56 2 -80 75 -4 106 -21 2 -29 21 -2 29 -104 5 -148 55 -3 78 -42 3 -59 69 -4 98 -84 4 -120 79 -3 113 -94 3 -135 76 -3 109 -51 2 -73 25 -1 36 16 blend + vstemhm + hintmask 10000011101100101101000101110000 + 55 767 2 0 3 37 -2 55 2 blend + rmoveto + -30 892 30 -44 2 -62 -6 0 -9 44 -2 62 3 blend + vlineto + hintmask 00000000000000000000100000000000 + -637 72 -28 1 -40 -26 2 -39 2 blend + rmoveto + hintmask 00000000000000000010000000000000 + -153 30 -27 0 -27 77 -2 111 2 blend + vlineto + hintmask 00000000000000000000100000100000 + 153 27 0 27 1 blend + vlineto + 315 -89 3 -128 1 blend + hmoveto + hintmask 00000000000000000010000000100000 + -153 30 -27 0 -27 79 -3 113 2 blend + vlineto + hintmask 00000000000100101100110000110000 + 153 27 0 27 1 blend + vlineto + -462 -288 8 0 12 -11 0 -16 2 blend + rmoveto + 571 -62 -571 -102 3 -147 27 -1 39 102 -3 147 3 blend + hlineto + 152 -29 1 -42 1 blend + vmoveto + 571 -60 -571 -102 3 -147 26 -1 37 102 -3 147 3 blend + hlineto + -30 -71 2 -102 1 blend + 90 rmoveto + -212 631 212 -23 1 -32 45 -2 64 23 -1 32 3 blend + vlineto + -776 -263 -22 1 -31 -4 0 -5 2 blend + rmoveto + -30 905 30 -42 2 -60 10 0 14 42 -2 60 3 blend + vlineto + hintmask 00000001100000000000000100000000 + -716 -160 36 -1 52 -26 2 -37 2 blend + rmoveto + -30 554 30 -13 0 -19 -59 2 -85 13 0 19 3 blend + vlineto + -554 -78 59 -2 85 5 -1 7 2 blend + rmoveto + -30 563 30 -13 1 -19 -56 1 -81 13 -1 19 3 blend + vlineto + hintmask 00000010000000000000001000000000 + -578 -79 2 1 4 6 0 8 2 blend + rmoveto + hintmask 00001000000000000000001000001000 + -30 617 -27 1 -39 4 -1 5 2 blend + vlineto + hintmask 00000010000001000000000000001000 + 30 27 -1 39 1 blend + vlineto + -477 382 -24 2 -34 8 0 12 2 blend + rmoveto + -46 -92 -113 -104 -167 -65 7 -5 10 -9 5 -8 6 -1 8 -5 -1 -8 17 0 25 11 0 16 -3 -1 -5 6 0 9 12 0 18 -11 0 -16 18 0 26 -27 1 -39 6 -1 8 -16 1 -23 12 blend + rrcurveto + hintmask 00000100010010010000000001000000 + 172 70 111 106 55 101 14 0 20 3 0 5 -6 0 -8 1 0 1 3 0 4 28 -1 41 6 blend + rrcurveto + 298 -65 -24 0 -35 3 0 4 2 blend + rmoveto + -25 -12 -55 2 -79 -15 0 -22 2 blend + rlineto + 62 -80 121 -81 100 -38 5 8 9 11 7 6 -101 33 -119 76 -59 77 2 0 3 -14 1 -20 -10 1 -14 2 0 3 20 0 29 1 0 2 9 -1 13 18 -1 25 20 -1 28 26 -1 38 14 0 21 14 -1 19 -13 0 -19 -7 0 -10 9 0 13 -18 2 -25 4 -1 5 -7 0 -10 18 blend + rrcurveto + -211 -88 -39 3 -55 -12 1 -17 2 blend + rmoveto + -239 30 239 -2 -1 -4 69 -4 98 2 1 4 3 blend + vlineto + hintmask 10000010000000000000000000001000 + 316 -223 -74 3 -106 11 -1 15 2 blend + rmoveto + -6 -4 0 -6 1 blend + vlineto + -8 -87 -7 -34 -10 -10 2 0 3 24 -1 35 -1 1 -1 6 0 9 1 -1 1 1 0 1 6 blend + rrcurveto + -6 -1 0 -1 1 blend + -6 -6 -1 -12 2 0 3 1 blend + hhcurveto + -11 -31 1 0 1 10 0 15 2 blend + 1 3 -34 0 -1 -1 9 0 13 2 blend + hvcurveto + 5 -8 3 -13 6 -1 8 -11 1 -16 5 0 8 -19 1 -26 4 blend + 1 -8 28 -2 30 -1 14 1 -14 1 -20 7 -1 9 1 0 1 2 0 3 2 -1 2 3 1 5 0 1 1 7 blend + 21 0 10 4 10 9 16 15 7 35 2 -1 2 8 -1 11 2 0 3 5 0 7 5 0 8 3 -1 4 3 0 4 2 1 4 3 0 4 9 blend + 9 89 -15 1 -21 1 blend + rrcurveto + 7 1 1 12 6 -1 8 1 0 1 1 -1 1 9 0 13 4 blend + 0 hhcurveto + -660 -34 -57 3 -82 -8 0 -11 2 blend + rmoveto + -17 -46 1 0 2 7 0 10 2 blend + -32 -46 -46 5 0 7 5 0 7 2 blend + -23 20 -21 56 -2 81 -24 0 -35 2 blend + rcurveline + hintmask 10010000000000000000000000000000 + 52 28 31 51 17 46 -4 -1 -7 0 1 1 -4 1 -5 -7 0 -10 1 -1 1 0 0 -1 6 blend + rrcurveto + hintmask 00100000000000000000000010000000 + 110 -3 -67 3 -96 1 0 2 2 blend + rmoveto + 13 -38 10 -49 0 -32 -3 0 -4 4 -1 5 -2 0 -3 2 1 4 -1 1 -1 3 0 4 6 blend + rrcurveto + 29 6 55 -3 78 8 -1 11 2 blend + -1 31 -10 50 -15 37 -3 0 -4 0 1 1 -4 0 -6 3 -1 4 -4 1 -5 5 blend + rlinecurve + hintmask 01000000000000000000000000100000 + 113 -6 -56 3 -80 -7 0 -10 2 blend + rmoveto + 22 -32 20 -44 7 -30 2 0 3 1 -1 1 3 -1 3 1 0 1 2 0 3 5 blend + rrcurveto + 28 10 -8 29 -21 44 -23 32 48 -2 69 15 0 22 -2 1 -2 -1 0 -2 0 -1 -1 -5 1 -6 -1 1 -1 -4 0 -6 8 blend + rlinecurve + hintmask 00010000001000000000001000000000 + 117 -5 -45 1 -65 -17 1 -24 2 blend + rmoveto + 25 -23 -1 0 -1 2 -1 2 2 blend + 27 -32 13 -23 -2 1 -2 1 0 2 2 blend + rrcurveto + 21 14 -12 44 -2 63 20 -1 28 0 0 -1 3 blend + 22 -27 32 -26 22 -2 0 -2 -2 0 -3 1 0 1 -2 1 -2 4 blend + rlinecurve + -381 267 39 -1 56 7 -1 10 2 blend + rmoveto + -16 -30 -33 1 -47 -23 1 -33 2 blend + rlineto + 498 30 -42 1 -61 23 -1 33 2 blend + hlineto + -516 -23 21 0 31 -14 0 -21 2 blend + rmoveto + hintmask 00000010000000000000001000000000 + -224 6 0 9 1 blend + vlineto + hintmask 00000010001000000000000100000000 + 30 247 75 -4 106 10 0 14 2 blend + hlineto + + + 4 vsindex + -50 30 -19 19 114 30 44 30 23 30 -30 114 35 30 316 30 -10 10 37 12 -21 0 -26 66 0 82 -29 21 -10 29 -21 10 -64 0 -80 55 0 69 -79 0 -99 75 0 94 -46 0 -58 56 0 71 -56 0 -71 26 21 59 -18 -25 -54 54 0 68 -76 8 -85 58 0 73 -24 0 -31 24 0 31 -46 -4 -63 30 0 37 20 blend + hstemhm + 82 30 197 30 -26 8 317 30 168 13 -13 0 -16 77 0 96 -109 -1 -136 78 0 97 -77 0 -96 29 0 36 -10 0 -12 84 0 105 -86 0 -108 21 0 27 10 blend + vstemhm + hintmask 1010101101110110 + 529 746 23 0 29 30 4 43 2 blend + rmoveto + -30 320 30 -58 0 -73 -29 0 -36 58 0 73 3 blend + vlineto + -397 -495 15 0 18 12 -4 10 2 blend + rmoveto + -30 442 30 -56 0 -71 21 0 27 56 0 71 3 blend + vlineto + -420 149 -6 0 -8 6 -4 2 2 blend + rmoveto + -30 374 30 -54 0 -68 -25 0 -31 54 0 68 3 blend + vlineto + -514 -420 34 0 42 -3 4 1 2 blend + rmoveto + -30 626 30 -66 0 -82 -29 0 -36 66 0 82 3 blend + vlineto + -531 144 15 0 19 -9 0 -11 2 blend + rmoveto + -30 460 30 -55 0 -69 -4 0 -5 55 0 69 3 blend + vlineto + -53 622 -42 0 -53 -6 4 -2 2 blend + rmoveto + -7 -9 0 -12 1 blend + vlineto + -86 -171 -222 -118 -188 -45 7 -7 8 -11 3 -8 14 0 18 37 0 46 27 0 34 19 0 24 -7 0 -9 5 0 7 15 0 18 -16 0 -20 17 0 22 -32 0 -40 9 0 11 -19 0 -24 12 blend + rrcurveto + hintmask 0000000010000010 + 192 51 224 119 94 187 21 0 26 3 0 3 -17 0 -21 -9 0 -11 2 0 2 -3 0 -4 6 blend + rrcurveto + hintmask 0100010100000110 + -19 12 -6 -2 -55 0 -68 27 0 34 -12 0 -15 -3 0 -3 4 blend + rlineto + -323 -32 55 0 69 -25 0 -32 2 blend + rmoveto + -25 -11 -68 0 -86 -23 0 -28 2 blend + rlineto + 83 -154 177 -116 201 -44 4 8 9 12 7 6 -200 39 -177 113 -79 147 11 0 14 12 0 15 -18 0 -22 21 0 26 -1 0 -1 4 0 5 11 0 13 21 0 26 21 0 27 32 0 40 17 0 21 16 0 20 9 0 11 -10 0 -12 17 0 21 -36 0 -45 1 0 2 -37 0 -47 18 blend + rrcurveto + 59 127 -46 0 -58 9 -4 6 2 blend + rmoveto + -40 -82 -80 -104 -112 -75 8 -4 10 -9 6 -7 115 80 2 0 2 8 0 10 7 0 9 23 0 29 2 0 3 16 0 20 16 0 20 -12 0 -15 26 0 32 -30 0 -37 10 0 13 -18 0 -23 8 0 10 -4 0 -5 14 blend + 80 106 47 90 -13 0 -16 11 0 13 14 0 17 3 blend + rrcurveto + -129 -493 -106 -5 -137 21 6 34 2 blend + rmoveto + -27 -73 -43 -71 -51 -50 8 -5 13 -9 5 -5 49 52 47 77 29 77 6 0 8 11 0 14 7 0 8 8 0 10 5 0 7 8 0 10 16 0 20 -8 0 -10 28 0 35 -17 -1 -22 15 0 18 -11 0 -14 -3 0 -4 -4 0 -5 -2 0 -2 -1 0 -1 -3 0 -4 -3 1 -3 18 blend + rrcurveto + 124 -1 -66 4 -77 10 15 31 2 blend + rmoveto + -374 30 374 4 0 5 84 0 105 -4 0 -5 3 blend + vlineto + hintmask 0000000000101000 + -586 460 -72 0 -90 2 -21 -24 2 blend + rmoveto + -875 30 845 209 30 -27 0 -33 77 0 96 -53 0 -66 -79 0 -99 80 0 99 5 blend + vlineto + -8 -29 0 -36 1 blend + hmoveto + -7 -29 0 -36 1 blend + vlineto + -28 -75 -43 -102 -46 -95 14 0 17 10 0 13 11 0 14 -41 0 -51 17 0 22 4 0 5 6 blend + rrcurveto + hintmask 0001000000010000 + 89 -91 24 -74 -63 -32 0 -40 23 0 28 -11 0 -14 10 0 13 17 0 21 5 blend + vvcurveto + -33 -6 -35 -19 -13 3 0 4 1 0 1 15 0 18 7 0 9 4 0 5 5 blend + vhcurveto + -10 -6 -12 3 0 3 0 0 1 1 0 2 3 blend + -3 -14 -1 -20 -2 -26 1 -29 4 0 5 1 0 1 7 0 9 2 0 2 13 0 16 -1 0 -1 11 0 14 7 blend + 2 7 -9 4 -13 11 0 13 -21 0 -26 5 0 6 -33 0 -41 4 blend + 1 -8 22 -2 27 0 22 -21 0 -27 1 0 2 1 0 1 -3 0 -4 0 0 1 -4 0 -5 6 blend + 2 19 2 17 5 12 9 3 0 4 2 0 2 3 0 3 2 0 2 4 0 5 3 0 4 6 blend + rrcurveto + 25 17 10 7 0 9 5 0 7 4 0 5 3 blend + 43 44 22 0 27 1 blend + vvcurveto + 67 -22 76 -86 89 -8 0 -10 9 0 12 -6 0 -8 24 0 30 -11 0 -13 5 blend + vhcurveto + hintmask 0000000001001000 + 39 84 42 98 33 81 -10 0 -13 -4 0 -5 -8 0 -10 14 0 17 -6 0 -8 7 0 9 6 blend + rrcurveto + hintmask 0000000000001000 + -20 14 -6 -2 -60 0 -75 32 0 40 -12 0 -14 -2 0 -3 4 blend + rlineto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py index c7e6a65d9..64f194d7f 100644 --- a/Tests/varLib/varLib_test.py +++ b/Tests/varLib/varLib_test.py @@ -244,6 +244,23 @@ class BuildTest(unittest.TestCase): self.expect_ttx(varfont, expected_ttx_path, tables) self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix) + def test_varlib_build_sparse_CFF2(self): + ds_path = self.get_test_input('TestSparseCFF2VF.designspace') + suffix = '.otf' + expected_ttx_name = 'TestSparseCFF2VF' + tables = ["fvar", "CFF2"] + + finder = lambda s: s.replace('.ufo', suffix) + varfont, model, _ = build(ds_path, finder) + # some data (e.g. counts printed in TTX inline comments) is only + # calculated at compile time, so before we can compare the TTX + # dumps we need to save to a temporary stream, and realod the font + varfont = reload_font(varfont) + + expected_ttx_path = self.get_test_output(expected_ttx_name + '.ttx') + self.expect_ttx(varfont, expected_ttx_path, tables) + self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix) + def test_varlib_main_ttf(self): """Mostly for testing varLib.main() """