Merge pull request #3053 from vyv03354/fpgm
Improve TT font program parser
This commit is contained in:
commit
2896c48e29
131
src/fonts.js
131
src/fonts.js
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user