Merge pull request #3053 from vyv03354/fpgm

Improve TT font program parser
This commit is contained in:
Yury Delendik 2013-04-29 12:01:18 -07:00
commit 2896c48e29

View File

@ -3647,73 +3647,136 @@ var Font = (function FontClosure() {
var TTOpsStackDeltas = [ var TTOpsStackDeltas = [
0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5, 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, -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, -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, 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, -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, -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 // 0xC0-DF == -1 and 0xE0-FF == -2
function sanitizeTTProgram(table, ttContext) { function sanitizeTTProgram(table, ttContext) {
var data = table.data; var data = table.data;
var i = 0, n, lastEndf = 0, lastDeff = 0; var i = 0, n, lastEndf = 0, lastDeff = 0;
var stack = []; var stack = [];
var callstack = [];
var functionsCalled = [];
var tooComplexToFollowFunctions = var tooComplexToFollowFunctions =
ttContext.tooComplexToFollowFunctions; ttContext.tooComplexToFollowFunctions;
var inFDEF = false, ifLevel = 0, inELSE = 0;
for (var ii = data.length; i < ii;) { for (var ii = data.length; i < ii;) {
var op = data[i++]; var op = data[i++];
// The TrueType instruction set docs can be found at // The TrueType instruction set docs can be found at
// https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html
if (op === 0x40) { // NPUSHB - pushes n bytes if (op === 0x40) { // NPUSHB - pushes n bytes
n = data[i++]; n = data[i++];
if (inFDEF || inELSE) {
i += n;
} else {
for (var j = 0; j < n; j++) { for (var j = 0; j < n; j++) {
stack.push(data[i++]); stack.push(data[i++]);
} }
}
} else if (op === 0x41) { // NPUSHW - pushes n words } else if (op === 0x41) { // NPUSHW - pushes n words
n = data[i++]; n = data[i++];
if (inFDEF || inELSE) {
i += n * 2;
} else {
for (var j = 0; j < n; j++) { for (var j = 0; j < n; j++) {
var b = data[i++]; var b = data[i++];
stack.push((b << 8) | data[i++]); stack.push((b << 8) | data[i++]);
} }
}
} else if ((op & 0xF8) === 0xB0) { // PUSHB - pushes bytes } else if ((op & 0xF8) === 0xB0) { // PUSHB - pushes bytes
n = op - 0xB0 + 1; n = op - 0xB0 + 1;
if (inFDEF || inELSE) {
i += n;
} else {
for (var j = 0; j < n; j++) { for (var j = 0; j < n; j++) {
stack.push(data[i++]); stack.push(data[i++]);
} }
}
} else if ((op & 0xF8) === 0xB8) { // PUSHW - pushes words } else if ((op & 0xF8) === 0xB8) { // PUSHW - pushes words
n = op - 0xB8 + 1; n = op - 0xB8 + 1;
if (inFDEF || inELSE) {
i += n * 2;
} else {
for (var j = 0; j < n; j++) { for (var j = 0; j < n; j++) {
var b = data[i++]; var b = data[i++];
stack.push((b << 8) | data[i++]); stack.push((b << 8) | data[i++]);
} }
}
} else if (op === 0x2B && !tooComplexToFollowFunctions) { // CALL } else if (op === 0x2B && !tooComplexToFollowFunctions) { // CALL
if (!inFDEF && !inELSE) {
// collecting inforamtion about which functions are used // collecting inforamtion about which functions are used
var funcId = stack[stack.length - 1]; var funcId = stack[stack.length - 1];
ttContext.functionsUsed[funcId] = true; ttContext.functionsUsed[funcId] = true;
if (i >= 2 && data[i - 2] === 0x2B) { if (funcId in ttContext.functionsStackDeltas) {
// all data in stack, calls are performed in sequence stack.length += ttContext.functionsStackDeltas[funcId];
tooComplexToFollowFunctions = true; } 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 } else if (op === 0x2C && !tooComplexToFollowFunctions) { // FDEF
// collecting inforamtion about which functions are defined if (inFDEF || inELSE) {
lastDeff = i; warn('TT: nested FDEFs not allowed');
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
tooComplexToFollowFunctions = true; 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 } else if (op === 0x2D) { // ENDF - end of function
if (inFDEF) {
inFDEF = false;
lastEndf = i; 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 } 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 // recording it as a function to track ENDF
lastDeff = i; 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 // Adjusting stack not extactly, but just enough to get function id
if (!inFDEF && !inELSE) {
var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] : var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0; 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) { while (stackDelta < 0 && stack.length > 0) {
stack.pop(); stack.pop();
stackDelta++; stackDelta++;
@ -3723,26 +3786,51 @@ var Font = (function FontClosure() {
stackDelta--; stackDelta--;
} }
} }
}
ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions; ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
var content = [data]; var content = [data];
if (i > data.length) { if (i > data.length) {
content.push(new Uint8Array(i - data.length)); content.push(new Uint8Array(i - data.length));
} }
if (lastDeff > lastEndf) { if (lastDeff > lastEndf) {
warn('TT: complementing a missing function tail');
// new function definition started, but not finished // new function definition started, but not finished
// complete function by [CLEAR, ENDF] // complete function by [CLEAR, ENDF]
content.push(new Uint8Array([0x22, 0x2D])); 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++) { for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
if (!ttContext.functionsUsed[j] || ttContext.functionsDefined[j]) { if (!ttContext.functionsUsed[j] || ttContext.functionsDefined[j]) {
continue; continue;
} }
undefinedFunctions.push(j);
if (j >= maxFunctionDefs) {
continue;
}
// function is used, but not defined // function is used, but not defined
if (j < 256) {
// creating empty one [PUSHB, function-id, FDEF, ENDF] // creating empty one [PUSHB, function-id, FDEF, ENDF]
content.push(new Uint8Array([0xB0, j, 0x2C, 0x2D])); 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) { if (content.length > 1) {
// concatenating the content items // concatenating the content items
var newLength = 0; var newLength = 0;
@ -3765,15 +3853,17 @@ var Font = (function FontClosure() {
var ttContext = { var ttContext = {
functionsDefined: [], functionsDefined: [],
functionsUsed: [], functionsUsed: [],
functionsStackDeltas: [],
tooComplexToFollowFunctions: false tooComplexToFollowFunctions: false
}; };
if (fpgm) {
sanitizeTTProgram(fpgm, ttContext);
}
if (prep) { if (prep) {
// collecting prep functions info first
sanitizeTTProgram(prep, ttContext); sanitizeTTProgram(prep, ttContext);
} }
if (fpgm) { if (fpgm) {
ttContext.defineMissingFunctions = true; addTTDummyFunctions(fpgm, ttContext, maxFunctionDefs);
sanitizeTTProgram(fpgm, ttContext);
} }
} }
@ -3843,12 +3933,17 @@ var Font = (function FontClosure() {
// Ensure the hmtx table contains the advance width and // Ensure the hmtx table contains the advance width and
// sidebearings information for numGlyphs in the maxp table // sidebearings information for numGlyphs in the maxp table
font.pos = (font.start || 0) + maxp.offset; 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 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); sanitizeMetrics(font, hhea, hmtx, numGlyphs);
sanitizeTTPrograms(fpgm, prep); sanitizeTTPrograms(fpgm, prep, maxFunctionDefs);
if (head) { if (head) {
sanitizeHead(head, numGlyphs, loca.length); sanitizeHead(head, numGlyphs, loca.length);