diff --git a/src/fonts.js b/src/fonts.js index 3cfdba094..a5754c3e5 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -3647,80 +3647,144 @@ var Font = (function FontClosure() { var TTOpsStackDeltas = [ 0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1, - 1, -1, -999, 0, 1, 0, 0, -2, 0, -1, -2, -1, -999, -999, -1, -1, + 1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1, 0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -2, 0, -2, -2, 0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1, - -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, + -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1, - -999, -2, -2, 0, 0, -1, -2, -2, 0, -999, 0, 0, 0, -1, -2]; + -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2]; // 0xC0-DF == -1 and 0xE0-FF == -2 function sanitizeTTProgram(table, ttContext) { var data = table.data; var i = 0, n, lastEndf = 0, lastDeff = 0; var stack = []; + var callstack = []; + var functionsCalled = []; var tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions; + var inFDEF = false, ifLevel = 0, inELSE = 0; for (var ii = data.length; i < ii;) { var op = data[i++]; // The TrueType instruction set docs can be found at // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html if (op === 0x40) { // NPUSHB - pushes n bytes n = data[i++]; - for (var j = 0; j < n; j++) { - stack.push(data[i++]); + if (inFDEF || inELSE) { + i += n; + } else { + for (var j = 0; j < n; j++) { + stack.push(data[i++]); + } } } else if (op === 0x41) { // NPUSHW - pushes n words n = data[i++]; - for (var j = 0; j < n; j++) { - var b = data[i++]; - stack.push((b << 8) | data[i++]); + if (inFDEF || inELSE) { + i += n * 2; + } else { + for (var j = 0; j < n; j++) { + var b = data[i++]; + stack.push((b << 8) | data[i++]); + } } } else if ((op & 0xF8) === 0xB0) { // PUSHB - pushes bytes n = op - 0xB0 + 1; - for (var j = 0; j < n; j++) { - stack.push(data[i++]); + if (inFDEF || inELSE) { + i += n; + } else { + for (var j = 0; j < n; j++) { + stack.push(data[i++]); + } } } else if ((op & 0xF8) === 0xB8) { // PUSHW - pushes words n = op - 0xB8 + 1; - for (var j = 0; j < n; j++) { - var b = data[i++]; - stack.push((b << 8) | data[i++]); + if (inFDEF || inELSE) { + i += n * 2; + } else { + for (var j = 0; j < n; j++) { + var b = data[i++]; + stack.push((b << 8) | data[i++]); + } } } else if (op === 0x2B && !tooComplexToFollowFunctions) { // CALL - // collecting inforamtion about which functions are used - var funcId = stack[stack.length - 1]; - ttContext.functionsUsed[funcId] = true; - if (i >= 2 && data[i - 2] === 0x2B) { - // all data in stack, calls are performed in sequence - tooComplexToFollowFunctions = true; + if (!inFDEF && !inELSE) { + // collecting inforamtion about which functions are used + var funcId = stack[stack.length - 1]; + ttContext.functionsUsed[funcId] = true; + if (funcId in ttContext.functionsStackDeltas) { + stack.length += ttContext.functionsStackDeltas[funcId]; + } else if (funcId in ttContext.functionsDefined && + functionsCalled.indexOf(funcId) < 0) { + callstack.push({data: data, i: i, stackTop: stack.length - 1}); + functionsCalled.push(funcId); + var pc = ttContext.functionsDefined[funcId]; + data = pc.data; + i = pc.i; + } } } else if (op === 0x2C && !tooComplexToFollowFunctions) { // FDEF - // collecting inforamtion about which functions are defined - lastDeff = i; - var funcId = stack[stack.length - 1]; - ttContext.functionsDefined[funcId] = true; - if (i >= 2 && data[i - 2] === 0x2D) { - // all function ids in stack, FDEF/ENDF perfomed in sequence + if (inFDEF || inELSE) { + warn('TT: nested FDEFs not allowed'); tooComplexToFollowFunctions = true; } + inFDEF = true; + // collecting inforamtion about which functions are defined + lastDeff = i; + var funcId = stack.pop(); + ttContext.functionsDefined[funcId] = {data: data, i: i}; } else if (op === 0x2D) { // ENDF - end of function - lastEndf = i; + if (inFDEF) { + inFDEF = false; + lastEndf = i; + } else { + var pc = callstack.pop(); + var funcId = functionsCalled.pop(); + data = pc.data; + i = pc.i; + ttContext.functionsStackDeltas[funcId] = + stack.length - pc.stackTop; + } } else if (op === 0x89) { // IDEF - instruction definition + if (inFDEF || inELSE) { + warn('TT: nested IDEFs not allowed'); + tooComplexToFollowFunctions = true; + } + inFDEF = true; // recording it as a function to track ENDF lastDeff = i; + } else if (op === 0x58) { // IF + ++ifLevel; + } else if (op === 0x1B) { // ELSE + inELSE = ifLevel; + } else if (op === 0x59) { // EIF + if (inELSE === ifLevel) { + inELSE = 0; + } + --ifLevel; + } else if (op === 0x1C) { // JMPR + var offset = stack[stack.length - 1]; + // only jumping forward to prevent infinite loop + if (offset > 0) { i += offset - 1; } } // Adjusting stack not extactly, but just enough to get function id - var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] : - op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0; - while (stackDelta < 0 && stack.length > 0) { - stack.pop(); - stackDelta++; - } - while (stackDelta > 0) { - stack.push(NaN); // pushing any number into stack - stackDelta--; + if (!inFDEF && !inELSE) { + var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] : + op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0; + if (op >= 0x71 && op <= 0x75) { + n = stack.pop(); + if (n === n) { + stackDelta = -n * 2; + } + } + while (stackDelta < 0 && stack.length > 0) { + stack.pop(); + stackDelta++; + } + while (stackDelta > 0) { + stack.push(NaN); // pushing any number into stack + stackDelta--; + } } } ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions; @@ -3729,20 +3793,44 @@ var Font = (function FontClosure() { content.push(new Uint8Array(i - data.length)); } if (lastDeff > lastEndf) { + warn('TT: complementing a missing function tail'); // new function definition started, but not finished // complete function by [CLEAR, ENDF] content.push(new Uint8Array([0x22, 0x2D])); } - if (ttContext.defineMissingFunctions && !tooComplexToFollowFunctions) { + foldTTTable(table, content); + } + + function addTTDummyFunctions(table, ttContext, maxFunctionDefs) { + var content = [table.data]; + if (!ttContext.tooComplexToFollowFunctions) { + var undefinedFunctions = []; for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) { if (!ttContext.functionsUsed[j] || ttContext.functionsDefined[j]) { continue; } + undefinedFunctions.push(j); + if (j >= maxFunctionDefs) { + continue; + } // function is used, but not defined - // creating empty one [PUSHB, function-id, FDEF, ENDF] - content.push(new Uint8Array([0xB0, j, 0x2C, 0x2D])); + if (j < 256) { + // creating empty one [PUSHB, function-id, FDEF, ENDF] + content.push(new Uint8Array([0xB0, j, 0x2C, 0x2D])); + } else { + // creating empty one [PUSHW, function-id, FDEF, ENDF] + content.push( + new Uint8Array([0xB8, j >> 8, j & 255, 0x2C, 0x2D])); + } + } + if (undefinedFunctions.length > 0) { + warn('TT: undefined functions: ' + undefinedFunctions); } } + foldTTTable(table, content); + } + + function foldTTTable(table, content) { if (content.length > 1) { // concatenating the content items var newLength = 0; @@ -3765,15 +3853,17 @@ var Font = (function FontClosure() { var ttContext = { functionsDefined: [], functionsUsed: [], + functionsStackDeltas: [], tooComplexToFollowFunctions: false }; + if (fpgm) { + sanitizeTTProgram(fpgm, ttContext); + } if (prep) { - // collecting prep functions info first sanitizeTTProgram(prep, ttContext); } if (fpgm) { - ttContext.defineMissingFunctions = true; - sanitizeTTProgram(fpgm, ttContext); + addTTDummyFunctions(fpgm, ttContext, maxFunctionDefs); } } @@ -3843,12 +3933,17 @@ var Font = (function FontClosure() { // Ensure the hmtx table contains the advance width and // sidebearings information for numGlyphs in the maxp table font.pos = (font.start || 0) + maxp.offset; - var version = int16(font.getBytes(4)); + var version = int32(font.getBytes(4)); var numGlyphs = int16(font.getBytes(2)); + var maxFunctionDefs = 0; + if (version >= 0x00010000 && maxp.length >= 22) { + font.pos += 14; + var maxFunctionDefs = int16(font.getBytes(2)); + } sanitizeMetrics(font, hhea, hmtx, numGlyphs); - sanitizeTTPrograms(fpgm, prep); + sanitizeTTPrograms(fpgm, prep, maxFunctionDefs); if (head) { sanitizeHead(head, numGlyphs, loca.length);