diff --git a/src/fonts.js b/src/fonts.js index 02c28a3c2..aa96be569 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -3341,7 +3341,8 @@ var Font = (function FontClosure() { } } - function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart) { + function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart, + hintsValid) { if (sourceEnd - sourceStart <= 12) { // glyph with data less than 12 is invalid one return 0; @@ -3361,8 +3362,10 @@ var Font = (function FontClosure() { j += 2; } // skipping instructions + var instructionsStart = j; var instructionsLength = (glyf[j] << 8) | glyf[j + 1]; j += 2 + instructionsLength; + var instructionsEnd = j; // validating flags var coordinatesLength = 0; for (var i = 0; i < flagsCount; i++) { @@ -3385,6 +3388,17 @@ var Font = (function FontClosure() { // not enough data for coordinates return 0; } + if (!hintsValid && instructionsLength > 0) { + dest.set(glyf.subarray(0, instructionsStart), destStart); + dest.set([0, 0], destStart + instructionsStart); + dest.set(glyf.subarray(instructionsEnd, glyphDataLength), + destStart + instructionsStart + 2); + glyphDataLength -= instructionsLength; + if (glyf.length - glyphDataLength > 3) { + glyphDataLength = (glyphDataLength + 3) & ~3; + } + return glyphDataLength; + } if (glyf.length - glyphDataLength > 3) { // truncating and aligning to 4 bytes the long glyph data glyphDataLength = (glyphDataLength + 3) & ~3; @@ -3441,7 +3455,7 @@ var Font = (function FontClosure() { } function sanitizeGlyphLocations(loca, glyf, numGlyphs, - isGlyphLocationsLong) { + isGlyphLocationsLong, hintsValid) { var itemSize, itemDecode, itemEncode; if (isGlyphLocationsLong) { itemSize = 4; @@ -3483,7 +3497,7 @@ var Font = (function FontClosure() { } var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset, - newGlyfData, writeOffset); + newGlyfData, writeOffset, hintsValid); writeOffset += newLength; itemEncode(locaData, j, writeOffset); startOffset = endOffset; @@ -3648,7 +3662,7 @@ var Font = (function FontClosure() { 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, -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, -999, 0, -2, -2, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -3801,33 +3815,22 @@ var Font = (function FontClosure() { 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++) { - if (!ttContext.functionsUsed[j] || ttContext.functionsDefined[j]) { - continue; - } - undefinedFunctions.push(j); - if (j >= maxFunctionDefs) { - continue; - } - // function is used, but not defined - if (j < 256) { - // creating empty one [PUSHB, function-id, FDEF, ENDF] - 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])); - } + function checkInvalidFunctions(ttContext, maxFunctionDefs) { + if (ttContext.tooComplexToFollowFunctions) { + return; + } + for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) { + if (j > maxFunctionDefs) { + warn('TT: invalid function id: ' + j); + ttContext.hintsValid = false; + return; } - if (undefinedFunctions.length > 0) { - warn('TT: undefined functions: ' + undefinedFunctions); + if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) { + warn('TT: undefined function: ' + j); + ttContext.hintsValid = false; + return; } } - foldTTTable(table, content); } function foldTTTable(table, content) { @@ -3854,7 +3857,8 @@ var Font = (function FontClosure() { functionsDefined: [], functionsUsed: [], functionsStackDeltas: [], - tooComplexToFollowFunctions: false + tooComplexToFollowFunctions: false, + hintsValid: true }; if (fpgm) { sanitizeTTProgram(fpgm, ttContext); @@ -3863,8 +3867,9 @@ var Font = (function FontClosure() { sanitizeTTProgram(prep, ttContext); } if (fpgm) { - addTTDummyFunctions(fpgm, ttContext, maxFunctionDefs); + checkInvalidFunctions(ttContext, maxFunctionDefs); } + return ttContext.hintsValid; } // Check that required tables are present @@ -3916,6 +3921,25 @@ var Font = (function FontClosure() { tables.push(table); } + // Ensure the hmtx table contains the advance width and + // sidebearings information for numGlyphs in the maxp table + font.pos = (font.start || 0) + maxp.offset; + var version = int32(font.getBytes(4)); + var numGlyphs = int16(font.getBytes(2)); + var maxFunctionDefs = 0; + if (version >= 0x00010000 && maxp.length >= 22) { + font.pos += 14; + var maxFunctionDefs = int16(font.getBytes(2)); + } + + var hintsValid = sanitizeTTPrograms(fpgm, prep, maxFunctionDefs); + if (!hintsValid) { + tables.splice(tables.indexOf(fpgm), 1); + fpgm = null; + tables.splice(tables.indexOf(prep), 1); + prep = null; + } + var numTables = tables.length + requiredTables.length; // header and new offsets. Table entry information is appended to the @@ -3930,28 +3954,16 @@ var Font = (function FontClosure() { // of missing tables createOpenTypeHeader(header.version, ttf, numTables); - // Ensure the hmtx table contains the advance width and - // sidebearings information for numGlyphs in the maxp table - font.pos = (font.start || 0) + maxp.offset; - var version = int32(font.getBytes(4)); - 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); - sanitizeTTPrograms(fpgm, prep, maxFunctionDefs); - if (head) { sanitizeHead(head, numGlyphs, loca.length); } var isGlyphLocationsLong = int16([head.data[50], head.data[51]]); if (head && loca && glyf) { - sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong); + sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong, + hintsValid); } var emptyGlyphIds = []; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 0dd45793f..3872774e3 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -48,3 +48,4 @@ !noembed-sjis.pdf !vertical.pdf !issue2099-1.pdf +!issue2956.pdf diff --git a/test/pdfs/issue2956.pdf b/test/pdfs/issue2956.pdf new file mode 100644 index 000000000..8865e9960 Binary files /dev/null and b/test/pdfs/issue2956.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 9464cba5f..fb365c296 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -1120,6 +1120,12 @@ "rounds": 1, "type": "eq" }, + { "id": "issue2956", + "file": "pdfs/issue2956.pdf", + "md5": "d8f68cbbb4bf54cde9f7f878acb6d7cd", + "rounds": 1, + "type": "eq" + }, { "id": "issue2177-eq", "file": "pdfs/issue2177.pdf", "md5": "48a808278bf31de8414c4e03ecd0900a",