Skip to content

Commit b697001

Browse files
committed
Improve TT font program parser
1 parent 5ceac52 commit b697001

File tree

1 file changed

+138
-43
lines changed

1 file changed

+138
-43
lines changed

src/fonts.js

Lines changed: 138 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3663,80 +3663,144 @@ var Font = (function FontClosure() {
36633663
var TTOpsStackDeltas = [
36643664
0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5,
36653665
-1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1,
3666-
1, -1, -999, 0, 1, 0, 0, -2, 0, -1, -2, -1, -999, -999, -1, -1,
3666+
1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1,
36673667
0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -2, 0, -2, -2,
36683668
0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1,
36693669
-1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1,
3670-
-1, -1, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0,
3670+
-1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
36713671
-2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1,
3672-
-999, -2, -2, 0, 0, -1, -2, -2, 0, -999, 0, 0, 0, -1, -2];
3672+
-999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];
36733673
// 0xC0-DF == -1 and 0xE0-FF == -2
36743674

36753675
function sanitizeTTProgram(table, ttContext) {
36763676
var data = table.data;
36773677
var i = 0, n, lastEndf = 0, lastDeff = 0;
36783678
var stack = [];
3679+
var callstack = [];
3680+
var functionsCalled = [];
36793681
var tooComplexToFollowFunctions =
36803682
ttContext.tooComplexToFollowFunctions;
3683+
var inFDEF = false, ifLevel = 0, inELSE = 0;
36813684
for (var ii = data.length; i < ii;) {
36823685
var op = data[i++];
36833686
// The TrueType instruction set docs can be found at
36843687
// https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html
36853688
if (op === 0x40) { // NPUSHB - pushes n bytes
36863689
n = data[i++];
3687-
for (var j = 0; j < n; j++) {
3688-
stack.push(data[i++]);
3690+
if (inFDEF || inELSE) {
3691+
i += n;
3692+
} else {
3693+
for (var j = 0; j < n; j++) {
3694+
stack.push(data[i++]);
3695+
}
36893696
}
36903697
} else if (op === 0x41) { // NPUSHW - pushes n words
36913698
n = data[i++];
3692-
for (var j = 0; j < n; j++) {
3693-
var b = data[i++];
3694-
stack.push((b << 8) | data[i++]);
3699+
if (inFDEF || inELSE) {
3700+
i += n * 2;
3701+
} else {
3702+
for (var j = 0; j < n; j++) {
3703+
var b = data[i++];
3704+
stack.push((b << 8) | data[i++]);
3705+
}
36953706
}
36963707
} else if ((op & 0xF8) === 0xB0) { // PUSHB - pushes bytes
36973708
n = op - 0xB0 + 1;
3698-
for (var j = 0; j < n; j++) {
3699-
stack.push(data[i++]);
3709+
if (inFDEF || inELSE) {
3710+
i += n;
3711+
} else {
3712+
for (var j = 0; j < n; j++) {
3713+
stack.push(data[i++]);
3714+
}
37003715
}
37013716
} else if ((op & 0xF8) === 0xB8) { // PUSHW - pushes words
37023717
n = op - 0xB8 + 1;
3703-
for (var j = 0; j < n; j++) {
3704-
var b = data[i++];
3705-
stack.push((b << 8) | data[i++]);
3718+
if (inFDEF || inELSE) {
3719+
i += n * 2;
3720+
} else {
3721+
for (var j = 0; j < n; j++) {
3722+
var b = data[i++];
3723+
stack.push((b << 8) | data[i++]);
3724+
}
37063725
}
37073726
} else if (op === 0x2B && !tooComplexToFollowFunctions) { // CALL
3708-
// collecting inforamtion about which functions are used
3709-
var funcId = stack[stack.length - 1];
3710-
ttContext.functionsUsed[funcId] = true;
3711-
if (i >= 2 && data[i - 2] === 0x2B) {
3712-
// all data in stack, calls are performed in sequence
3713-
tooComplexToFollowFunctions = true;
3727+
if (!inFDEF && !inELSE) {
3728+
// collecting inforamtion about which functions are used
3729+
var funcId = stack[stack.length - 1];
3730+
ttContext.functionsUsed[funcId] = true;
3731+
if (funcId in ttContext.functionsStackDeltas) {
3732+
stack.length += ttContext.functionsStackDeltas[funcId];
3733+
} else if (funcId in ttContext.functionsDefined &&
3734+
functionsCalled.indexOf(funcId) < 0) {
3735+
callstack.push({data: data, i: i, stackTop: stack.length - 1});
3736+
functionsCalled.push(funcId);
3737+
var pc = ttContext.functionsDefined[funcId];
3738+
data = pc.data;
3739+
i = pc.i;
3740+
}
37143741
}
37153742
} else if (op === 0x2C && !tooComplexToFollowFunctions) { // FDEF
3716-
// collecting inforamtion about which functions are defined
3717-
lastDeff = i;
3718-
var funcId = stack[stack.length - 1];
3719-
ttContext.functionsDefined[funcId] = true;
3720-
if (i >= 2 && data[i - 2] === 0x2D) {
3721-
// all function ids in stack, FDEF/ENDF perfomed in sequence
3743+
if (inFDEF || inELSE) {
3744+
warn('TT: nested FDEFs not allowed');
37223745
tooComplexToFollowFunctions = true;
37233746
}
3747+
inFDEF = true;
3748+
// collecting inforamtion about which functions are defined
3749+
lastDeff = i;
3750+
var funcId = stack.pop();
3751+
ttContext.functionsDefined[funcId] = {data: data, i: i};
37243752
} else if (op === 0x2D) { // ENDF - end of function
3725-
lastEndf = i;
3753+
if (inFDEF) {
3754+
inFDEF = false;
3755+
lastEndf = i;
3756+
} else {
3757+
var pc = callstack.pop();
3758+
var funcId = functionsCalled.pop();
3759+
data = pc.data;
3760+
i = pc.i;
3761+
ttContext.functionsStackDeltas[funcId] =
3762+
stack.length - pc.stackTop;
3763+
}
37263764
} else if (op === 0x89) { // IDEF - instruction definition
3765+
if (inFDEF || inELSE) {
3766+
warn('TT: nested IDEFs not allowed');
3767+
tooComplexToFollowFunctions = true;
3768+
}
3769+
inFDEF = true;
37273770
// recording it as a function to track ENDF
37283771
lastDeff = i;
3772+
} else if (op === 0x58) { // IF
3773+
++ifLevel;
3774+
} else if (op === 0x1B) { // ELSE
3775+
inELSE = ifLevel;
3776+
} else if (op === 0x59) { // EIF
3777+
if (inELSE === ifLevel) {
3778+
inELSE = 0;
3779+
}
3780+
--ifLevel;
3781+
} else if (op === 0x1C) { // JMPR
3782+
var offset = stack[stack.length - 1];
3783+
// only jumping forward to prevent infinite loop
3784+
if (offset > 0) { i += offset - 1; }
37293785
}
37303786
// Adjusting stack not extactly, but just enough to get function id
3731-
var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
3732-
op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
3733-
while (stackDelta < 0 && stack.length > 0) {
3734-
stack.pop();
3735-
stackDelta++;
3736-
}
3737-
while (stackDelta > 0) {
3738-
stack.push(NaN); // pushing any number into stack
3739-
stackDelta--;
3787+
if (!inFDEF && !inELSE) {
3788+
var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
3789+
op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
3790+
if (op >= 0x71 && op <= 0x75) {
3791+
n = stack.pop();
3792+
if (n === n) {
3793+
stackDelta = -n * 2;
3794+
}
3795+
}
3796+
while (stackDelta < 0 && stack.length > 0) {
3797+
stack.pop();
3798+
stackDelta++;
3799+
}
3800+
while (stackDelta > 0) {
3801+
stack.push(NaN); // pushing any number into stack
3802+
stackDelta--;
3803+
}
37403804
}
37413805
}
37423806
ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
@@ -3745,20 +3809,44 @@ var Font = (function FontClosure() {
37453809
content.push(new Uint8Array(i - data.length));
37463810
}
37473811
if (lastDeff > lastEndf) {
3812+
warn('TT: complementing a missing function tail');
37483813
// new function definition started, but not finished
37493814
// complete function by [CLEAR, ENDF]
37503815
content.push(new Uint8Array([0x22, 0x2D]));
37513816
}
3752-
if (ttContext.defineMissingFunctions && !tooComplexToFollowFunctions) {
3817+
foldTTTable(table, content);
3818+
}
3819+
3820+
function addTTDummyFunctions(table, ttContext, maxFunctionDefs) {
3821+
var content = [table.data];
3822+
if (!ttContext.tooComplexToFollowFunctions) {
3823+
var undefinedFunctions = [];
37533824
for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
37543825
if (!ttContext.functionsUsed[j] || ttContext.functionsDefined[j]) {
37553826
continue;
37563827
}
3828+
undefinedFunctions.push(j);
3829+
if (j >= maxFunctionDefs) {
3830+
continue;
3831+
}
37573832
// function is used, but not defined
3758-
// creating empty one [PUSHB, function-id, FDEF, ENDF]
3759-
content.push(new Uint8Array([0xB0, j, 0x2C, 0x2D]));
3833+
if (j < 256) {
3834+
// creating empty one [PUSHB, function-id, FDEF, ENDF]
3835+
content.push(new Uint8Array([0xB0, j, 0x2C, 0x2D]));
3836+
} else {
3837+
// creating empty one [PUSHW, function-id, FDEF, ENDF]
3838+
content.push(
3839+
new Uint8Array([0xB8, j >> 8, j & 255, 0x2C, 0x2D]));
3840+
}
3841+
}
3842+
if (undefinedFunctions.length > 0) {
3843+
warn('TT: undefined functions: ' + undefinedFunctions);
37603844
}
37613845
}
3846+
foldTTTable(table, content);
3847+
}
3848+
3849+
function foldTTTable(table, content) {
37623850
if (content.length > 1) {
37633851
// concatenating the content items
37643852
var newLength = 0;
@@ -3781,15 +3869,17 @@ var Font = (function FontClosure() {
37813869
var ttContext = {
37823870
functionsDefined: [],
37833871
functionsUsed: [],
3872+
functionsStackDeltas: [],
37843873
tooComplexToFollowFunctions: false
37853874
};
3875+
if (fpgm) {
3876+
sanitizeTTProgram(fpgm, ttContext);
3877+
}
37863878
if (prep) {
3787-
// collecting prep functions info first
37883879
sanitizeTTProgram(prep, ttContext);
37893880
}
37903881
if (fpgm) {
3791-
ttContext.defineMissingFunctions = true;
3792-
sanitizeTTProgram(fpgm, ttContext);
3882+
addTTDummyFunctions(fpgm, ttContext, maxFunctionDefs);
37933883
}
37943884
}
37953885

@@ -3859,12 +3949,17 @@ var Font = (function FontClosure() {
38593949
// Ensure the hmtx table contains the advance width and
38603950
// sidebearings information for numGlyphs in the maxp table
38613951
font.pos = (font.start || 0) + maxp.offset;
3862-
var version = int16(font.getBytes(4));
3952+
var version = int32(font.getBytes(4));
38633953
var numGlyphs = int16(font.getBytes(2));
3954+
var maxFunctionDefs = 0;
3955+
if (version >= 0x00010000 && maxp.length >= 22) {
3956+
font.pos += 14;
3957+
var maxFunctionDefs = int16(font.getBytes(2));
3958+
}
38643959

38653960
sanitizeMetrics(font, hhea, hmtx, numGlyphs);
38663961

3867-
sanitizeTTPrograms(fpgm, prep);
3962+
sanitizeTTPrograms(fpgm, prep, maxFunctionDefs);
38683963

38693964
if (head) {
38703965
sanitizeHead(head, numGlyphs, loca.length);

0 commit comments

Comments
 (0)