diff --git a/Lib/fontTools/cffLib/specializer.py b/Lib/fontTools/cffLib/specializer.py index e61ad26f8..11e9e578e 100644 --- a/Lib/fontTools/cffLib/specializer.py +++ b/Lib/fontTools/cffLib/specializer.py @@ -764,7 +764,17 @@ def specializeCommands( # Make sure the stack depth does not exceed (maxstack - 1), so # that subroutinizer can insert subroutine calls at any point. - if new_op and _argsStackUse(args1) + _argsStackUse(args2) < maxstack: + # + # The assumption is that args1, and args2, each individually + # can be successfully processed without a stack overflow. + # When combined, the stack depth to consider would be the + # number of items in args1 plus what it takes to build args2 + # on top of args1, which will be already on the stack as + # len(args1) items. + # + # It's unfortunate that _argsStackUse() is O(n) in the number + # of args, but it's not a big deal hopefully. + if new_op and len(args1) + _argsStackUse(args2) < maxstack: commands[i - 1] = (new_op, args1 + args2) del commands[i] diff --git a/Tests/cffLib/specializer_test.py b/Tests/cffLib/specializer_test.py index 08c137ada..aad3bfdfb 100644 --- a/Tests/cffLib/specializer_test.py +++ b/Tests/cffLib/specializer_test.py @@ -599,6 +599,20 @@ class CFFSpecializeProgramTest: stack_use = charstr_stack_use(specialized, getNumRegions=getNumRegions) assert maxStack - numRegions < stack_use < maxStack + def test_maxstack_blends2(self): + # See if two long blend sequences are merged into one + numRegions = 400 + numOps = 2 + getNumRegions = lambda iv: numRegions + blend_one = " ".join([str(i) for i in range(1 + numRegions)] + ["1", "blend"]) + operands = " ".join([blend_one] * 6) + operator = "rrcurveto" + charstr = " ".join([operands, operator] * numOps) + specialized = charstr_specialize( + charstr, getNumRegions=getNumRegions, maxstack=maxStack + ) + assert specialized.index("rrcurveto") == len(specialized) - len("rrcurveto") + class CFF2VFTestSpecialize(DataFilesHandler): def test_blend_round_trip(self):