Merge remote-tracking branch 'origin/master' into partial-instancer
This commit is contained in:
commit
f74e82fec5
@ -5,6 +5,6 @@ from fontTools.misc.loggingTools import configLogger
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
version = __version__ = "3.40.1.dev0"
|
||||
version = __version__ = "3.41.1.dev0"
|
||||
|
||||
__all__ = ["version", "log", "configLogger"]
|
||||
|
@ -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)))
|
||||
|
||||
|
@ -7,6 +7,7 @@ from fontTools.feaLib.error import FeatureLibError
|
||||
from fontTools.feaLib.parser import Parser
|
||||
from fontTools.feaLib.ast import FeatureFile
|
||||
from fontTools.otlLib import builder as otl
|
||||
from fontTools.otlLib.maxContextCalc import maxCtxFont
|
||||
from fontTools.ttLib import newTable, getTableModule
|
||||
from fontTools.ttLib.tables import otBase, otTables
|
||||
from collections import defaultdict, OrderedDict
|
||||
@ -137,6 +138,9 @@ class Builder(object):
|
||||
fontTable.table = table
|
||||
elif tag in self.font:
|
||||
del self.font[tag]
|
||||
if (any(tag in self.font for tag in ("GPOS", "GSUB")) and
|
||||
"OS/2" in self.font):
|
||||
self.font["OS/2"].usMaxContext = maxCtxFont(self.font)
|
||||
if "GDEF" in tables:
|
||||
gdef = self.buildGDEF()
|
||||
if gdef:
|
||||
|
@ -21,6 +21,7 @@ that works:
|
||||
fb.setupHorizontalHeader()
|
||||
fb.setupNameTable(...)
|
||||
fb.setupOS2()
|
||||
fb.addOpenTypeFeatures(...)
|
||||
fb.setupPost()
|
||||
fb.save(...)
|
||||
|
||||
@ -299,7 +300,7 @@ _OS2Defaults = dict(
|
||||
sCapHeight = 0,
|
||||
usDefaultChar = 0, # .notdef
|
||||
usBreakChar = 32, # space
|
||||
usMaxContext = 2, # just kerning
|
||||
usMaxContext = 0,
|
||||
usLowerOpticalPointSize = 0,
|
||||
usUpperOpticalPointSize = 0,
|
||||
)
|
||||
|
@ -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:
|
||||
|
101
Lib/fontTools/otlLib/maxContextCalc.py
Normal file
101
Lib/fontTools/otlLib/maxContextCalc.py
Normal file
@ -0,0 +1,101 @@
|
||||
from __future__ import print_function, division, absolute_import, unicode_literals
|
||||
|
||||
__all__ = ['maxCtxFont']
|
||||
|
||||
|
||||
def maxCtxFont(font):
|
||||
"""Calculate the usMaxContext value for an entire font."""
|
||||
|
||||
maxCtx = 0
|
||||
for tag in ('GSUB', 'GPOS'):
|
||||
if tag not in font:
|
||||
continue
|
||||
table = font[tag].table
|
||||
if not table.LookupList:
|
||||
continue
|
||||
for lookup in table.LookupList.Lookup:
|
||||
for st in lookup.SubTable:
|
||||
maxCtx = maxCtxSubtable(maxCtx, tag, lookup.LookupType, st)
|
||||
return maxCtx
|
||||
|
||||
|
||||
def maxCtxSubtable(maxCtx, tag, lookupType, st):
|
||||
"""Calculate usMaxContext based on a single lookup table (and an existing
|
||||
max value).
|
||||
"""
|
||||
|
||||
# single positioning, single / multiple substitution
|
||||
if (tag == 'GPOS' and lookupType == 1) or (
|
||||
tag == 'GSUB' and lookupType in (1, 2, 3)):
|
||||
maxCtx = max(maxCtx, 1)
|
||||
|
||||
# pair positioning
|
||||
elif tag == 'GPOS' and lookupType == 2:
|
||||
maxCtx = max(maxCtx, 2)
|
||||
|
||||
# ligatures
|
||||
elif tag == 'GSUB' and lookupType == 4:
|
||||
for ligatures in st.ligatures.values():
|
||||
for ligature in ligatures:
|
||||
maxCtx = max(maxCtx, ligature.CompCount)
|
||||
|
||||
# context
|
||||
elif (tag == 'GPOS' and lookupType == 7) or (
|
||||
tag == 'GSUB' and lookupType == 5):
|
||||
maxCtx = maxCtxContextualSubtable(
|
||||
maxCtx, st, 'Pos' if tag == 'GPOS' else 'Sub')
|
||||
|
||||
# chained context
|
||||
elif (tag == 'GPOS' and lookupType == 8) or (
|
||||
tag == 'GSUB' and lookupType == 6):
|
||||
maxCtx = maxCtxContextualSubtable(
|
||||
maxCtx, st, 'Pos' if tag == 'GPOS' else 'Sub', 'Chain')
|
||||
|
||||
# extensions
|
||||
elif (tag == 'GPOS' and lookupType == 9) or (
|
||||
tag == 'GSUB' and lookupType == 7):
|
||||
maxCtx = maxCtxSubtable(
|
||||
maxCtx, tag, st.ExtensionLookupType, st.ExtSubTable)
|
||||
|
||||
# reverse-chained context
|
||||
elif tag == 'GSUB' and lookupType == 8:
|
||||
maxCtx = maxCtxContextualRule(maxCtx, st, 'Reverse')
|
||||
|
||||
return maxCtx
|
||||
|
||||
|
||||
def maxCtxContextualSubtable(maxCtx, st, ruleType, chain=''):
|
||||
"""Calculate usMaxContext based on a contextual feature subtable."""
|
||||
|
||||
if st.Format == 1:
|
||||
for ruleset in getattr(st, '%s%sRuleSet' % (chain, ruleType)):
|
||||
if ruleset is None:
|
||||
continue
|
||||
for rule in getattr(ruleset, '%s%sRule' % (chain, ruleType)):
|
||||
if rule is None:
|
||||
continue
|
||||
maxCtx = maxCtxContextualRule(maxCtx, rule, chain)
|
||||
|
||||
elif st.Format == 2:
|
||||
for ruleset in getattr(st, '%s%sClassSet' % (chain, ruleType)):
|
||||
if ruleset is None:
|
||||
continue
|
||||
for rule in getattr(ruleset, '%s%sClassRule' % (chain, ruleType)):
|
||||
if rule is None:
|
||||
continue
|
||||
maxCtx = maxCtxContextualRule(maxCtx, rule, chain)
|
||||
|
||||
elif st.Format == 3:
|
||||
maxCtx = maxCtxContextualRule(maxCtx, st, chain)
|
||||
|
||||
return maxCtx
|
||||
|
||||
|
||||
def maxCtxContextualRule(maxCtx, st, chain):
|
||||
"""Calculate usMaxContext based on a contextual feature rule."""
|
||||
|
||||
if not chain:
|
||||
return max(maxCtx, st.GlyphCount)
|
||||
elif chain == 'Reverse':
|
||||
return max(maxCtx, st.GlyphCount + st.LookAheadGlyphCount)
|
||||
return max(maxCtx, st.InputGlyphCount + st.LookAheadGlyphCount)
|
@ -7,6 +7,7 @@ from fontTools.misc.py23 import *
|
||||
from fontTools.misc.fixedTools import otRound
|
||||
from fontTools import ttLib
|
||||
from fontTools.ttLib.tables import otTables
|
||||
from fontTools.otlLib.maxContextCalc import maxCtxFont
|
||||
from fontTools.pens.basePen import NullPen
|
||||
from fontTools.misc.loggingTools import Timer
|
||||
from fontTools.subset.cff import *
|
||||
@ -322,6 +323,10 @@ Other font-specific options:
|
||||
Update the 'OS/2 xAvgCharWidth' field after subsetting.
|
||||
--no-recalc-average-width
|
||||
Don't change the 'OS/2 xAvgCharWidth' field. [default]
|
||||
--recalc-max-context
|
||||
Update the 'OS/2 usMaxContext' field after subsetting.
|
||||
--no-recalc-max-context
|
||||
Don't change the 'OS/2 usMaxContext' field. [default]
|
||||
--font-number=<number>
|
||||
Select font number for TrueType Collection (.ttc/.otc), starting from 0.
|
||||
|
||||
@ -1789,39 +1794,47 @@ def subset_glyphs(self, s):
|
||||
self.glyphCount = len(self.variations)
|
||||
return bool(self.variations)
|
||||
|
||||
def _remap_index_map(s, varidx_map, table_map):
|
||||
map_ = {k:varidx_map[v] for k,v in table_map.mapping.items()}
|
||||
# Emptied glyphs are remapped to:
|
||||
# if GID <= last retained GID, 0/0: delta set for 0/0 is expected to exist & zeros compress well
|
||||
# if GID > last retained GID, major/minor of the last retained glyph: will be optimized out by table compiler
|
||||
last_idx = varidx_map[table_map.mapping[s.last_retained_glyph]]
|
||||
for g,i in s.reverseEmptiedGlyphMap.items():
|
||||
map_[g] = last_idx if i > s.last_retained_order else 0
|
||||
return map_
|
||||
|
||||
@_add_method(ttLib.getTableClass('HVAR'))
|
||||
def subset_glyphs(self, s):
|
||||
table = self.table
|
||||
|
||||
# TODO Update for retain_gids
|
||||
|
||||
used = set()
|
||||
advIdxes_ = set()
|
||||
retainAdvMap = False
|
||||
|
||||
if table.AdvWidthMap:
|
||||
if not s.options.retain_gids:
|
||||
table.AdvWidthMap.mapping = _dict_subset(table.AdvWidthMap.mapping, s.glyphs)
|
||||
table.AdvWidthMap.mapping = _dict_subset(table.AdvWidthMap.mapping, s.glyphs)
|
||||
used.update(table.AdvWidthMap.mapping.values())
|
||||
else:
|
||||
assert table.LsbMap is None and table.RsbMap is None, "File a bug."
|
||||
used.update(s.reverseOrigGlyphMap.values())
|
||||
advIdxes_ = used.copy()
|
||||
retainAdvMap = s.options.retain_gids
|
||||
|
||||
if table.LsbMap:
|
||||
if not s.options.retain_gids:
|
||||
table.LsbMap.mapping = _dict_subset(table.LsbMap.mapping, s.glyphs)
|
||||
table.LsbMap.mapping = _dict_subset(table.LsbMap.mapping, s.glyphs)
|
||||
used.update(table.LsbMap.mapping.values())
|
||||
if table.RsbMap:
|
||||
if not s.options.retain_gids:
|
||||
table.RsbMap.mapping = _dict_subset(table.RsbMap.mapping, s.glyphs)
|
||||
table.RsbMap.mapping = _dict_subset(table.RsbMap.mapping, s.glyphs)
|
||||
used.update(table.RsbMap.mapping.values())
|
||||
|
||||
varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used)
|
||||
varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used, retainFirstMap=retainAdvMap, advIdxes=advIdxes_)
|
||||
|
||||
if table.AdvWidthMap:
|
||||
table.AdvWidthMap.mapping = {k:varidx_map[v] for k,v in table.AdvWidthMap.mapping.items()}
|
||||
table.AdvWidthMap.mapping = _remap_index_map(s, varidx_map, table.AdvWidthMap)
|
||||
if table.LsbMap:
|
||||
table.LsbMap.mapping = {k:varidx_map[v] for k,v in table.LsbMap.mapping.items()}
|
||||
table.LsbMap.mapping = _remap_index_map(s, varidx_map, table.LsbMap)
|
||||
if table.RsbMap:
|
||||
table.RsbMap.mapping = {k:varidx_map[v] for k,v in table.RsbMap.mapping.items()}
|
||||
table.RsbMap.mapping = _remap_index_map(s, varidx_map, table.RsbMap)
|
||||
|
||||
# TODO Return emptiness...
|
||||
return True
|
||||
@ -1831,37 +1844,37 @@ def subset_glyphs(self, s):
|
||||
table = self.table
|
||||
|
||||
used = set()
|
||||
advIdxes_ = set()
|
||||
retainAdvMap = False
|
||||
|
||||
if table.AdvHeightMap:
|
||||
if not s.options.retain_gids:
|
||||
table.AdvHeightMap.mapping = _dict_subset(table.AdvHeightMap.mapping, s.glyphs)
|
||||
table.AdvHeightMap.mapping = _dict_subset(table.AdvHeightMap.mapping, s.glyphs)
|
||||
used.update(table.AdvHeightMap.mapping.values())
|
||||
else:
|
||||
assert table.TsbMap is None and table.BsbMap is None and table.VOrgMap is None, "File a bug."
|
||||
used.update(s.reverseOrigGlyphMap.values())
|
||||
advIdxes_ = used.copy()
|
||||
retainAdvMap = s.options.retain_gids
|
||||
|
||||
if table.TsbMap:
|
||||
if not s.options.retain_gids:
|
||||
table.TsbMap.mapping = _dict_subset(table.TsbMap.mapping, s.glyphs)
|
||||
table.TsbMap.mapping = _dict_subset(table.TsbMap.mapping, s.glyphs)
|
||||
used.update(table.TsbMap.mapping.values())
|
||||
if table.BsbMap:
|
||||
if not s.options.retain_gids:
|
||||
table.BsbMap.mapping = _dict_subset(table.BsbMap.mapping, s.glyphs)
|
||||
table.BsbMap.mapping = _dict_subset(table.BsbMap.mapping, s.glyphs)
|
||||
used.update(table.BsbMap.mapping.values())
|
||||
if table.VOrgMap:
|
||||
if not s.options.retain_gids:
|
||||
table.VOrgMap.mapping = _dict_subset(table.VOrgMap.mapping, s.glyphs)
|
||||
table.VOrgMap.mapping = _dict_subset(table.VOrgMap.mapping, s.glyphs)
|
||||
used.update(table.VOrgMap.mapping.values())
|
||||
|
||||
varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used)
|
||||
varidx_map = varStore.VarStore_subset_varidxes(table.VarStore, used, retainFirstMap=retainAdvMap, advIdxes=advIdxes_)
|
||||
|
||||
if table.AdvHeightMap:
|
||||
table.AdvHeightMap.mapping = {k:varidx_map[v] for k,v in table.AdvHeightMap.mapping.items()}
|
||||
table.AdvHeightMap.mapping = _remap_index_map(s, varidx_map, table.AdvHeightMap)
|
||||
if table.TsbMap:
|
||||
table.TsbMap.mapping = {k:varidx_map[v] for k,v in table.TsbMap.mapping.items()}
|
||||
table.TsbMap.mapping = _remap_index_map(s, varidx_map, table.TsbMap)
|
||||
if table.BsbMap:
|
||||
table.BsbMap.mapping = {k:varidx_map[v] for k,v in table.BsbMap.mapping.items()}
|
||||
table.BsbMap.mapping = _remap_index_map(s, varidx_map, table.BsbMap)
|
||||
if table.VOrgMap:
|
||||
table.VOrgMap.mapping = {k:varidx_map[v] for k,v in table.VOrgMap.mapping.items()}
|
||||
table.VOrgMap.mapping = _remap_index_map(s, varidx_map, table.VOrgMap)
|
||||
|
||||
# TODO Return emptiness...
|
||||
return True
|
||||
@ -2305,6 +2318,7 @@ class Options(object):
|
||||
self.recalc_timestamp = False # Recalculate font modified timestamp
|
||||
self.prune_unicode_ranges = True # Clear unused 'ulUnicodeRange' bits
|
||||
self.recalc_average_width = False # update 'xAvgCharWidth'
|
||||
self.recalc_max_context = False # update 'usMaxContext'
|
||||
self.canonical_order = None # Order tables as recommended
|
||||
self.flavor = None # May be 'woff' or 'woff2'
|
||||
self.with_zopfli = False # use zopfli instead of zlib for WOFF 1.0
|
||||
@ -2565,6 +2579,9 @@ class Subsetter(object):
|
||||
|
||||
order = font.getReverseGlyphMap()
|
||||
self.reverseOrigGlyphMap = {g:order[g] for g in self.glyphs_retained}
|
||||
self.reverseEmptiedGlyphMap = {g:order[g] for g in self.glyphs_emptied}
|
||||
self.last_retained_order = max(self.reverseOrigGlyphMap.values())
|
||||
self.last_retained_glyph = font.getGlyphOrder()[self.last_retained_order]
|
||||
|
||||
log.info("Retaining %d glyphs", len(self.glyphs_retained))
|
||||
|
||||
@ -2614,6 +2631,11 @@ class Subsetter(object):
|
||||
if avg_width != font[tag].xAvgCharWidth:
|
||||
font[tag].xAvgCharWidth = avg_width
|
||||
log.info("%s xAvgCharWidth updated: %d", tag, avg_width)
|
||||
if self.options.recalc_max_context:
|
||||
max_context = maxCtxFont(font)
|
||||
if max_context != font[tag].usMaxContext:
|
||||
font[tag].usMaxContext = max_context
|
||||
log.info("%s usMaxContext updated: %d", tag, max_context)
|
||||
clazz = ttLib.getTableClass(tag)
|
||||
if hasattr(clazz, 'prune_post_subset'):
|
||||
with timer("prune '%s'" % tag):
|
||||
|
@ -406,7 +406,7 @@ class Silf(object):
|
||||
if version >= 3.0:
|
||||
pseudo = sstruct.unpack(Silf_pseudomap_format, data[8+6*i:14+6*i], _Object())
|
||||
else:
|
||||
pseudo = struct.unpack('>HH', data[8+4*i:12+4*i], _Object())
|
||||
pseudo = sstruct.unpack('>HH', data[8+4*i:12+4*i], _Object())
|
||||
self.pMap[pseudo.unicode] = ttFont.getGlyphName(pseudo.nPseudo)
|
||||
data = data[8 + 6 * numPseudo:]
|
||||
currpos = (sstruct.calcsize(Silf_part1_format)
|
||||
|
@ -669,12 +669,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)
|
||||
|
||||
|
||||
|
522
Lib/fontTools/varLib/cff.py
Normal file → Executable file
522
Lib/fontTools/varLib/cff.py
Normal file → Executable file
@ -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
|
||||
|
@ -64,24 +64,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
|
||||
|
@ -190,8 +190,10 @@ class VarStoreInstancer(object):
|
||||
#
|
||||
# Optimizations
|
||||
#
|
||||
# retainFirstMap - If true, major 0 mappings are retained. Deltas for unused indices are zeroed
|
||||
# advIdxes - Set of major 0 indices for advance deltas to be listed first. Other major 0 indices follow.
|
||||
|
||||
def VarStore_subset_varidxes(self, varIdxes, optimize=True):
|
||||
def VarStore_subset_varidxes(self, varIdxes, optimize=True, retainFirstMap=False, advIdxes=set()):
|
||||
|
||||
# Sort out used varIdxes by major/minor.
|
||||
used = {}
|
||||
@ -220,10 +222,19 @@ def VarStore_subset_varidxes(self, varIdxes, optimize=True):
|
||||
|
||||
items = data.Item
|
||||
newItems = []
|
||||
for minor in sorted(usedMinors):
|
||||
newMinor = len(newItems)
|
||||
newItems.append(items[minor])
|
||||
varDataMap[(major<<16)+minor] = (newMajor<<16)+newMinor
|
||||
if major == 0 and retainFirstMap:
|
||||
for minor in range(len(items)):
|
||||
newItems.append(items[minor] if minor in usedMinors else [0] * len(items[minor]))
|
||||
varDataMap[minor] = minor
|
||||
else:
|
||||
if major == 0:
|
||||
minors = sorted(advIdxes) + sorted(usedMinors - advIdxes)
|
||||
else:
|
||||
minors = sorted(usedMinors)
|
||||
for minor in minors:
|
||||
newMinor = len(newItems)
|
||||
newItems.append(items[minor])
|
||||
varDataMap[(major<<16)+minor] = (newMajor<<16)+newMinor
|
||||
|
||||
data.Item = newItems
|
||||
data.ItemCount = len(data.Item)
|
||||
|
18
NEWS.rst
18
NEWS.rst
@ -1,3 +1,21 @@
|
||||
3.41.0 (released 2019-04-29)
|
||||
----------------------------
|
||||
|
||||
- [varLib/cffLib] Added support for building ``CFF2`` variable font from sparse
|
||||
masters, or masters with more than one model (multiple ``VarStore.VarData``).
|
||||
In ``cffLib.specializer``, added support for ``CFF2`` CharStrings with
|
||||
``blend`` operators (#1547, #1591).
|
||||
- [subset] Fixed subsetting ``HVAR`` and ``VVAR`` with ``--retain-gids`` option,
|
||||
and when advances mapping is null while sidebearings mappings are non-null
|
||||
(#1587, #1588).
|
||||
- Added ``otlLib.maxContextCalc`` module to compute ``OS/2.usMaxContext`` value.
|
||||
Calculate it automatically when compiling features with feaLib. Added option
|
||||
``--recalc-max-context`` to ``subset`` module (#1582).
|
||||
- [otBase/otTables] Fixed ``AttributeError`` on missing OT table fields after
|
||||
importing font from TTX (#1584).
|
||||
- [Silf] Fixed typo ``Silf`` table's ``decompile`` method (#1586).
|
||||
- [otlLib] Better compress ``GPOS`` SinglePos (LookupType 1) subtables (#1539).
|
||||
|
||||
3.40.0 (released 2019-04-08)
|
||||
----------------------------
|
||||
|
||||
|
BIN
Tests/cffLib/data/TestSparseCFF2VF.otf
Normal file
BIN
Tests/cffLib/data/TestSparseCFF2VF.otf
Normal file
Binary file not shown.
@ -1,6 +1,11 @@
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from fontTools.cffLib.specializer import (programToString, stringToProgram,
|
||||
generalizeProgram, specializeProgram)
|
||||
generalizeProgram, specializeProgram,
|
||||
programToCommands, commandsToProgram,
|
||||
generalizeCommands,
|
||||
specializeCommands)
|
||||
from fontTools.ttLib import TTFont
|
||||
import os
|
||||
import unittest
|
||||
|
||||
# TODO
|
||||
@ -913,6 +918,42 @@ class CFFSpecializeProgramTest(unittest.TestCase):
|
||||
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
|
||||
|
||||
|
||||
class CFF2VFTestSpecialize(unittest.TestCase):
|
||||
|
||||
def __init__(self, methodName):
|
||||
unittest.TestCase.__init__(self, methodName)
|
||||
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
|
||||
# and fires deprecation warnings if a program uses the old name.
|
||||
if not hasattr(self, "assertRaisesRegex"):
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
@staticmethod
|
||||
def get_test_input(test_file_or_folder):
|
||||
path, _ = os.path.split(__file__)
|
||||
return os.path.join(path, "data", test_file_or_folder)
|
||||
|
||||
def test_blend_round_trip(self):
|
||||
otfvf_path = self.get_test_input('TestSparseCFF2VF.otf')
|
||||
ttf_font = TTFont(otfvf_path)
|
||||
fontGlyphList = ttf_font.getGlyphOrder()
|
||||
topDict = ttf_font['CFF2'].cff.topDictIndex[0]
|
||||
charstrings = topDict.CharStrings
|
||||
for glyphName in fontGlyphList:
|
||||
print(glyphName)
|
||||
cs = charstrings[glyphName]
|
||||
cs.decompile()
|
||||
cmds = programToCommands(cs.program, getNumRegions=cs.getNumRegions)
|
||||
cmds_g = generalizeCommands(cmds)
|
||||
cmds = specializeCommands(cmds_g, generalizeFirst=False)
|
||||
program = commandsToProgram(cmds)
|
||||
self.assertEqual(program, cs.program)
|
||||
program = specializeProgram(program, getNumRegions=cs.getNumRegions)
|
||||
self.assertEqual(program, cs.program)
|
||||
program_g = generalizeProgram(program, getNumRegions=cs.getNumRegions)
|
||||
program = commandsToProgram(cmds_g)
|
||||
self.assertEqual(program, program_g)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.exit(unittest.main())
|
||||
|
@ -233,6 +233,57 @@
|
||||
</GlobalSubrs>
|
||||
</CFF>
|
||||
|
||||
<GPOS>
|
||||
<Version value="0x00010000"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=1 -->
|
||||
<ScriptRecord index="0">
|
||||
<ScriptTag value="DFLT"/>
|
||||
<Script>
|
||||
<DefaultLangSys>
|
||||
<ReqFeatureIndex value="65535"/>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureIndex index="0" value="0"/>
|
||||
</DefaultLangSys>
|
||||
<!-- LangSysCount=0 -->
|
||||
</Script>
|
||||
</ScriptRecord>
|
||||
</ScriptList>
|
||||
<FeatureList>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureRecord index="0">
|
||||
<FeatureTag value="kern"/>
|
||||
<Feature>
|
||||
<!-- LookupCount=1 -->
|
||||
<LookupListIndex index="0" value="0"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=1 -->
|
||||
<Lookup index="0">
|
||||
<LookupType value="2"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<PairPos index="0" Format="1">
|
||||
<Coverage Format="1">
|
||||
<Glyph value="A"/>
|
||||
</Coverage>
|
||||
<ValueFormat1 value="4"/>
|
||||
<ValueFormat2 value="0"/>
|
||||
<!-- PairSetCount=1 -->
|
||||
<PairSet index="0">
|
||||
<!-- PairValueCount=1 -->
|
||||
<PairValueRecord index="0">
|
||||
<SecondGlyph value="a"/>
|
||||
<Value1 XAdvance="-50"/>
|
||||
</PairValueRecord>
|
||||
</PairSet>
|
||||
</PairPos>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GPOS>
|
||||
|
||||
<hmtx>
|
||||
<mtx name=".notdef" width="600" lsb="100"/>
|
||||
<mtx name=".null" width="600" lsb="100"/>
|
||||
|
@ -119,7 +119,7 @@
|
||||
<sCapHeight value="0"/>
|
||||
<usDefaultChar value="0"/>
|
||||
<usBreakChar value="32"/>
|
||||
<usMaxContext value="2"/>
|
||||
<usMaxContext value="1"/>
|
||||
</OS_2>
|
||||
|
||||
<hmtx>
|
||||
@ -257,6 +257,45 @@
|
||||
</extraNames>
|
||||
</post>
|
||||
|
||||
<GSUB>
|
||||
<Version value="0x00010000"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=1 -->
|
||||
<ScriptRecord index="0">
|
||||
<ScriptTag value="DFLT"/>
|
||||
<Script>
|
||||
<DefaultLangSys>
|
||||
<ReqFeatureIndex value="65535"/>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureIndex index="0" value="0"/>
|
||||
</DefaultLangSys>
|
||||
<!-- LangSysCount=0 -->
|
||||
</Script>
|
||||
</ScriptRecord>
|
||||
</ScriptList>
|
||||
<FeatureList>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureRecord index="0">
|
||||
<FeatureTag value="salt"/>
|
||||
<Feature>
|
||||
<!-- LookupCount=1 -->
|
||||
<LookupListIndex index="0" value="0"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=1 -->
|
||||
<Lookup index="0">
|
||||
<LookupType value="1"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<SingleSubst index="0" Format="1">
|
||||
<Substitution in="A" out="a"/>
|
||||
</SingleSubst>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GSUB>
|
||||
|
||||
<DSIG>
|
||||
<!-- note that the Digital Signature will be invalid after recompilation! -->
|
||||
<tableHeader flag="0x1" numSigs="1" version="1"/>
|
||||
|
@ -105,7 +105,7 @@
|
||||
<sCapHeight value="0"/>
|
||||
<usDefaultChar value="0"/>
|
||||
<usBreakChar value="32"/>
|
||||
<usMaxContext value="2"/>
|
||||
<usMaxContext value="0"/>
|
||||
</OS_2>
|
||||
|
||||
<hmtx>
|
||||
|
@ -119,7 +119,7 @@
|
||||
<sCapHeight value="0"/>
|
||||
<usDefaultChar value="0"/>
|
||||
<usBreakChar value="32"/>
|
||||
<usMaxContext value="2"/>
|
||||
<usMaxContext value="0"/>
|
||||
</OS_2>
|
||||
|
||||
<hmtx>
|
||||
|
@ -117,6 +117,7 @@ def test_build_ttf(tmpdir):
|
||||
fb.setupHorizontalHeader(ascent=824, descent=200)
|
||||
fb.setupNameTable(nameStrings)
|
||||
fb.setupOS2()
|
||||
fb.addOpenTypeFeatures("feature salt { sub A by a; } salt;")
|
||||
fb.setupPost()
|
||||
fb.setupDummyDSIG()
|
||||
|
||||
@ -145,6 +146,7 @@ def test_build_otf(tmpdir):
|
||||
fb.setupHorizontalHeader(ascent=824, descent=200)
|
||||
fb.setupNameTable(nameStrings)
|
||||
fb.setupOS2()
|
||||
fb.addOpenTypeFeatures("feature kern { pos A a -50; } kern;")
|
||||
fb.setupPost()
|
||||
fb.setupDummyDSIG()
|
||||
|
||||
|
99
Tests/otlLib/data/gpos_91.ttx
Normal file
99
Tests/otlLib/data/gpos_91.ttx
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
|
||||
|
||||
<GlyphOrder>
|
||||
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
|
||||
<GlyphID id="0" name=".notdef"/>
|
||||
<GlyphID id="1" name="A"/>
|
||||
</GlyphOrder>
|
||||
|
||||
<maxp>
|
||||
<!-- Most of this table will be recalculated by the compiler -->
|
||||
<tableVersion value="0x10000"/>
|
||||
<numGlyphs value="2"/>
|
||||
<maxPoints value="0"/>
|
||||
<maxContours value="0"/>
|
||||
<maxCompositePoints value="0"/>
|
||||
<maxCompositeContours value="0"/>
|
||||
<maxZones value="2"/>
|
||||
<maxTwilightPoints value="0"/>
|
||||
<maxStorage value="0"/>
|
||||
<maxFunctionDefs value="0"/>
|
||||
<maxInstructionDefs value="0"/>
|
||||
<maxStackElements value="0"/>
|
||||
<maxSizeOfInstructions value="0"/>
|
||||
<maxComponentElements value="0"/>
|
||||
<maxComponentDepth value="0"/>
|
||||
</maxp>
|
||||
|
||||
<post>
|
||||
<formatType value="2.0"/>
|
||||
<italicAngle value="0.0"/>
|
||||
<underlinePosition value="0"/>
|
||||
<underlineThickness value="0"/>
|
||||
<isFixedPitch value="0"/>
|
||||
<minMemType42 value="0"/>
|
||||
<maxMemType42 value="0"/>
|
||||
<minMemType1 value="0"/>
|
||||
<maxMemType1 value="0"/>
|
||||
<psNames>
|
||||
<!-- This file uses unique glyph names based on the information
|
||||
found in the 'post' table. Since these names might not be unique,
|
||||
we have to invent artificial names in case of clashes. In order to
|
||||
be able to retain the original information, we need a name to
|
||||
ps name mapping for those cases where they differ. That's what
|
||||
you see below.
|
||||
-->
|
||||
</psNames>
|
||||
<extraNames>
|
||||
<!-- following are the name that are not taken from the standard Mac glyph order -->
|
||||
</extraNames>
|
||||
</post>
|
||||
|
||||
<GPOS>
|
||||
<Version value="0x00010000"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=1 -->
|
||||
<ScriptRecord index="0">
|
||||
<ScriptTag value="latn"/>
|
||||
<Script>
|
||||
<DefaultLangSys>
|
||||
<ReqFeatureIndex value="65535"/>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureIndex index="0" value="0"/>
|
||||
</DefaultLangSys>
|
||||
<!-- LangSysCount=0 -->
|
||||
</Script>
|
||||
</ScriptRecord>
|
||||
</ScriptList>
|
||||
<FeatureList>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureRecord index="0">
|
||||
<FeatureTag value="test"/>
|
||||
<Feature>
|
||||
<!-- LookupCount=1 -->
|
||||
<LookupListIndex index="0" value="0"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=1 -->
|
||||
<Lookup index="0">
|
||||
<LookupType value="9"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<ExtensionPos index="0" Format="1">
|
||||
<ExtensionLookupType value="1"/>
|
||||
<SinglePos Format="1">
|
||||
<Coverage Format="1">
|
||||
<Glyph value="A"/>
|
||||
</Coverage>
|
||||
<ValueFormat value="4"/>
|
||||
<Value XAdvance="20"/>
|
||||
</SinglePos>
|
||||
</ExtensionPos>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GPOS>
|
||||
|
||||
</ttFont>
|
146
Tests/otlLib/data/gsub_51.ttx
Normal file
146
Tests/otlLib/data/gsub_51.ttx
Normal file
@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
|
||||
|
||||
<GlyphOrder>
|
||||
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
|
||||
<GlyphID id="0" name=".notdef"/>
|
||||
<GlyphID id="1" name="g20"/>
|
||||
<GlyphID id="2" name="g21"/>
|
||||
<GlyphID id="3" name="g22"/>
|
||||
<GlyphID id="4" name="g60"/>
|
||||
<GlyphID id="5" name="g61"/>
|
||||
<GlyphID id="6" name="g62"/>
|
||||
<GlyphID id="7" name="g63"/>
|
||||
</GlyphOrder>
|
||||
|
||||
<maxp>
|
||||
<!-- Most of this table will be recalculated by the compiler -->
|
||||
<tableVersion value="0x10000"/>
|
||||
<numGlyphs value="8"/>
|
||||
<maxPoints value="0"/>
|
||||
<maxContours value="0"/>
|
||||
<maxCompositePoints value="0"/>
|
||||
<maxCompositeContours value="0"/>
|
||||
<maxZones value="2"/>
|
||||
<maxTwilightPoints value="0"/>
|
||||
<maxStorage value="0"/>
|
||||
<maxFunctionDefs value="0"/>
|
||||
<maxInstructionDefs value="0"/>
|
||||
<maxStackElements value="0"/>
|
||||
<maxSizeOfInstructions value="0"/>
|
||||
<maxComponentElements value="0"/>
|
||||
<maxComponentDepth value="0"/>
|
||||
</maxp>
|
||||
|
||||
<post>
|
||||
<formatType value="2.0"/>
|
||||
<italicAngle value="0.0"/>
|
||||
<underlinePosition value="0"/>
|
||||
<underlineThickness value="0"/>
|
||||
<isFixedPitch value="0"/>
|
||||
<minMemType42 value="0"/>
|
||||
<maxMemType42 value="0"/>
|
||||
<minMemType1 value="0"/>
|
||||
<maxMemType1 value="0"/>
|
||||
<psNames>
|
||||
<!-- This file uses unique glyph names based on the information
|
||||
found in the 'post' table. Since these names might not be unique,
|
||||
we have to invent artificial names in case of clashes. In order to
|
||||
be able to retain the original information, we need a name to
|
||||
ps name mapping for those cases where they differ. That's what
|
||||
you see below.
|
||||
-->
|
||||
</psNames>
|
||||
<extraNames>
|
||||
<!-- following are the name that are not taken from the standard Mac glyph order -->
|
||||
</extraNames>
|
||||
</post>
|
||||
|
||||
<GSUB>
|
||||
<Version value="0x00010000"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=1 -->
|
||||
<ScriptRecord index="0">
|
||||
<ScriptTag value="latn"/>
|
||||
<Script>
|
||||
<DefaultLangSys>
|
||||
<ReqFeatureIndex value="65535"/>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureIndex index="0" value="0"/>
|
||||
</DefaultLangSys>
|
||||
<!-- LangSysCount=0 -->
|
||||
</Script>
|
||||
</ScriptRecord>
|
||||
</ScriptList>
|
||||
<FeatureList>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureRecord index="0">
|
||||
<FeatureTag value="test"/>
|
||||
<Feature>
|
||||
<!-- LookupCount=1 -->
|
||||
<LookupListIndex index="0" value="4"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=5 -->
|
||||
<Lookup index="0">
|
||||
<LookupType value="1"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<SingleSubst index="0" Format="1">
|
||||
<Substitution in="g20" out="g60"/>
|
||||
<Substitution in="g21" out="g61"/>
|
||||
</SingleSubst>
|
||||
</Lookup>
|
||||
<Lookup index="1">
|
||||
<LookupType value="4"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<LigatureSubst index="0" Format="1">
|
||||
<LigatureSet glyph="g21">
|
||||
<Ligature components="g22" glyph="g61"/>
|
||||
</LigatureSet>
|
||||
</LigatureSubst>
|
||||
</Lookup>
|
||||
<Lookup index="2">
|
||||
<LookupType value="4"/>
|
||||
<LookupFlag value="8"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<LigatureSubst index="0" Format="1">
|
||||
<LigatureSet glyph="g21">
|
||||
<Ligature components="g22" glyph="g61"/>
|
||||
</LigatureSet>
|
||||
</LigatureSubst>
|
||||
</Lookup>
|
||||
<Lookup index="3">
|
||||
<LookupType value="2"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<MultipleSubst index="0" Format="1">
|
||||
<Substitution in="g21" out="g61,g62,g63"/>
|
||||
</MultipleSubst>
|
||||
</Lookup>
|
||||
<Lookup index="4">
|
||||
<LookupType value="5"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<ContextSubst index="0" Format="1">
|
||||
<Coverage Format="1">
|
||||
<Glyph value="g20"/>
|
||||
</Coverage>
|
||||
<!-- SubRuleSetCount=1 -->
|
||||
<SubRuleSet index="0">
|
||||
<!-- SubRuleCount=1 -->
|
||||
<SubRule index="0">
|
||||
<!-- GlyphCount=2 -->
|
||||
<!-- SubstCount=0 -->
|
||||
<Input index="0" value="g20"/>
|
||||
</SubRule>
|
||||
</SubRuleSet>
|
||||
</ContextSubst>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GSUB>
|
||||
|
||||
</ttFont>
|
150
Tests/otlLib/data/gsub_52.ttx
Normal file
150
Tests/otlLib/data/gsub_52.ttx
Normal file
@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
|
||||
|
||||
<GlyphOrder>
|
||||
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
|
||||
<GlyphID id="0" name=".notdef"/>
|
||||
<GlyphID id="1" name="g20"/>
|
||||
<GlyphID id="2" name="g21"/>
|
||||
<GlyphID id="3" name="g22"/>
|
||||
<GlyphID id="4" name="g60"/>
|
||||
<GlyphID id="5" name="g61"/>
|
||||
<GlyphID id="6" name="g62"/>
|
||||
<GlyphID id="7" name="g63"/>
|
||||
</GlyphOrder>
|
||||
|
||||
<maxp>
|
||||
<!-- Most of this table will be recalculated by the compiler -->
|
||||
<tableVersion value="0x10000"/>
|
||||
<numGlyphs value="8"/>
|
||||
<maxPoints value="0"/>
|
||||
<maxContours value="0"/>
|
||||
<maxCompositePoints value="0"/>
|
||||
<maxCompositeContours value="0"/>
|
||||
<maxZones value="2"/>
|
||||
<maxTwilightPoints value="0"/>
|
||||
<maxStorage value="0"/>
|
||||
<maxFunctionDefs value="0"/>
|
||||
<maxInstructionDefs value="0"/>
|
||||
<maxStackElements value="0"/>
|
||||
<maxSizeOfInstructions value="0"/>
|
||||
<maxComponentElements value="0"/>
|
||||
<maxComponentDepth value="0"/>
|
||||
</maxp>
|
||||
|
||||
<post>
|
||||
<formatType value="2.0"/>
|
||||
<italicAngle value="0.0"/>
|
||||
<underlinePosition value="0"/>
|
||||
<underlineThickness value="0"/>
|
||||
<isFixedPitch value="0"/>
|
||||
<minMemType42 value="0"/>
|
||||
<maxMemType42 value="0"/>
|
||||
<minMemType1 value="0"/>
|
||||
<maxMemType1 value="0"/>
|
||||
<psNames>
|
||||
<!-- This file uses unique glyph names based on the information
|
||||
found in the 'post' table. Since these names might not be unique,
|
||||
we have to invent artificial names in case of clashes. In order to
|
||||
be able to retain the original information, we need a name to
|
||||
ps name mapping for those cases where they differ. That's what
|
||||
you see below.
|
||||
-->
|
||||
</psNames>
|
||||
<extraNames>
|
||||
<!-- following are the name that are not taken from the standard Mac glyph order -->
|
||||
</extraNames>
|
||||
</post>
|
||||
|
||||
<GSUB>
|
||||
<Version value="0x00010000"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=1 -->
|
||||
<ScriptRecord index="0">
|
||||
<ScriptTag value="latn"/>
|
||||
<Script>
|
||||
<DefaultLangSys>
|
||||
<ReqFeatureIndex value="65535"/>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureIndex index="0" value="0"/>
|
||||
</DefaultLangSys>
|
||||
<!-- LangSysCount=0 -->
|
||||
</Script>
|
||||
</ScriptRecord>
|
||||
</ScriptList>
|
||||
<FeatureList>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureRecord index="0">
|
||||
<FeatureTag value="test"/>
|
||||
<Feature>
|
||||
<!-- LookupCount=1 -->
|
||||
<LookupListIndex index="0" value="4"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=5 -->
|
||||
<Lookup index="0">
|
||||
<LookupType value="1"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<SingleSubst index="0" Format="1">
|
||||
<Substitution in="g20" out="g60"/>
|
||||
<Substitution in="g21" out="g61"/>
|
||||
</SingleSubst>
|
||||
</Lookup>
|
||||
<Lookup index="1">
|
||||
<LookupType value="4"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<LigatureSubst index="0" Format="1">
|
||||
<LigatureSet glyph="g21">
|
||||
<Ligature components="g22" glyph="g61"/>
|
||||
</LigatureSet>
|
||||
</LigatureSubst>
|
||||
</Lookup>
|
||||
<Lookup index="2">
|
||||
<LookupType value="4"/>
|
||||
<LookupFlag value="8"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<LigatureSubst index="0" Format="1">
|
||||
<LigatureSet glyph="g21">
|
||||
<Ligature components="g22" glyph="g61"/>
|
||||
</LigatureSet>
|
||||
</LigatureSubst>
|
||||
</Lookup>
|
||||
<Lookup index="3">
|
||||
<LookupType value="2"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<MultipleSubst index="0" Format="1">
|
||||
<Substitution in="g21" out="g61,g62,g63"/>
|
||||
</MultipleSubst>
|
||||
</Lookup>
|
||||
<Lookup index="4">
|
||||
<LookupType value="5"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<ContextSubst index="0" Format="2">
|
||||
<Coverage Format="1">
|
||||
<Glyph value="g20"/>
|
||||
</Coverage>
|
||||
<ClassDef Format="2">
|
||||
<ClassDef glyph="g20" class="1"/>
|
||||
</ClassDef>
|
||||
<!-- SubClassSetCount=2 -->
|
||||
<SubClassSet index="0" empty="1"/>
|
||||
<SubClassSet index="1">
|
||||
<!-- SubClassRuleCount=1 -->
|
||||
<SubClassRule index="0">
|
||||
<!-- GlyphCount=2 -->
|
||||
<!-- SubstCount=0 -->
|
||||
<Class index="0" value="1"/>
|
||||
</SubClassRule>
|
||||
</SubClassSet>
|
||||
</ContextSubst>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GSUB>
|
||||
|
||||
</ttFont>
|
99
Tests/otlLib/data/gsub_71.ttx
Normal file
99
Tests/otlLib/data/gsub_71.ttx
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
|
||||
|
||||
<GlyphOrder>
|
||||
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
|
||||
<GlyphID id="0" name=".notdef"/>
|
||||
<GlyphID id="1" name="g18"/>
|
||||
<GlyphID id="2" name="g19"/>
|
||||
<GlyphID id="3" name="g23"/>
|
||||
<GlyphID id="4" name="g24"/>
|
||||
</GlyphOrder>
|
||||
|
||||
<maxp>
|
||||
<!-- Most of this table will be recalculated by the compiler -->
|
||||
<tableVersion value="0x10000"/>
|
||||
<numGlyphs value="5"/>
|
||||
<maxPoints value="0"/>
|
||||
<maxContours value="0"/>
|
||||
<maxCompositePoints value="0"/>
|
||||
<maxCompositeContours value="0"/>
|
||||
<maxZones value="2"/>
|
||||
<maxTwilightPoints value="0"/>
|
||||
<maxStorage value="0"/>
|
||||
<maxFunctionDefs value="0"/>
|
||||
<maxInstructionDefs value="0"/>
|
||||
<maxStackElements value="0"/>
|
||||
<maxSizeOfInstructions value="0"/>
|
||||
<maxComponentElements value="0"/>
|
||||
<maxComponentDepth value="0"/>
|
||||
</maxp>
|
||||
|
||||
<post>
|
||||
<formatType value="2.0"/>
|
||||
<italicAngle value="0.0"/>
|
||||
<underlinePosition value="0"/>
|
||||
<underlineThickness value="0"/>
|
||||
<isFixedPitch value="0"/>
|
||||
<minMemType42 value="0"/>
|
||||
<maxMemType42 value="0"/>
|
||||
<minMemType1 value="0"/>
|
||||
<maxMemType1 value="0"/>
|
||||
<psNames>
|
||||
<!-- This file uses unique glyph names based on the information
|
||||
found in the 'post' table. Since these names might not be unique,
|
||||
we have to invent artificial names in case of clashes. In order to
|
||||
be able to retain the original information, we need a name to
|
||||
ps name mapping for those cases where they differ. That's what
|
||||
you see below.
|
||||
-->
|
||||
</psNames>
|
||||
<extraNames>
|
||||
<!-- following are the name that are not taken from the standard Mac glyph order -->
|
||||
</extraNames>
|
||||
</post>
|
||||
|
||||
<GSUB>
|
||||
<Version value="0x00010000"/>
|
||||
<ScriptList>
|
||||
<!-- ScriptCount=1 -->
|
||||
<ScriptRecord index="0">
|
||||
<ScriptTag value="latn"/>
|
||||
<Script>
|
||||
<DefaultLangSys>
|
||||
<ReqFeatureIndex value="65535"/>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureIndex index="0" value="0"/>
|
||||
</DefaultLangSys>
|
||||
<!-- LangSysCount=0 -->
|
||||
</Script>
|
||||
</ScriptRecord>
|
||||
</ScriptList>
|
||||
<FeatureList>
|
||||
<!-- FeatureCount=1 -->
|
||||
<FeatureRecord index="0">
|
||||
<FeatureTag value="test"/>
|
||||
<Feature>
|
||||
<!-- LookupCount=1 -->
|
||||
<LookupListIndex index="0" value="0"/>
|
||||
</Feature>
|
||||
</FeatureRecord>
|
||||
</FeatureList>
|
||||
<LookupList>
|
||||
<!-- LookupCount=1 -->
|
||||
<Lookup index="0">
|
||||
<LookupType value="7"/>
|
||||
<LookupFlag value="0"/>
|
||||
<!-- SubTableCount=1 -->
|
||||
<ExtensionSubst index="0" Format="1">
|
||||
<ExtensionLookupType value="1"/>
|
||||
<SingleSubst Format="1">
|
||||
<Substitution in="g18" out="g23"/>
|
||||
<Substitution in="g19" out="g24"/>
|
||||
</SingleSubst>
|
||||
</ExtensionSubst>
|
||||
</Lookup>
|
||||
</LookupList>
|
||||
</GSUB>
|
||||
|
||||
</ttFont>
|
76
Tests/otlLib/maxContextCalc_test.py
Normal file
76
Tests/otlLib/maxContextCalc_test.py
Normal file
@ -0,0 +1,76 @@
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import pytest
|
||||
from fontTools.ttLib import TTFont
|
||||
from fontTools.otlLib.maxContextCalc import maxCtxFont
|
||||
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
|
||||
|
||||
|
||||
def test_max_ctx_calc_no_features():
|
||||
font = TTFont()
|
||||
assert maxCtxFont(font) == 0
|
||||
font.setGlyphOrder(['.notdef'])
|
||||
addOpenTypeFeaturesFromString(font, '')
|
||||
assert maxCtxFont(font) == 0
|
||||
|
||||
|
||||
def test_max_ctx_calc_features():
|
||||
glyphs = '.notdef space A B C a b c'.split()
|
||||
features = """
|
||||
lookup GSUB_EXT useExtension {
|
||||
sub a by b;
|
||||
} GSUB_EXT;
|
||||
|
||||
lookup GPOS_EXT useExtension {
|
||||
pos a b -10;
|
||||
} GPOS_EXT;
|
||||
|
||||
feature sub1 {
|
||||
sub A by a;
|
||||
sub A B by b;
|
||||
sub A B C by c;
|
||||
sub [A B] C by c;
|
||||
sub [A B] C [A B] by c;
|
||||
sub A by A B;
|
||||
sub A' C by A B;
|
||||
sub a' by b;
|
||||
sub a' b by c;
|
||||
sub a from [A B C];
|
||||
rsub a by b;
|
||||
rsub a' by b;
|
||||
rsub a b' by c;
|
||||
rsub a b' c by A;
|
||||
rsub [a b] c' by A;
|
||||
rsub [a b] c' [a b] by B;
|
||||
lookup GSUB_EXT;
|
||||
} sub1;
|
||||
|
||||
feature pos1 {
|
||||
pos A 20;
|
||||
pos A B -50;
|
||||
pos A B' 10 C;
|
||||
lookup GPOS_EXT;
|
||||
} pos1;
|
||||
"""
|
||||
font = TTFont()
|
||||
font.setGlyphOrder(glyphs)
|
||||
addOpenTypeFeaturesFromString(font, features)
|
||||
|
||||
assert maxCtxFont(font) == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize('file_name, max_context', [
|
||||
('gsub_51', 2),
|
||||
('gsub_52', 2),
|
||||
('gsub_71', 1),
|
||||
('gpos_91', 1),
|
||||
])
|
||||
def test_max_ctx_calc_features_ttx(file_name, max_context):
|
||||
ttx_path = os.path.join(os.path.dirname(__file__),
|
||||
'data', '{}.ttx'.format(file_name))
|
||||
font = TTFont()
|
||||
font.importXML(ttx_path)
|
||||
|
||||
assert maxCtxFont(font) == max_context
|
443
Tests/subset/data/TestHVVAR.ttx
Normal file
443
Tests/subset/data/TestHVVAR.ttx
Normal file
@ -0,0 +1,443 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
|
||||
|
||||
<GlyphOrder>
|
||||
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
|
||||
<GlyphID id="0" name=".notdef"/>
|
||||
<GlyphID id="1" name="A"/>
|
||||
<GlyphID id="2" name="B"/>
|
||||
<GlyphID id="3" name="C"/>
|
||||
<GlyphID id="4" name="D"/>
|
||||
<GlyphID id="5" name="E"/>
|
||||
</GlyphOrder>
|
||||
|
||||
<head>
|
||||
<!-- Most of this table will be recalculated by the compiler -->
|
||||
<tableVersion value="1.0"/>
|
||||
<fontRevision value="1.0"/>
|
||||
<checkSumAdjustment value="0xddab44f"/>
|
||||
<magicNumber value="0x5f0f3cf5"/>
|
||||
<flags value="00000000 00000001"/>
|
||||
<unitsPerEm value="1000"/>
|
||||
<created value="Tue Apr 23 17:22:52 2019"/>
|
||||
<modified value="Tue Apr 23 17:22:52 2019"/>
|
||||
<xMin value="0"/>
|
||||
<yMin value="0"/>
|
||||
<xMax value="0"/>
|
||||
<yMax value="0"/>
|
||||
<macStyle value="00000000 00000000"/>
|
||||
<lowestRecPPEM value="7"/>
|
||||
<fontDirectionHint value="2"/>
|
||||
<indexToLocFormat value="0"/>
|
||||
<glyphDataFormat value="0"/>
|
||||
</head>
|
||||
|
||||
<hhea>
|
||||
<tableVersion value="0x00010000"/>
|
||||
<ascent value="1000"/>
|
||||
<descent value="-200"/>
|
||||
<lineGap value="0"/>
|
||||
<advanceWidthMax value="700"/>
|
||||
<minLeftSideBearing value="40"/>
|
||||
<minRightSideBearing value="580"/>
|
||||
<xMaxExtent value="80"/>
|
||||
<caretSlopeRise value="1"/>
|
||||
<caretSlopeRun value="0"/>
|
||||
<caretOffset value="0"/>
|
||||
<reserved0 value="0"/>
|
||||
<reserved1 value="0"/>
|
||||
<reserved2 value="0"/>
|
||||
<reserved3 value="0"/>
|
||||
<metricDataFormat value="0"/>
|
||||
<numberOfHMetrics value="4"/>
|
||||
</hhea>
|
||||
|
||||
<maxp>
|
||||
<!-- Most of this table will be recalculated by the compiler -->
|
||||
<tableVersion value="0x10000"/>
|
||||
<numGlyphs value="6"/>
|
||||
<maxPoints value="1"/>
|
||||
<maxContours value="1"/>
|
||||
<maxCompositePoints value="0"/>
|
||||
<maxCompositeContours value="0"/>
|
||||
<maxZones value="1"/>
|
||||
<maxTwilightPoints value="0"/>
|
||||
<maxStorage value="0"/>
|
||||
<maxFunctionDefs value="1"/>
|
||||
<maxInstructionDefs value="0"/>
|
||||
<maxStackElements value="0"/>
|
||||
<maxSizeOfInstructions value="0"/>
|
||||
<maxComponentElements value="0"/>
|
||||
<maxComponentDepth value="0"/>
|
||||
</maxp>
|
||||
|
||||
<OS_2>
|
||||
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
|
||||
will be recalculated by the compiler -->
|
||||
<version value="3"/>
|
||||
<xAvgCharWidth value="606"/>
|
||||
<usWeightClass value="400"/>
|
||||
<usWidthClass value="5"/>
|
||||
<fsType value="00000000 00001000"/>
|
||||
<ySubscriptXSize value="650"/>
|
||||
<ySubscriptYSize value="600"/>
|
||||
<ySubscriptXOffset value="0"/>
|
||||
<ySubscriptYOffset value="75"/>
|
||||
<ySuperscriptXSize value="650"/>
|
||||
<ySuperscriptYSize value="600"/>
|
||||
<ySuperscriptXOffset value="0"/>
|
||||
<ySuperscriptYOffset value="350"/>
|
||||
<yStrikeoutSize value="50"/>
|
||||
<yStrikeoutPosition value="300"/>
|
||||
<sFamilyClass value="0"/>
|
||||
<panose>
|
||||
<bFamilyType value="0"/>
|
||||
<bSerifStyle value="0"/>
|
||||
<bWeight value="5"/>
|
||||
<bProportion value="0"/>
|
||||
<bContrast value="0"/>
|
||||
<bStrokeVariation value="0"/>
|
||||
<bArmStyle value="0"/>
|
||||
<bLetterForm value="0"/>
|
||||
<bMidline value="0"/>
|
||||
<bXHeight value="0"/>
|
||||
</panose>
|
||||
<ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
|
||||
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
|
||||
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
|
||||
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
|
||||
<achVendID value="UKWN"/>
|
||||
<fsSelection value="00000000 01000000"/>
|
||||
<usFirstCharIndex value="32"/>
|
||||
<usLastCharIndex value="8722"/>
|
||||
<sTypoAscender value="800"/>
|
||||
<sTypoDescender value="-200"/>
|
||||
<sTypoLineGap value="200"/>
|
||||
<usWinAscent value="1000"/>
|
||||
<usWinDescent value="200"/>
|
||||
<ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
|
||||
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
|
||||
<sxHeight value="500"/>
|
||||
<sCapHeight value="700"/>
|
||||
<usDefaultChar value="0"/>
|
||||
<usBreakChar value="32"/>
|
||||
<usMaxContext value="0"/>
|
||||
</OS_2>
|
||||
|
||||
<hmtx>
|
||||
<mtx name=".notdef" width="700" lsb="80"/>
|
||||
<mtx name="A" width="700" lsb="0"/>
|
||||
<mtx name="B" width="625" lsb="40"/>
|
||||
<mtx name="C" width="660" lsb="80"/>
|
||||
<mtx name="D" width="660" lsb="80"/>
|
||||
<mtx name="E" width="660" lsb="80"/>
|
||||
</hmtx>
|
||||
|
||||
<cmap>
|
||||
<tableVersion version="0"/>
|
||||
<cmap_format_4 platformID="0" platEncID="3" language="0">
|
||||
<map code="0x41" name="A"/><!-- SPACE -->
|
||||
<map code="0x42" name="B"/><!-- DIGIT ZERO -->
|
||||
<map code="0x43" name="C"/><!-- EQUALS SIGN -->
|
||||
<map code="0x44" name="D"/><!-- MINUS SIGN -->
|
||||
<map code="0x45" name="E"/><!-- PLUS SIGN -->
|
||||
</cmap_format_4>
|
||||
</cmap>
|
||||
|
||||
<loca>
|
||||
<!-- The 'loca' table will be calculated by the compiler -->
|
||||
</loca>
|
||||
|
||||
<glyf>
|
||||
|
||||
<!-- The xMin, yMin, xMax and yMax values
|
||||
will be recalculated by the compiler. -->
|
||||
|
||||
<TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0">
|
||||
<contour>
|
||||
<pt x="0" y="0" on="1"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="A"/><!-- contains no outline data -->
|
||||
|
||||
<TTGlyph name="B" xMin="0" yMin="0" xMax="0" yMax="0">
|
||||
<contour>
|
||||
<pt x="0" y="0" on="1"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="C" xMin="0" yMin="0" xMax="0" yMax="0">
|
||||
<contour>
|
||||
<pt x="0" y="0" on="1"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="D" xMin="0" yMin="0" xMax="0" yMax="0">
|
||||
<contour>
|
||||
<pt x="0" y="0" on="1"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
<TTGlyph name="E" xMin="0" yMin="0" xMax="0" yMax="0">
|
||||
<contour>
|
||||
<pt x="0" y="0" on="1"/>
|
||||
</contour>
|
||||
<instructions/>
|
||||
</TTGlyph>
|
||||
|
||||
</glyf>
|
||||
|
||||
<name>
|
||||
<namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
|
||||
TestHVVAR
|
||||
</namerecord>
|
||||
<namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
|
||||
Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="3" platformID="1" platEncID="0" langID="0x0" unicode="True">
|
||||
1.000;UKWN;TestHVVAR-Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
|
||||
TestHVVAR
|
||||
</namerecord>
|
||||
<namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
|
||||
Version 1.000
|
||||
</namerecord>
|
||||
<namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
|
||||
TestHVVAR-Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
|
||||
TestHVVAR
|
||||
</namerecord>
|
||||
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
|
||||
Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
|
||||
1.000;UKWN;TestHVVAR-Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
|
||||
TestHVVAR-Regular
|
||||
</namerecord>
|
||||
<namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
|
||||
Version 1.000
|
||||
</namerecord>
|
||||
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
|
||||
TestHVVAR-Regular
|
||||
</namerecord>
|
||||
</name>
|
||||
|
||||
<post>
|
||||
<formatType value="2.0"/>
|
||||
<italicAngle value="0.0"/>
|
||||
<underlinePosition value="-75"/>
|
||||
<underlineThickness value="50"/>
|
||||
<isFixedPitch value="0"/>
|
||||
<minMemType42 value="0"/>
|
||||
<maxMemType42 value="0"/>
|
||||
<minMemType1 value="0"/>
|
||||
<maxMemType1 value="0"/>
|
||||
<psNames>
|
||||
<!-- This file uses unique glyph names based on the information
|
||||
found in the 'post' table. Since these names might not be unique,
|
||||
we have to invent artificial names in case of clashes. In order to
|
||||
be able to retain the original information, we need a name to
|
||||
ps name mapping for those cases where they differ. That's what
|
||||
you see below.
|
||||
-->
|
||||
</psNames>
|
||||
<extraNames>
|
||||
<!-- following are the name that are not taken from the standard Mac glyph order -->
|
||||
</extraNames>
|
||||
</post>
|
||||
|
||||
<HVAR>
|
||||
<Version value="0x00010000"/>
|
||||
<VarStore Format="1">
|
||||
<Format value="1"/>
|
||||
<VarRegionList>
|
||||
<!-- RegionAxisCount=1 -->
|
||||
<!-- RegionCount=2 -->
|
||||
<Region index="0">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="-1.0"/>
|
||||
<PeakCoord value="-1.0"/>
|
||||
<EndCoord value="0.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
<Region index="1">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="1.0"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
</VarRegionList>
|
||||
<!-- VarDataCount=2 -->
|
||||
<VarData index="0">
|
||||
<!-- ItemCount=6 -->
|
||||
<NumShorts value="0"/>
|
||||
<!-- VarRegionCount=1 -->
|
||||
<VarRegionIndex index="0" value="1"/>
|
||||
<Item index="0" value="[1]"/>
|
||||
<Item index="1" value="[2]"/>
|
||||
<Item index="2" value="[3]"/>
|
||||
<Item index="3" value="[4]"/>
|
||||
<Item index="4" value="[5]"/>
|
||||
<Item index="5" value="[6]"/>
|
||||
</VarData>
|
||||
<VarData index="1">
|
||||
<!-- ItemCount=6 -->
|
||||
<NumShorts value="1"/>
|
||||
<!-- VarRegionCount=2 -->
|
||||
<VarRegionIndex index="0" value="0"/>
|
||||
<VarRegionIndex index="1" value="1"/>
|
||||
<Item index="0" value="[10, 21]"/>
|
||||
<Item index="1" value="[11, 22]"/>
|
||||
<Item index="2" value="[126, 23]"/>
|
||||
<Item index="3" value="[127, 24]"/>
|
||||
<Item index="4" value="[-128, 25]"/>
|
||||
<Item index="5" value="[-129, 26]"/>
|
||||
</VarData>
|
||||
</VarStore>
|
||||
<LsbMap>
|
||||
<Map glyph=".notdef" outer="0" inner="2"/>
|
||||
<Map glyph="A" outer="0" inner="5"/>
|
||||
<Map glyph="B" outer="1" inner="1"/>
|
||||
<Map glyph="C" outer="1" inner="4"/>
|
||||
<Map glyph="D" outer="0" inner="3"/>
|
||||
<Map glyph="E" outer="1" inner="0"/>
|
||||
</LsbMap>
|
||||
<RsbMap>
|
||||
<Map glyph=".notdef" outer="0" inner="1"/>
|
||||
<Map glyph="A" outer="0" inner="4"/>
|
||||
<Map glyph="B" outer="1" inner="5"/>
|
||||
<Map glyph="C" outer="0" inner="0"/>
|
||||
<Map glyph="D" outer="1" inner="2"/>
|
||||
<Map glyph="E" outer="1" inner="3"/>
|
||||
</RsbMap>
|
||||
</HVAR>
|
||||
|
||||
<VVAR>
|
||||
<Version value="0x00010000"/>
|
||||
<VarStore Format="1">
|
||||
<Format value="1"/>
|
||||
<VarRegionList>
|
||||
<!-- RegionAxisCount=1 -->
|
||||
<!-- RegionCount=4 -->
|
||||
<Region index="0">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="-1.0"/>
|
||||
<PeakCoord value="-1.0"/>
|
||||
<EndCoord value="0.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
<Region index="1">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="1.0"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
<Region index="2">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="-1.0"/>
|
||||
<PeakCoord value="-0.5"/>
|
||||
<EndCoord value="0.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
<Region index="3">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.5"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
</VarRegionList>
|
||||
<!-- VarDataCount=2 -->
|
||||
<VarData index="0">
|
||||
<!-- ItemCount=4 -->
|
||||
<NumShorts value="0"/>
|
||||
<!-- VarRegionCount=1 -->
|
||||
<VarRegionIndex index="0" value="1"/>
|
||||
<Item index="0" value="[32]"/>
|
||||
<Item index="1" value="[17]"/>
|
||||
<Item index="2" value="[0]"/>
|
||||
<Item index="3" value="[14]"/>
|
||||
<Item index="4" value="[5]"/>
|
||||
<Item index="5" value="[6]"/>
|
||||
</VarData>
|
||||
<VarData index="1">
|
||||
<!-- ItemCount=6 -->
|
||||
<NumShorts value="3"/>
|
||||
<!-- VarRegionCount=4 -->
|
||||
<VarRegionIndex index="0" value="0"/>
|
||||
<VarRegionIndex index="1" value="1"/>
|
||||
<VarRegionIndex index="2" value="2"/>
|
||||
<VarRegionIndex index="3" value="3"/>
|
||||
<Item index="0" value="[0, 1, 11, 21]"/>
|
||||
<Item index="1" value="[0, 2, 12, 22]"/>
|
||||
<Item index="2" value="[128, 130, 13, 23]"/>
|
||||
<Item index="3" value="[0, 4, 14, 24]"/>
|
||||
<Item index="4" value="[0, 5, -129, 25]"/>
|
||||
<Item index="5" value="[14, 6, 16, 26]"/>
|
||||
</VarData>
|
||||
</VarStore>
|
||||
<AdvHeightMap>
|
||||
<Map glyph=".notdef" outer="1" inner="0"/>
|
||||
<Map glyph="A" outer="1" inner="2"/>
|
||||
<Map glyph="B" outer="0" inner="2"/>
|
||||
<Map glyph="C" outer="0" inner="0"/>
|
||||
<Map glyph="D" outer="1" inner="1"/>
|
||||
<Map glyph="E" outer="0" inner="1"/>
|
||||
</AdvHeightMap>
|
||||
<VOrgMap>
|
||||
<Map glyph=".notdef" outer="1" inner="3"/>
|
||||
<Map glyph="A" outer="1" inner="5"/>
|
||||
<Map glyph="B" outer="0" inner="5"/>
|
||||
<Map glyph="C" outer="0" inner="3"/>
|
||||
<Map glyph="D" outer="1" inner="4"/>
|
||||
<Map glyph="E" outer="0" inner="4"/>
|
||||
</VOrgMap>
|
||||
</VVAR>
|
||||
|
||||
<avar>
|
||||
<segment axis="wght">
|
||||
<mapping from="-1.0" to="-1.0"/>
|
||||
<mapping from="0.0" to="0.0"/>
|
||||
<mapping from="0.3" to="0.5"/>
|
||||
<mapping from="1.0" to="1.0"/>
|
||||
</segment>
|
||||
</avar>
|
||||
|
||||
<fvar>
|
||||
<Axis>
|
||||
<AxisTag>wght</AxisTag>
|
||||
<Flags>0x0</Flags>
|
||||
<MinValue>100.0</MinValue>
|
||||
<DefaultValue>400.0</DefaultValue>
|
||||
<MaxValue>900.0</MaxValue>
|
||||
<AxisNameID>257</AxisNameID>
|
||||
</Axis>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="258">
|
||||
<coord axis="wght" value="100.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="259">
|
||||
<coord axis="wght" value="300.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="260">
|
||||
<coord axis="wght" value="400.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="261">
|
||||
<coord axis="wght" value="700.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="262">
|
||||
<coord axis="wght" value="900.0"/>
|
||||
</NamedInstance>
|
||||
</fvar>
|
||||
|
||||
</ttFont>
|
166
Tests/subset/data/expect_HVVAR.ttx
Normal file
166
Tests/subset/data/expect_HVVAR.ttx
Normal file
@ -0,0 +1,166 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
|
||||
|
||||
<GlyphOrder>
|
||||
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
|
||||
<GlyphID id="0" name=".notdef"/>
|
||||
<GlyphID id="1" name="B"/>
|
||||
<GlyphID id="2" name="D"/>
|
||||
</GlyphOrder>
|
||||
|
||||
<HVAR>
|
||||
<Version value="0x00010000"/>
|
||||
<VarStore Format="1">
|
||||
<Format value="1"/>
|
||||
<VarRegionList>
|
||||
<!-- RegionAxisCount=1 -->
|
||||
<!-- RegionCount=2 -->
|
||||
<Region index="0">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="-1.0"/>
|
||||
<PeakCoord value="-1.0"/>
|
||||
<EndCoord value="0.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
<Region index="1">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="1.0"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
</VarRegionList>
|
||||
<!-- VarDataCount=2 -->
|
||||
<VarData index="0">
|
||||
<!-- ItemCount=5 -->
|
||||
<NumShorts value="0"/>
|
||||
<!-- VarRegionCount=1 -->
|
||||
<VarRegionIndex index="0" value="1"/>
|
||||
<Item index="0" value="[1]"/>
|
||||
<Item index="1" value="[3]"/>
|
||||
<Item index="2" value="[5]"/>
|
||||
<Item index="3" value="[2]"/>
|
||||
<Item index="4" value="[4]"/>
|
||||
</VarData>
|
||||
<VarData index="1">
|
||||
<!-- ItemCount=3 -->
|
||||
<NumShorts value="1"/>
|
||||
<!-- VarRegionCount=2 -->
|
||||
<VarRegionIndex index="0" value="0"/>
|
||||
<VarRegionIndex index="1" value="1"/>
|
||||
<Item index="0" value="[11, 22]"/>
|
||||
<Item index="1" value="[126, 23]"/>
|
||||
<Item index="2" value="[-129, 26]"/>
|
||||
</VarData>
|
||||
</VarStore>
|
||||
<LsbMap>
|
||||
<Map glyph=".notdef" outer="0" inner="1"/>
|
||||
<Map glyph="B" outer="1" inner="0"/>
|
||||
<Map glyph="D" outer="0" inner="4"/>
|
||||
</LsbMap>
|
||||
<RsbMap>
|
||||
<Map glyph=".notdef" outer="0" inner="3"/>
|
||||
<Map glyph="B" outer="1" inner="2"/>
|
||||
<Map glyph="D" outer="1" inner="1"/>
|
||||
</RsbMap>
|
||||
</HVAR>
|
||||
|
||||
<VVAR>
|
||||
<Version value="0x00010000"/>
|
||||
<VarStore Format="1">
|
||||
<Format value="1"/>
|
||||
<VarRegionList>
|
||||
<!-- RegionAxisCount=1 -->
|
||||
<!-- RegionCount=3 -->
|
||||
<Region index="0">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="1.0"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
<Region index="1">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="-1.0"/>
|
||||
<PeakCoord value="-0.5"/>
|
||||
<EndCoord value="0.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
<Region index="2">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.5"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
</VarRegionList>
|
||||
<!-- VarDataCount=2 -->
|
||||
<VarData index="0">
|
||||
<!-- ItemCount=2 -->
|
||||
<NumShorts value="0"/>
|
||||
<!-- VarRegionCount=1 -->
|
||||
<VarRegionIndex index="0" value="0"/>
|
||||
<Item index="0" value="[0]"/>
|
||||
<Item index="1" value="[6]"/>
|
||||
</VarData>
|
||||
<VarData index="1">
|
||||
<!-- ItemCount=4 -->
|
||||
<NumShorts value="1"/>
|
||||
<!-- VarRegionCount=3 -->
|
||||
<VarRegionIndex index="0" value="1"/>
|
||||
<VarRegionIndex index="1" value="0"/>
|
||||
<VarRegionIndex index="2" value="2"/>
|
||||
<Item index="0" value="[11, 1, 21]"/>
|
||||
<Item index="1" value="[12, 2, 22]"/>
|
||||
<Item index="2" value="[14, 4, 24]"/>
|
||||
<Item index="3" value="[-129, 5, 25]"/>
|
||||
</VarData>
|
||||
</VarStore>
|
||||
<AdvHeightMap>
|
||||
<Map glyph=".notdef" outer="1" inner="0"/>
|
||||
<Map glyph="B" outer="0" inner="0"/>
|
||||
<Map glyph="D" outer="1" inner="1"/>
|
||||
</AdvHeightMap>
|
||||
<VOrgMap>
|
||||
<Map glyph=".notdef" outer="1" inner="2"/>
|
||||
<Map glyph="B" outer="0" inner="1"/>
|
||||
<Map glyph="D" outer="1" inner="3"/>
|
||||
</VOrgMap>
|
||||
</VVAR>
|
||||
|
||||
<avar>
|
||||
<segment axis="wght">
|
||||
<mapping from="-1.0" to="-1.0"/>
|
||||
<mapping from="0.0" to="0.0"/>
|
||||
<mapping from="0.3" to="0.5"/>
|
||||
<mapping from="1.0" to="1.0"/>
|
||||
</segment>
|
||||
</avar>
|
||||
|
||||
<fvar>
|
||||
<Axis>
|
||||
<AxisTag>wght</AxisTag>
|
||||
<Flags>0x0</Flags>
|
||||
<MinValue>100.0</MinValue>
|
||||
<DefaultValue>400.0</DefaultValue>
|
||||
<MaxValue>900.0</MaxValue>
|
||||
<AxisNameID>257</AxisNameID>
|
||||
</Axis>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="258">
|
||||
<coord axis="wght" value="100.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="259">
|
||||
<coord axis="wght" value="300.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="260">
|
||||
<coord axis="wght" value="400.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="261">
|
||||
<coord axis="wght" value="700.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="262">
|
||||
<coord axis="wght" value="900.0"/>
|
||||
</NamedInstance>
|
||||
</fvar>
|
||||
|
||||
</ttFont>
|
182
Tests/subset/data/expect_HVVAR_retain_gids.ttx
Normal file
182
Tests/subset/data/expect_HVVAR_retain_gids.ttx
Normal file
@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.40">
|
||||
|
||||
<GlyphOrder>
|
||||
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
|
||||
<GlyphID id="0" name=".notdef"/>
|
||||
<GlyphID id="1" name="glyph00001"/>
|
||||
<GlyphID id="2" name="B"/>
|
||||
<GlyphID id="3" name="glyph00003"/>
|
||||
<GlyphID id="4" name="D"/>
|
||||
<GlyphID id="5" name="glyph00005"/>
|
||||
</GlyphOrder>
|
||||
|
||||
<HVAR>
|
||||
<Version value="0x00010000"/>
|
||||
<VarStore Format="1">
|
||||
<Format value="1"/>
|
||||
<VarRegionList>
|
||||
<!-- RegionAxisCount=1 -->
|
||||
<!-- RegionCount=2 -->
|
||||
<Region index="0">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="-1.0"/>
|
||||
<PeakCoord value="-1.0"/>
|
||||
<EndCoord value="0.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
<Region index="1">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="1.0"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
</VarRegionList>
|
||||
<!-- VarDataCount=2 -->
|
||||
<VarData index="0">
|
||||
<!-- ItemCount=6 -->
|
||||
<NumShorts value="0"/>
|
||||
<!-- VarRegionCount=1 -->
|
||||
<VarRegionIndex index="0" value="1"/>
|
||||
<Item index="0" value="[1]"/>
|
||||
<Item index="1" value="[2]"/>
|
||||
<Item index="2" value="[3]"/>
|
||||
<Item index="3" value="[4]"/>
|
||||
<Item index="4" value="[5]"/>
|
||||
<Item index="5" value="[0]"/>
|
||||
</VarData>
|
||||
<VarData index="1">
|
||||
<!-- ItemCount=3 -->
|
||||
<NumShorts value="1"/>
|
||||
<!-- VarRegionCount=2 -->
|
||||
<VarRegionIndex index="0" value="0"/>
|
||||
<VarRegionIndex index="1" value="1"/>
|
||||
<Item index="0" value="[11, 22]"/>
|
||||
<Item index="1" value="[126, 23]"/>
|
||||
<Item index="2" value="[-129, 26]"/>
|
||||
</VarData>
|
||||
</VarStore>
|
||||
<LsbMap>
|
||||
<Map glyph=".notdef" outer="0" inner="2"/>
|
||||
<Map glyph="B" outer="1" inner="0"/>
|
||||
<Map glyph="D" outer="0" inner="3"/>
|
||||
<Map glyph="glyph00001" outer="0" inner="0"/>
|
||||
<Map glyph="glyph00003" outer="0" inner="0"/>
|
||||
<Map glyph="glyph00005" outer="0" inner="3"/>
|
||||
</LsbMap>
|
||||
<RsbMap>
|
||||
<Map glyph=".notdef" outer="0" inner="1"/>
|
||||
<Map glyph="B" outer="1" inner="2"/>
|
||||
<Map glyph="D" outer="1" inner="1"/>
|
||||
<Map glyph="glyph00001" outer="0" inner="0"/>
|
||||
<Map glyph="glyph00003" outer="0" inner="0"/>
|
||||
<Map glyph="glyph00005" outer="1" inner="1"/>
|
||||
</RsbMap>
|
||||
</HVAR>
|
||||
|
||||
<VVAR>
|
||||
<Version value="0x00010000"/>
|
||||
<VarStore Format="1">
|
||||
<Format value="1"/>
|
||||
<VarRegionList>
|
||||
<!-- RegionAxisCount=1 -->
|
||||
<!-- RegionCount=3 -->
|
||||
<Region index="0">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="1.0"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
<Region index="1">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="-1.0"/>
|
||||
<PeakCoord value="-0.5"/>
|
||||
<EndCoord value="0.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
<Region index="2">
|
||||
<VarRegionAxis index="0">
|
||||
<StartCoord value="0.0"/>
|
||||
<PeakCoord value="0.5"/>
|
||||
<EndCoord value="1.0"/>
|
||||
</VarRegionAxis>
|
||||
</Region>
|
||||
</VarRegionList>
|
||||
<!-- VarDataCount=2 -->
|
||||
<VarData index="0">
|
||||
<!-- ItemCount=2 -->
|
||||
<NumShorts value="0"/>
|
||||
<!-- VarRegionCount=1 -->
|
||||
<VarRegionIndex index="0" value="0"/>
|
||||
<Item index="0" value="[0]"/>
|
||||
<Item index="1" value="[6]"/>
|
||||
</VarData>
|
||||
<VarData index="1">
|
||||
<!-- ItemCount=4 -->
|
||||
<NumShorts value="1"/>
|
||||
<!-- VarRegionCount=3 -->
|
||||
<VarRegionIndex index="0" value="1"/>
|
||||
<VarRegionIndex index="1" value="0"/>
|
||||
<VarRegionIndex index="2" value="2"/>
|
||||
<Item index="0" value="[11, 1, 21]"/>
|
||||
<Item index="1" value="[12, 2, 22]"/>
|
||||
<Item index="2" value="[14, 4, 24]"/>
|
||||
<Item index="3" value="[-129, 5, 25]"/>
|
||||
</VarData>
|
||||
</VarStore>
|
||||
<AdvHeightMap>
|
||||
<Map glyph=".notdef" outer="1" inner="0"/>
|
||||
<Map glyph="B" outer="0" inner="0"/>
|
||||
<Map glyph="D" outer="1" inner="1"/>
|
||||
<Map glyph="glyph00001" outer="0" inner="0"/>
|
||||
<Map glyph="glyph00003" outer="0" inner="0"/>
|
||||
<Map glyph="glyph00005" outer="1" inner="1"/>
|
||||
</AdvHeightMap>
|
||||
<VOrgMap>
|
||||
<Map glyph=".notdef" outer="1" inner="2"/>
|
||||
<Map glyph="B" outer="0" inner="1"/>
|
||||
<Map glyph="D" outer="1" inner="3"/>
|
||||
<Map glyph="glyph00001" outer="0" inner="0"/>
|
||||
<Map glyph="glyph00003" outer="0" inner="0"/>
|
||||
<Map glyph="glyph00005" outer="1" inner="3"/>
|
||||
</VOrgMap>
|
||||
</VVAR>
|
||||
|
||||
<avar>
|
||||
<segment axis="wght">
|
||||
<mapping from="-1.0" to="-1.0"/>
|
||||
<mapping from="0.0" to="0.0"/>
|
||||
<mapping from="0.3" to="0.5"/>
|
||||
<mapping from="1.0" to="1.0"/>
|
||||
</segment>
|
||||
</avar>
|
||||
|
||||
<fvar>
|
||||
<Axis>
|
||||
<AxisTag>wght</AxisTag>
|
||||
<Flags>0x0</Flags>
|
||||
<MinValue>100.0</MinValue>
|
||||
<DefaultValue>400.0</DefaultValue>
|
||||
<MaxValue>900.0</MaxValue>
|
||||
<AxisNameID>257</AxisNameID>
|
||||
</Axis>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="258">
|
||||
<coord axis="wght" value="100.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="259">
|
||||
<coord axis="wght" value="300.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="260">
|
||||
<coord axis="wght" value="400.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="261">
|
||||
<coord axis="wght" value="700.0"/>
|
||||
</NamedInstance>
|
||||
<NamedInstance flags="0x0" subfamilyNameID="262">
|
||||
<coord axis="wght" value="900.0"/>
|
||||
</NamedInstance>
|
||||
</fvar>
|
||||
|
||||
</ttFont>
|
@ -485,6 +485,24 @@ class SubsetTest(unittest.TestCase):
|
||||
subset.main([fontpath, "--recalc-timestamp", "--output-file=%s" % subsetpath, "*"])
|
||||
self.assertLess(modified, TTFont(subsetpath)['head'].modified)
|
||||
|
||||
def test_recalc_max_context(self):
|
||||
ttxpath = self.getpath("Lobster.subset.ttx")
|
||||
font = TTFont()
|
||||
font.importXML(ttxpath)
|
||||
max_context = font['OS/2'].usMaxContext
|
||||
_, fontpath = self.compile_font(ttxpath, ".otf")
|
||||
subsetpath = self.temp_path(".otf")
|
||||
|
||||
# by default, the subsetter does not recalculate the usMaxContext
|
||||
subset.main([fontpath, "--drop-tables+=GSUB,GPOS",
|
||||
"--output-file=%s" % subsetpath])
|
||||
self.assertEqual(max_context, TTFont(subsetpath)['OS/2'].usMaxContext)
|
||||
|
||||
subset.main([fontpath, "--recalc-max-context",
|
||||
"--drop-tables+=GSUB,GPOS",
|
||||
"--output-file=%s" % subsetpath])
|
||||
self.assertEqual(0, TTFont(subsetpath)['OS/2'].usMaxContext)
|
||||
|
||||
def test_retain_gids_ttf(self):
|
||||
_, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
|
||||
font = TTFont(fontpath)
|
||||
@ -586,6 +604,20 @@ class SubsetTest(unittest.TestCase):
|
||||
self.assertGreater(len(cs["A"].program), 0)
|
||||
self.assertEqual(cs["glyph00002"].program, [])
|
||||
|
||||
def test_HVAR_VVAR(self):
|
||||
_, fontpath = self.compile_font(self.getpath("TestHVVAR.ttx"), ".ttf")
|
||||
subsetpath = self.temp_path(".ttf")
|
||||
subset.main([fontpath, "--text=BD", "--output-file=%s" % subsetpath])
|
||||
subsetfont = TTFont(subsetpath)
|
||||
self.expect_ttx(subsetfont, self.getpath("expect_HVVAR.ttx"), ["GlyphOrder", "HVAR", "VVAR", "avar", "fvar"])
|
||||
|
||||
def test_HVAR_VVAR_retain_gids(self):
|
||||
_, fontpath = self.compile_font(self.getpath("TestHVVAR.ttx"), ".ttf")
|
||||
subsetpath = self.temp_path(".ttf")
|
||||
subset.main([fontpath, "--text=BD", "--retain-gids", "--output-file=%s" % subsetpath])
|
||||
subsetfont = TTFont(subsetpath)
|
||||
self.expect_ttx(subsetfont, self.getpath("expect_HVVAR_retain_gids.ttx"), ["GlyphOrder", "HVAR", "VVAR", "avar", "fvar"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(unittest.main())
|
||||
|
229
Tests/varLib/data/TestSparseCFF2VF.designspace
Normal file
229
Tests/varLib/data/TestSparseCFF2VF.designspace
Normal file
@ -0,0 +1,229 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<designspace format="3">
|
||||
<axes>
|
||||
<axis default="200" maximum="900" minimum="200" name="weight" tag="wght">
|
||||
<map input="200" output="0" /> <!-- ExtraLight -->
|
||||
<map input="300" output="160" /> <!-- Light -->
|
||||
<map input="350" output="320" /> <!-- Normal -->
|
||||
<map input="400" output="390" /> <!-- Regular -->
|
||||
<map input="500" output="560" /> <!-- Medium -->
|
||||
<map input="700" output="780" /> <!-- Bold -->
|
||||
<map input="900" output="1000" /><!-- Heavy -->
|
||||
</axis>
|
||||
</axes>
|
||||
<sources>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w0.00.ufo" stylename="w0.00">
|
||||
<info copy="1" />
|
||||
<location>
|
||||
<dimension name="weight" xvalue="0.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w439.00.ufo" stylename="w439.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="439.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w440.00.ufo" stylename="w440.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="440.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w599.00.ufo" stylename="w599.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="599.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w600.00.ufo" stylename="w600.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="600.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w669.00.ufo" stylename="w669.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="669.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w670.00.ufo" stylename="w670.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="670.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w799.00.ufo" stylename="w799.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="799.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w800.00.ufo" stylename="w800.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="800.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w889.00.ufo" stylename="w889.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="889.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w890.00.ufo" stylename="w890.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="890.00" />
|
||||
</location>
|
||||
</source>
|
||||
<source filename="master_sparse_cff2/MasterSet_Kanji-w1000.00.ufo" stylename="w1000.00">
|
||||
|
||||
<location>
|
||||
<dimension name="weight" xvalue="1000.00" />
|
||||
</location>
|
||||
</source>
|
||||
</sources>
|
||||
<instances>
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w0.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w0.00" stylename="Kanji-w0.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="0" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w239.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w239.00" stylename="Kanji-w239.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="239" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w240.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w240.00" stylename="Kanji-w240.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="240" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w439.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w439.00" stylename="Kanji-w439.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="439" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w440.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w440.00" stylename="Kanji-w440.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="440" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
|
||||
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w499.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w499.00" stylename="Kanji-w499.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="499" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w500.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w500.00" stylename="Kanji-w500.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="500" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
|
||||
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w599.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w599.00" stylename="Kanji-w599.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="599" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w600.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w600.00" stylename="Kanji-w600.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="600" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
|
||||
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w669.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w669.00" stylename="Kanji-w669.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="669" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w670.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w670.00" stylename="Kanji-w670.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="670" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
|
||||
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w699.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w699.00" stylename="Kanji-w699.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="699" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w700.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w700.00" stylename="Kanji-w700.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="700" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
|
||||
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-799.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-799.00" stylename="Kanji-799.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="799" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w800.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w800.00" stylename="Kanji-w800.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="800" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
|
||||
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w889.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w889.00" stylename="Kanji-w889.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="889" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w890.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w890.00" stylename="Kanji-w890.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="890" />
|
||||
</location>
|
||||
<kerning />
|
||||
<info />
|
||||
</instance>
|
||||
|
||||
|
||||
<instance familyname="SHSansJPVFTest" filename="instances/SHSansJPVFTest-Kanji-w1000.00.otf" postscriptfontname="SHSansJPVFTest-Kanji-w1000.00" stylename="Kanji-w1000.00">
|
||||
<location>
|
||||
<dimension name="weight" xvalue="1000" />
|
||||
</location>
|
||||
</instance>
|
||||
</instances>
|
||||
</designspace>
|
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w0.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w0.00.otf
Normal file
Binary file not shown.
Binary file not shown.
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w439.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w439.00.otf
Normal file
Binary file not shown.
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w440.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w440.00.otf
Normal file
Binary file not shown.
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w599.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w599.00.otf
Normal file
Binary file not shown.
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w600.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w600.00.otf
Normal file
Binary file not shown.
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w669.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w669.00.otf
Normal file
Binary file not shown.
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w670.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w670.00.otf
Normal file
Binary file not shown.
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w799.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w799.00.otf
Normal file
Binary file not shown.
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w800.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w800.00.otf
Normal file
Binary file not shown.
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w889.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w889.00.otf
Normal file
Binary file not shown.
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w890.00.otf
Normal file
BIN
Tests/varLib/data/master_sparse_cff2/MasterSet_Kanji-w890.00.otf
Normal file
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ttFont sfntVersion="OTTO" ttLibVersion="3.32">
|
||||
<ttFont sfntVersion="OTTO" ttLibVersion="3.38">
|
||||
|
||||
<fvar>
|
||||
|
||||
@ -67,29 +67,29 @@
|
||||
<BlueValues>
|
||||
<blend value="-12 0 0"/>
|
||||
<blend value="0 0 0"/>
|
||||
<blend value="486 -8 14"/>
|
||||
<blend value="486 -8 -8"/>
|
||||
<blend value="498 0 0"/>
|
||||
<blend value="574 4 -8"/>
|
||||
<blend value="574 4 4"/>
|
||||
<blend value="586 0 0"/>
|
||||
<blend value="638 6 -10"/>
|
||||
<blend value="638 6 6"/>
|
||||
<blend value="650 0 0"/>
|
||||
<blend value="656 2 -2"/>
|
||||
<blend value="656 2 2"/>
|
||||
<blend value="668 0 0"/>
|
||||
<blend value="712 6 -10"/>
|
||||
<blend value="712 6 6"/>
|
||||
<blend value="724 0 0"/>
|
||||
</BlueValues>
|
||||
<OtherBlues>
|
||||
<blend value="-217 -17 29"/>
|
||||
<blend value="-217 -17 -17"/>
|
||||
<blend value="-205 0 0"/>
|
||||
</OtherBlues>
|
||||
<BlueScale value="0.0625"/>
|
||||
<BlueShift value="7"/>
|
||||
<BlueFuzz value="0"/>
|
||||
<StdHW>
|
||||
<blend value="67 -39.0 67.0"/>
|
||||
<blend value="67 -39.0 -39.0"/>
|
||||
</StdHW>
|
||||
<StdVW>
|
||||
<blend value="85 -51.0 87.0"/>
|
||||
<blend value="85 -51.0 -51.0"/>
|
||||
</StdVW>
|
||||
</Private>
|
||||
</FontDict>
|
||||
|
1301
Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
Normal file
1301
Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
Normal file
File diff suppressed because it is too large
Load Diff
@ -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()
|
||||
"""
|
||||
|
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 3.40.1.dev0
|
||||
current_version = 3.41.1.dev0
|
||||
commit = True
|
||||
tag = False
|
||||
tag_name = {new_version}
|
||||
|
Loading…
x
Reference in New Issue
Block a user