|
|
@ -3663,73 +3663,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++; |
|
|
@ -3739,26 +3802,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; |
|
|
@ -3781,15 +3869,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); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -3859,12 +3949,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); |
|
|
|