Merge pull request #3689 from fonttools/specializer-argsStackUse
[cffLib.specializer] Adjust stack use calculation
This commit is contained in:
commit
081d6a27ab
@ -80,8 +80,9 @@ def programToCommands(program, getNumRegions=None):
|
|||||||
numBlendArgs = numBlends * numSourceFonts + 1
|
numBlendArgs = numBlends * numSourceFonts + 1
|
||||||
# replace first blend op by a list of the blend ops.
|
# replace first blend op by a list of the blend ops.
|
||||||
stack[-numBlendArgs:] = [stack[-numBlendArgs:]]
|
stack[-numBlendArgs:] = [stack[-numBlendArgs:]]
|
||||||
lenBlendStack += numBlends + len(stack) - 1
|
lenStack = len(stack)
|
||||||
lastBlendIndex = len(stack)
|
lenBlendStack += numBlends + lenStack - 1
|
||||||
|
lastBlendIndex = lenStack
|
||||||
# if a blend op exists, this is or will be a CFF2 charstring.
|
# if a blend op exists, this is or will be a CFF2 charstring.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -153,9 +154,10 @@ def commandsToProgram(commands):
|
|||||||
|
|
||||||
def _everyN(el, n):
|
def _everyN(el, n):
|
||||||
"""Group the list el into groups of size n"""
|
"""Group the list el into groups of size n"""
|
||||||
if len(el) % n != 0:
|
l = len(el)
|
||||||
|
if l % n != 0:
|
||||||
raise ValueError(el)
|
raise ValueError(el)
|
||||||
for i in range(0, len(el), n):
|
for i in range(0, l, n):
|
||||||
yield el[i : i + n]
|
yield el[i : i + n]
|
||||||
|
|
||||||
|
|
||||||
@ -218,9 +220,10 @@ class _GeneralizerDecombinerCommandsMap(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hhcurveto(args):
|
def hhcurveto(args):
|
||||||
if len(args) < 4 or len(args) % 4 > 1:
|
l = len(args)
|
||||||
|
if l < 4 or l % 4 > 1:
|
||||||
raise ValueError(args)
|
raise ValueError(args)
|
||||||
if len(args) % 2 == 1:
|
if l % 2 == 1:
|
||||||
yield ("rrcurveto", [args[1], args[0], args[2], args[3], args[4], 0])
|
yield ("rrcurveto", [args[1], args[0], args[2], args[3], args[4], 0])
|
||||||
args = args[5:]
|
args = args[5:]
|
||||||
for args in _everyN(args, 4):
|
for args in _everyN(args, 4):
|
||||||
@ -228,9 +231,10 @@ class _GeneralizerDecombinerCommandsMap(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def vvcurveto(args):
|
def vvcurveto(args):
|
||||||
if len(args) < 4 or len(args) % 4 > 1:
|
l = len(args)
|
||||||
|
if l < 4 or l % 4 > 1:
|
||||||
raise ValueError(args)
|
raise ValueError(args)
|
||||||
if len(args) % 2 == 1:
|
if l % 2 == 1:
|
||||||
yield ("rrcurveto", [args[0], args[1], args[2], args[3], 0, args[4]])
|
yield ("rrcurveto", [args[0], args[1], args[2], args[3], 0, args[4]])
|
||||||
args = args[5:]
|
args = args[5:]
|
||||||
for args in _everyN(args, 4):
|
for args in _everyN(args, 4):
|
||||||
@ -238,11 +242,12 @@ class _GeneralizerDecombinerCommandsMap(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hvcurveto(args):
|
def hvcurveto(args):
|
||||||
if len(args) < 4 or len(args) % 8 not in {0, 1, 4, 5}:
|
l = len(args)
|
||||||
|
if l < 4 or l % 8 not in {0, 1, 4, 5}:
|
||||||
raise ValueError(args)
|
raise ValueError(args)
|
||||||
last_args = None
|
last_args = None
|
||||||
if len(args) % 2 == 1:
|
if l % 2 == 1:
|
||||||
lastStraight = len(args) % 8 == 5
|
lastStraight = l % 8 == 5
|
||||||
args, last_args = args[:-5], args[-5:]
|
args, last_args = args[:-5], args[-5:]
|
||||||
it = _everyN(args, 4)
|
it = _everyN(args, 4)
|
||||||
try:
|
try:
|
||||||
@ -262,11 +267,12 @@ class _GeneralizerDecombinerCommandsMap(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def vhcurveto(args):
|
def vhcurveto(args):
|
||||||
if len(args) < 4 or len(args) % 8 not in {0, 1, 4, 5}:
|
l = len(args)
|
||||||
|
if l < 4 or l % 8 not in {0, 1, 4, 5}:
|
||||||
raise ValueError(args)
|
raise ValueError(args)
|
||||||
last_args = None
|
last_args = None
|
||||||
if len(args) % 2 == 1:
|
if l % 2 == 1:
|
||||||
lastStraight = len(args) % 8 == 5
|
lastStraight = l % 8 == 5
|
||||||
args, last_args = args[:-5], args[-5:]
|
args, last_args = args[:-5], args[-5:]
|
||||||
it = _everyN(args, 4)
|
it = _everyN(args, 4)
|
||||||
try:
|
try:
|
||||||
@ -286,7 +292,8 @@ class _GeneralizerDecombinerCommandsMap(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def rcurveline(args):
|
def rcurveline(args):
|
||||||
if len(args) < 8 or len(args) % 6 != 2:
|
l = len(args)
|
||||||
|
if l < 8 or l % 6 != 2:
|
||||||
raise ValueError(args)
|
raise ValueError(args)
|
||||||
args, last_args = args[:-2], args[-2:]
|
args, last_args = args[:-2], args[-2:]
|
||||||
for args in _everyN(args, 6):
|
for args in _everyN(args, 6):
|
||||||
@ -295,7 +302,8 @@ class _GeneralizerDecombinerCommandsMap(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def rlinecurve(args):
|
def rlinecurve(args):
|
||||||
if len(args) < 8 or len(args) % 2 != 0:
|
l = len(args)
|
||||||
|
if l < 8 or l % 2 != 0:
|
||||||
raise ValueError(args)
|
raise ValueError(args)
|
||||||
args, last_args = args[:-6], args[-6:]
|
args, last_args = args[:-6], args[-6:]
|
||||||
for args in _everyN(args, 2):
|
for args in _everyN(args, 2):
|
||||||
@ -330,8 +338,9 @@ def _convertBlendOpToArgs(blendList):
|
|||||||
# comprehension. See calling context
|
# comprehension. See calling context
|
||||||
args = args[:-1]
|
args = args[:-1]
|
||||||
|
|
||||||
numRegions = len(args) // numBlends - 1
|
l = len(args)
|
||||||
if not (numBlends * (numRegions + 1) == len(args)):
|
numRegions = l // numBlends - 1
|
||||||
|
if not (numBlends * (numRegions + 1) == l):
|
||||||
raise ValueError(blendList)
|
raise ValueError(blendList)
|
||||||
|
|
||||||
defaultArgs = [[arg] for arg in args[:numBlends]]
|
defaultArgs = [[arg] for arg in args[:numBlends]]
|
||||||
@ -368,7 +377,7 @@ def generalizeCommands(commands, ignoreErrors=False):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
func = getattr(mapping, op, None)
|
func = getattr(mapping, op, None)
|
||||||
if not func:
|
if func is None:
|
||||||
result.append((op, args))
|
result.append((op, args))
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
@ -715,6 +724,7 @@ def specializeCommands(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# 5. Combine adjacent operators when possible, minding not to go over max stack size.
|
# 5. Combine adjacent operators when possible, minding not to go over max stack size.
|
||||||
|
stackUse = _argsStackUse(commands[-1][1]) if commands else 0
|
||||||
for i in range(len(commands) - 1, 0, -1):
|
for i in range(len(commands) - 1, 0, -1):
|
||||||
op1, args1 = commands[i - 1]
|
op1, args1 = commands[i - 1]
|
||||||
op2, args2 = commands[i]
|
op2, args2 = commands[i]
|
||||||
@ -725,9 +735,10 @@ def specializeCommands(
|
|||||||
if op1 == op2:
|
if op1 == op2:
|
||||||
new_op = op1
|
new_op = op1
|
||||||
else:
|
else:
|
||||||
if op2 == "rrcurveto" and len(args2) == 6:
|
l = len(args2)
|
||||||
|
if op2 == "rrcurveto" and l == 6:
|
||||||
new_op = "rlinecurve"
|
new_op = "rlinecurve"
|
||||||
elif len(args2) == 2:
|
elif l == 2:
|
||||||
new_op = "rcurveline"
|
new_op = "rcurveline"
|
||||||
|
|
||||||
elif (op1, op2) in {("rlineto", "rlinecurve"), ("rrcurveto", "rcurveline")}:
|
elif (op1, op2) in {("rlineto", "rlinecurve"), ("rrcurveto", "rcurveline")}:
|
||||||
@ -764,9 +775,14 @@ def specializeCommands(
|
|||||||
|
|
||||||
# Make sure the stack depth does not exceed (maxstack - 1), so
|
# Make sure the stack depth does not exceed (maxstack - 1), so
|
||||||
# that subroutinizer can insert subroutine calls at any point.
|
# that subroutinizer can insert subroutine calls at any point.
|
||||||
if new_op and _argsStackUse(args1) + _argsStackUse(args2) < maxstack:
|
args1StackUse = _argsStackUse(args1)
|
||||||
|
combinedStackUse = max(args1StackUse, len(args1) + stackUse)
|
||||||
|
if new_op and combinedStackUse < maxstack:
|
||||||
commands[i - 1] = (new_op, args1 + args2)
|
commands[i - 1] = (new_op, args1 + args2)
|
||||||
del commands[i]
|
del commands[i]
|
||||||
|
stackUse = combinedStackUse
|
||||||
|
else:
|
||||||
|
stackUse = args1StackUse
|
||||||
|
|
||||||
# 6. Resolve any remaining made-up operators into real operators.
|
# 6. Resolve any remaining made-up operators into real operators.
|
||||||
for i in range(len(commands)):
|
for i in range(len(commands)):
|
||||||
@ -777,9 +793,11 @@ def specializeCommands(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if op[2:] == "curveto" and op[:2] not in {"rr", "hh", "vv", "vh", "hv"}:
|
if op[2:] == "curveto" and op[:2] not in {"rr", "hh", "vv", "vh", "hv"}:
|
||||||
|
l = len(args)
|
||||||
|
|
||||||
op0, op1 = op[:2]
|
op0, op1 = op[:2]
|
||||||
if (op0 == "r") ^ (op1 == "r"):
|
if (op0 == "r") ^ (op1 == "r"):
|
||||||
assert len(args) % 2 == 1
|
assert l % 2 == 1
|
||||||
if op0 == "0":
|
if op0 == "0":
|
||||||
op0 = "h"
|
op0 = "h"
|
||||||
if op1 == "0":
|
if op1 == "0":
|
||||||
@ -790,9 +808,9 @@ def specializeCommands(
|
|||||||
op1 = _negateCategory(op0)
|
op1 = _negateCategory(op0)
|
||||||
assert {op0, op1} <= {"h", "v"}, (op0, op1)
|
assert {op0, op1} <= {"h", "v"}, (op0, op1)
|
||||||
|
|
||||||
if len(args) % 2:
|
if l % 2:
|
||||||
if op0 != op1: # vhcurveto / hvcurveto
|
if op0 != op1: # vhcurveto / hvcurveto
|
||||||
if (op0 == "h") ^ (len(args) % 8 == 1):
|
if (op0 == "h") ^ (l % 8 == 1):
|
||||||
# Swap last two args order
|
# Swap last two args order
|
||||||
args = args[:-2] + args[-1:] + args[-2:-1]
|
args = args[:-2] + args[-1:] + args[-2:-1]
|
||||||
else: # hhcurveto / vvcurveto
|
else: # hhcurveto / vvcurveto
|
||||||
|
@ -23,9 +23,7 @@ def charstr_specialize(charstr, **kwargs):
|
|||||||
return programToString(specializeProgram(stringToProgram(charstr), **kwargs))
|
return programToString(specializeProgram(stringToProgram(charstr), **kwargs))
|
||||||
|
|
||||||
|
|
||||||
def charstr_stack_use(charstr, getNumRegions=None):
|
def program_stack_use(program, getNumRegions=None):
|
||||||
program = stringToProgram(charstr)
|
|
||||||
|
|
||||||
vsindex = None
|
vsindex = None
|
||||||
maxStack = 0
|
maxStack = 0
|
||||||
stack = []
|
stack = []
|
||||||
@ -586,19 +584,38 @@ class CFFSpecializeProgramTest:
|
|||||||
# maxstack CFF2=513, specializer uses up to 512
|
# maxstack CFF2=513, specializer uses up to 512
|
||||||
def test_maxstack_blends(self):
|
def test_maxstack_blends(self):
|
||||||
numRegions = 15
|
numRegions = 15
|
||||||
numOps = 2000
|
numOps = 600
|
||||||
getNumRegions = lambda iv: numRegions
|
getNumRegions = lambda iv: numRegions
|
||||||
blend_one = " ".join([str(i) for i in range(1 + numRegions)] + ["1", "blend"])
|
blend_one = [i for i in range(1 + numRegions)] + [1, "blend"]
|
||||||
operands = " ".join([blend_one] * 6)
|
operands = blend_one * 6
|
||||||
operator = "rrcurveto"
|
operator = "rrcurveto"
|
||||||
charstr = " ".join([operands, operator] * numOps)
|
program = (operands + [operator]) * numOps
|
||||||
expected = charstr
|
specialized = specializeProgram(
|
||||||
specialized = charstr_specialize(
|
program,
|
||||||
charstr, getNumRegions=getNumRegions, maxstack=maxStack
|
getNumRegions=getNumRegions,
|
||||||
|
maxstack=maxStack,
|
||||||
|
generalizeFirst=False,
|
||||||
)
|
)
|
||||||
stack_use = charstr_stack_use(specialized, getNumRegions=getNumRegions)
|
stack_use = program_stack_use(specialized, getNumRegions=getNumRegions)
|
||||||
assert maxStack - numRegions < stack_use < maxStack
|
assert maxStack - numRegions < stack_use < maxStack
|
||||||
|
|
||||||
|
def test_maxstack_commands(self):
|
||||||
|
# See if two commands with deep blends are merged into one
|
||||||
|
numRegions = 400
|
||||||
|
numOps = 2
|
||||||
|
getNumRegions = lambda iv: numRegions
|
||||||
|
blend_one = [i for i in range(1 + numRegions)] + [1, "blend"]
|
||||||
|
operands = blend_one * 6
|
||||||
|
operator = "rrcurveto"
|
||||||
|
program = (operands + [operator]) * numOps
|
||||||
|
specialized = specializeProgram(
|
||||||
|
program,
|
||||||
|
getNumRegions=getNumRegions,
|
||||||
|
maxstack=maxStack,
|
||||||
|
generalizeFirst=False,
|
||||||
|
)
|
||||||
|
assert specialized.index("rrcurveto") == len(specialized) - 1
|
||||||
|
|
||||||
|
|
||||||
class CFF2VFTestSpecialize(DataFilesHandler):
|
class CFF2VFTestSpecialize(DataFilesHandler):
|
||||||
def test_blend_round_trip(self):
|
def test_blend_round_trip(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user