Skip to content

Commit 2896c48

Browse files
committed
Merge pull request mozilla#3053 from vyv03354/fpgm
Improve TT font program parser
2 parents 1e53ab1 + b697001 commit 2896c48

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
@@ -3647,80 +3647,144 @@ var Font = (function FontClosure() {
36473647
var TTOpsStackDeltas = [
36483648
0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5,
36493649
-1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1,
3650-
1, -1, -999, 0, 1, 0, 0, -2, 0, -1, -2, -1, -999, -999, -1, -1,
3650+
1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1,
36513651
0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -2, 0, -2, -2,
36523652
0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1,
36533653
-1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1,
3654-
-1, -1, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0,
3654+
-1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
36553655
-2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1,
3656-
-999, -2, -2, 0, 0, -1, -2, -2, 0, -999, 0, 0, 0, -1, -2];
3656+
-999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];
36573657
// 0xC0-DF == -1 and 0xE0-FF == -2
36583658

36593659
function sanitizeTTProgram(table, ttContext) {
36603660
var data = table.data;
36613661
var i = 0, n, lastEndf = 0, lastDeff = 0;
36623662
var stack = [];
3663+
var callstack = [];
3664+
var functionsCalled = [];
36633665
var tooComplexToFollowFunctions =
36643666
ttContext.tooComplexToFollowFunctions;
3667+
var inFDEF = false, ifLevel = 0, inELSE = 0;
36653668
for (var ii = data.length; i < ii;) {
36663669
var op = data[i++];
36673670
// The TrueType instruction set docs can be found at
36683671
// https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html
36693672
if (op === 0x40) { // NPUSHB - pushes n bytes
36703673
n = data[i++];
3671-
for (var j = 0; j < n; j++) {
3672-
stack.push(data[i++]);
3674+
if (inFDEF || inELSE) {
3675+
i += n;
3676+
} else {
3677+
for (var j = 0; j < n; j++) {
3678+
stack.push(data[i++]);
3679+
}
36733680
}
36743681
} else if (op === 0x41) { // NPUSHW - pushes n words
36753682
n = data[i++];
3676-
for (var j = 0; j < n; j++) {
3677-
var b = data[i++];
3678-
stack.push((b << 8) | data[i++]);
3683+
if (inFDEF || inELSE) {
3684+
i += n * 2;
3685+
} else {
3686+
for (var j = 0; j < n; j++) {
3687+
var b = data[i++];
3688+
stack.push((b << 8) | data[i++]);
3689+
}
36793690
}
36803691
} else if ((op & 0xF8) === 0xB0) { // PUSHB - pushes bytes
36813692
n = op - 0xB0 + 1;
3682-
for (var j = 0; j < n; j++) {
3683-
stack.push(data[i++]);
3693+
if (inFDEF || inELSE) {
3694+
i += n;
3695+
} else {
3696+
for (var j = 0; j < n; j++) {
3697+
stack.push(data[i++]);
3698+
}
36843699
}
36853700
} else if ((op & 0xF8) === 0xB8) { // PUSHW - pushes words
36863701
n = op - 0xB8 + 1;
3687-
for (var j = 0; j < n; j++) {
3688-
var b = data[i++];
3689-
stack.push((b << 8) | data[i++]);
3702+
if (inFDEF || inELSE) {
3703+
i += n * 2;
3704+
} else {
3705+
for (var j = 0; j < n; j++) {
3706+
var b = data[i++];
3707+
stack.push((b << 8) | data[i++]);
3708+
}
36903709
}
36913710
} else if (op === 0x2B && !tooComplexToFollowFunctions) { // CALL
3692-
// collecting inforamtion about which functions are used
3693-
var funcId = stack[stack.length - 1];
3694-
ttContext.functionsUsed[funcId] = true;
3695-
if (i >= 2 && data[i - 2] === 0x2B) {
3696-
// all data in stack, calls are performed in sequence
3697-
tooComplexToFollowFunctions = true;
3711+
if (!inFDEF && !inELSE) {
3712+
// collecting inforamtion about which functions are used
3713+
var funcId = stack[stack.length - 1];
3714+
ttContext.functionsUsed[funcId] = true;
3715+
if (funcId in ttContext.functionsStackDeltas) {
3716+
stack.length += ttContext.functionsStackDeltas[funcId];
3717+
} else if (funcId in ttContext.functionsDefined &&
3718+
functionsCalled.indexOf(funcId) < 0) {
3719+
callstack.push({data: data, i: i, stackTop: stack.length - 1});
3720+
functionsCalled.push(funcId);
3721+
var pc = ttContext.functionsDefined[funcId];
3722+
data = pc.data;
3723+
i = pc.i;
3724+
}
36983725
}
36993726
} else if (op === 0x2C && !tooComplexToFollowFunctions) { // FDEF
3700-
// collecting inforamtion about which functions are defined
3701-
lastDeff = i;
3702-
var funcId = stack[stack.length - 1];
3703-
ttContext.functionsDefined[funcId] = true;
3704-
if (i >= 2 && data[i - 2] === 0x2D) {
3705-
// all function ids in stack, FDEF/ENDF perfomed in sequence
3727+
if (inFDEF || inELSE) {
3728+
warn('TT: nested FDEFs not allowed');
37063729
tooComplexToFollowFunctions = true;
37073730
}
3731+
inFDEF = true;
3732+
// collecting inforamtion about which functions are defined
3733+
lastDeff = i;
3734+
var funcId = stack.pop();
3735+
ttContext.functionsDefined[funcId] = {data: data, i: i};
37083736
} else if (op === 0x2D) { // ENDF - end of function
3709-
lastEndf = i;
3737+
if (inFDEF) {
3738+
inFDEF = false;
3739+
lastEndf = i;
3740+
} else {
3741+
var pc = callstack.pop();
3742+
var funcId = functionsCalled.pop();
3743+
data = pc.data;
3744+
i = pc.i;
3745+
ttContext.functionsStackDeltas[funcId] =
3746+
stack.length - pc.stackTop;
3747+
}
37103748
} else if (op === 0x89) { // IDEF - instruction definition
3749+
if (inFDEF || inELSE) {
3750+
warn('TT: nested IDEFs not allowed');
3751+
tooComplexToFollowFunctions = true;
3752+
}
3753+
inFDEF = true;
37113754
// recording it as a function to track ENDF
37123755
lastDeff = i;
3756+
} else if (op === 0x58) { // IF
3757+
++ifLevel;
3758+
} else if (op === 0x1B) { // ELSE
3759+
inELSE = ifLevel;
3760+
} else if (op === 0x59) { // EIF
3761+
if (inELSE === ifLevel) {
3762+
inELSE = 0;
3763+
}
3764+
--ifLevel;
3765+
} else if (op === 0x1C) { // JMPR
3766+
var offset = stack[stack.length - 1];
3767+
// only jumping forward to prevent infinite loop
3768+
if (offset > 0) { i += offset - 1; }
37133769
}
37143770
// Adjusting stack not extactly, but just enough to get function id
3715-
var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
3716-
op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
3717-
while (stackDelta < 0 && stack.length > 0) {
3718-
stack.pop();
3719-
stackDelta++;
3720-
}
3721-
while (stackDelta > 0) {
3722-
stack.push(NaN); // pushing any number into stack
3723-
stackDelta--;
3771+
if (!inFDEF && !inELSE) {
3772+
var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
3773+
op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
3774+
if (op >= 0x71 && op <= 0x75) {
3775+
n = stack.pop();
3776+
if (n === n) {
3777+
stackDelta = -n * 2;
3778+
}
3779+
}
3780+
while (stackDelta < 0 && stack.length > 0) {
3781+
stack.pop();
3782+
stackDelta++;
3783+
}
3784+
while (stackDelta > 0) {
3785+
stack.push(NaN); // pushing any number into stack
3786+
stackDelta--;
3787+
}
37243788
}
37253789
}
37263790
ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
@@ -3729,20 +3793,44 @@ var Font = (function FontClosure() {
37293793
content.push(new Uint8Array(i - data.length));
37303794
}
37313795
if (lastDeff > lastEndf) {
3796+
warn('TT: complementing a missing function tail');
37323797
// new function definition started, but not finished
37333798
// complete function by [CLEAR, ENDF]
37343799
content.push(new Uint8Array([0x22, 0x2D]));
37353800
}
3736-
if (ttContext.defineMissingFunctions && !tooComplexToFollowFunctions) {
3801+
foldTTTable(table, content);
3802+
}
3803+
3804+
function addTTDummyFunctions(table, ttContext, maxFunctionDefs) {
3805+
var content = [table.data];
3806+
if (!ttContext.tooComplexToFollowFunctions) {
3807+
var undefinedFunctions = [];
37373808
for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
37383809
if (!ttContext.functionsUsed[j] || ttContext.functionsDefined[j]) {
37393810
continue;
37403811
}
3812+
undefinedFunctions.push(j);
3813+
if (j >= maxFunctionDefs) {
3814+
continue;
3815+
}
37413816
// function is used, but not defined
3742-
// creating empty one [PUSHB, function-id, FDEF, ENDF]
3743-
content.push(new Uint8Array([0xB0, j, 0x2C, 0x2D]));
3817+
if (j < 256) {
3818+
// creating empty one [PUSHB, function-id, FDEF, ENDF]
3819+
content.push(new Uint8Array([0xB0, j, 0x2C, 0x2D]));
3820+
} else {
3821+
// creating empty one [PUSHW, function-id, FDEF, ENDF]
3822+
content.push(
3823+
new Uint8Array([0xB8, j >> 8, j & 255, 0x2C, 0x2D]));
3824+
}
3825+
}
3826+
if (undefinedFunctions.length > 0) {
3827+
warn('TT: undefined functions: ' + undefinedFunctions);
37443828
}
37453829
}
3830+
foldTTTable(table, content);
3831+
}
3832+
3833+
function foldTTTable(table, content) {
37463834
if (content.length > 1) {
37473835
// concatenating the content items
37483836
var newLength = 0;
@@ -3765,15 +3853,17 @@ var Font = (function FontClosure() {
37653853
var ttContext = {
37663854
functionsDefined: [],
37673855
functionsUsed: [],
3856+
functionsStackDeltas: [],
37683857
tooComplexToFollowFunctions: false
37693858
};
3859+
if (fpgm) {
3860+
sanitizeTTProgram(fpgm, ttContext);
3861+
}
37703862
if (prep) {
3771-
// collecting prep functions info first
37723863
sanitizeTTProgram(prep, ttContext);
37733864
}
37743865
if (fpgm) {
3775-
ttContext.defineMissingFunctions = true;
3776-
sanitizeTTProgram(fpgm, ttContext);
3866+
addTTDummyFunctions(fpgm, ttContext, maxFunctionDefs);
37773867
}
37783868
}
37793869

@@ -3843,12 +3933,17 @@ var Font = (function FontClosure() {
38433933
// Ensure the hmtx table contains the advance width and
38443934
// sidebearings information for numGlyphs in the maxp table
38453935
font.pos = (font.start || 0) + maxp.offset;
3846-
var version = int16(font.getBytes(4));
3936+
var version = int32(font.getBytes(4));
38473937
var numGlyphs = int16(font.getBytes(2));
3938+
var maxFunctionDefs = 0;
3939+
if (version >= 0x00010000 && maxp.length >= 22) {
3940+
font.pos += 14;
3941+
var maxFunctionDefs = int16(font.getBytes(2));
3942+
}
38483943

38493944
sanitizeMetrics(font, hhea, hmtx, numGlyphs);
38503945

3851-
sanitizeTTPrograms(fpgm, prep);
3946+
sanitizeTTPrograms(fpgm, prep, maxFunctionDefs);
38523947

38533948
if (head) {
38543949
sanitizeHead(head, numGlyphs, loca.length);

0 commit comments

Comments
 (0)