diff --git a/src/fonts.js b/src/fonts.js index 01e7a7c9f..e0890c6fa 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -11,6 +11,7 @@ var kMaxWaitForFontFace = 1000; // Unicode Private Use Area var kCmapGlyphOffset = 0xE000; var kSizeOfGlyphArea = 0x1900; +var kSymbolicFontGlyphOffset = 0xF000; // PDF Glyph Space Units are one Thousandth of a TextSpace Unit // except for Type 3 fonts @@ -1660,6 +1661,18 @@ var Font = (function FontClosure() { itemEncode(locaData, j, writeOffset); startOffset = endOffset; } + + if (writeOffset == 0) { + // glyf table cannot be empty -- redoing the glyf and loca tables + // to have single glyph with one point + var simpleGlyph = new Uint8Array( + [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]); + for (var i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) + itemEncode(locaData, j, simpleGlyph.length); + glyf.data = simpleGlyph; + return; + } + glyf.data = newGlyfData.subarray(0, writeOffset); } @@ -1887,8 +1900,8 @@ var Font = (function FontClosure() { break; } } - // if it is, replacing with meaningful toFontChar values - if (isIdentity) { + // if it is, replacing with meaningful toUnicode values + if (isIdentity && !this.isSymbolicFont) { var usedUnicodes = [], unassignedUnicodeItems = []; for (var i = 0, ii = glyphs.length; i < ii; i++) { var unicode = toFontChar[i + 1]; @@ -1941,6 +1954,16 @@ var Font = (function FontClosure() { } } + // Moving all symbolic font glyphs into 0xF000 - 0xF0FF range. + if (this.isSymbolicFont) { + for (var i = 0, ii = glyphs.length; i < ii; i++) { + var code = glyphs[i].unicode; + code = kSymbolicFontGlyphOffset | (code & 0xFF); + glyphs[i].unicode = toFontChar[i] = code; + } + this.useToFontChar = true; + } + // remove glyph references outside range of avaialable glyphs for (var i = 0, ii = ids.length; i < ii; i++) { if (ids[i] >= numGlyphs) @@ -3349,7 +3372,9 @@ var Type2CFF = (function Type2CFFClosure() { parse: function cff_parse() { var header = this.parseHeader(); var properties = this.properties; + var nameIndex = this.parseIndex(header.endPos); + this.sanitizeName(nameIndex); var dictIndex = this.parseIndex(nameIndex.endPos); if (dictIndex.length != 1) @@ -3718,6 +3743,39 @@ var Type2CFF = (function Type2CFFClosure() { } return dict; }, + sanitizeName: function cff_sanitizeName(nameIndex) { + // There should really only be one font, but loop to make sure. + for (var i = 0, ii = nameIndex.length; i < ii; ++i) { + var data = nameIndex.get(i).data; + var length = data.length; + if (length > 127) + warn('Font had name longer than 127 chars, will be rejected.'); + // Only certain chars are permitted in the font name. + for (var j = 0; j < length; ++j) { + var c = data[j]; + if (j === 0 && c === 0) + continue; + if (c < 33 || c > 126) { + data[j] = 95; + continue; + } + switch (c) { + case 91: // [ + case 93: // ] + case 40: // ( + case 41: // ) + case 123: // { + case 125: // } + case 60: // < + case 62: // > + case 47: // / + case 37: // % + data[j] = 95; + break; + } + } + } + }, getStrings: function cff_getStrings(stringIndex) { function bytesToString(bytesArray) { var str = ''; diff --git a/src/jpx.js b/src/jpx.js index a15c3db54..b420b04e1 100644 --- a/src/jpx.js +++ b/src/jpx.js @@ -1052,7 +1052,7 @@ var JpxImage = (function JpxImageClosure() { } r = 0; } - error('JPX error: Out of packets'); + throw 'Out of packets'; }; } function ResolutionLayerComponentPositionIterator(context) { @@ -1091,7 +1091,7 @@ var JpxImage = (function JpxImageClosure() { } l = 0; } - error('JPX error: Out of packets'); + throw 'Out of packets'; }; } function buildPackets(context) { @@ -1187,7 +1187,7 @@ var JpxImage = (function JpxImageClosure() { new ResolutionLayerComponentPositionIterator(context); break; default: - error('JPX error: Unsupported progression order ' + progressionOrder); + throw 'Unsupported progression order ' + progressionOrder; } } function parseTilePackets(context, data, offset, dataLength) { @@ -1553,6 +1553,7 @@ var JpxImage = (function JpxImageClosure() { } function JpxImage() { + this.failOnCorruptedImage = false; } JpxImage.prototype = { load: function jpxImageLoad(url) { @@ -1612,237 +1613,244 @@ var JpxImage = (function JpxImageClosure() { }, parseCodestream: function jpxImageParseCodestream(data, start, end) { var context = {}; - var position = start; - while (position < end) { - var code = readUint16(data, position); - position += 2; + try { + var position = start; + while (position < end) { + var code = readUint16(data, position); + position += 2; - var length = 0, j; - switch (code) { - case 0xFF4F: // Start of codestream (SOC) - context.mainHeader = true; - break; - case 0xFFD9: // End of codestream (EOC) - break; - case 0xFF51: // Image and tile size (SIZ) - length = readUint16(data, position); - var siz = {}; - siz.Xsiz = readUint32(data, position + 4); - siz.Ysiz = readUint32(data, position + 8); - siz.XOsiz = readUint32(data, position + 12); - siz.YOsiz = readUint32(data, position + 16); - siz.XTsiz = readUint32(data, position + 20); - siz.YTsiz = readUint32(data, position + 24); - siz.XTOsiz = readUint32(data, position + 28); - siz.YTOsiz = readUint32(data, position + 32); - var componentsCount = readUint16(data, position + 36); - siz.Csiz = componentsCount; - var components = []; - j = position + 38; - for (var i = 0; i < componentsCount; i++) { - var component = { - precision: (data[j] & 0x7F) + 1, - isSigned: !!(data[j] & 0x80), - XRsiz: data[j + 1], - YRsiz: data[j + 1] - }; - calculateComponentDimensions(component, siz); - components.push(component); - } - context.SIZ = siz; - context.components = components; - calculateTileGrids(context, components); - context.QCC = []; - context.COC = []; - break; - case 0xFF5C: // Quantization default (QCD) - length = readUint16(data, position); - var qcd = {}; - j = position + 2; - var sqcd = data[j++]; - var spqcdSize, scalarExpounded; - switch (sqcd & 0x1F) { - case 0: - spqcdSize = 8; - scalarExpounded = true; - break; - case 1: - spqcdSize = 16; - scalarExpounded = false; - break; - case 2: - spqcdSize = 16; - scalarExpounded = true; - break; - default: - error('JPX error: Invalid SQcd value ' + sqcd); - } - qcd.noQuantization = spqcdSize == 8; - qcd.scalarExpounded = scalarExpounded; - qcd.guardBits = sqcd >> 5; - var spqcds = []; - while (j < length + position) { - var spqcd = {}; - if (spqcdSize == 8) { - spqcd.epsilon = data[j++] >> 3; - spqcd.mu = 0; - } else { - spqcd.epsilon = data[j] >> 3; - spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; - j += 2; + var length = 0, j; + switch (code) { + case 0xFF4F: // Start of codestream (SOC) + context.mainHeader = true; + break; + case 0xFFD9: // End of codestream (EOC) + break; + case 0xFF51: // Image and tile size (SIZ) + length = readUint16(data, position); + var siz = {}; + siz.Xsiz = readUint32(data, position + 4); + siz.Ysiz = readUint32(data, position + 8); + siz.XOsiz = readUint32(data, position + 12); + siz.YOsiz = readUint32(data, position + 16); + siz.XTsiz = readUint32(data, position + 20); + siz.YTsiz = readUint32(data, position + 24); + siz.XTOsiz = readUint32(data, position + 28); + siz.YTOsiz = readUint32(data, position + 32); + var componentsCount = readUint16(data, position + 36); + siz.Csiz = componentsCount; + var components = []; + j = position + 38; + for (var i = 0; i < componentsCount; i++) { + var component = { + precision: (data[j] & 0x7F) + 1, + isSigned: !!(data[j] & 0x80), + XRsiz: data[j + 1], + YRsiz: data[j + 1] + }; + calculateComponentDimensions(component, siz); + components.push(component); } - spqcds.push(spqcd); - } - qcd.SPqcds = spqcds; - if (context.mainHeader) - context.QCD = qcd; - else { - context.currentTile.QCD = qcd; - context.currentTile.QCC = []; - } - break; - case 0xFF5D: // Quantization component (QCC) - length = readUint16(data, position); - var qcc = {}; - j = position + 2; - var cqcc; - if (context.SIZ.Csiz < 257) - cqcc = data[j++]; - else { - cqcc = readUint16(data, j); - j += 2; - } - var sqcd = data[j++]; - var spqcdSize, scalarExpounded; - switch (sqcd & 0x1F) { - case 0: - spqcdSize = 8; - scalarExpounded = true; - break; - case 1: - spqcdSize = 16; - scalarExpounded = false; - break; - case 2: - spqcdSize = 16; - scalarExpounded = true; - break; - default: - error('JPX error: Invalid SQcd value ' + sqcd); - } - qcc.noQuantization = spqcdSize == 8; - qcc.scalarExpounded = scalarExpounded; - qcc.guardBits = sqcd >> 5; - var spqcds = []; - while (j < length + position) { - var spqcd = {}; - if (spqcdSize == 8) { - spqcd.epsilon = data[j++] >> 3; - spqcd.mu = 0; - } else { - spqcd.epsilon = data[j] >> 3; - spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; - j += 2; + context.SIZ = siz; + context.components = components; + calculateTileGrids(context, components); + context.QCC = []; + context.COC = []; + break; + case 0xFF5C: // Quantization default (QCD) + length = readUint16(data, position); + var qcd = {}; + j = position + 2; + var sqcd = data[j++]; + var spqcdSize, scalarExpounded; + switch (sqcd & 0x1F) { + case 0: + spqcdSize = 8; + scalarExpounded = true; + break; + case 1: + spqcdSize = 16; + scalarExpounded = false; + break; + case 2: + spqcdSize = 16; + scalarExpounded = true; + break; + default: + throw 'Invalid SQcd value ' + sqcd; } - spqcds.push(spqcd); - } - qcc.SPqcds = spqcds; - if (context.mainHeader) - context.QCC[cqcc] = qcc; - else - context.currentTile.QCC[cqcc] = qcc; - break; - case 0xFF52: // Coding style default (COD) - length = readUint16(data, position); - var cod = {}; - j = position + 2; - var scod = data[j++]; - cod.entropyCoderWithCustomPrecincts = !!(scod & 1); - cod.sopMarkerUsed = !!(scod & 2); - cod.ephMarkerUsed = !!(scod & 4); - var codingStyle = {}; - cod.progressionOrder = data[j++]; - cod.layersCount = readUint16(data, j); - j += 2; - cod.multipleComponentTransform = data[j++]; - - cod.decompositionLevelsCount = data[j++]; - cod.xcb = (data[j++] & 0xF) + 2; - cod.ycb = (data[j++] & 0xF) + 2; - var blockStyle = data[j++]; - cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1); - cod.resetContextProbabilities = !!(blockStyle & 2); - cod.terminationOnEachCodingPass = !!(blockStyle & 4); - cod.verticalyStripe = !!(blockStyle & 8); - cod.predictableTermination = !!(blockStyle & 16); - cod.segmentationSymbolUsed = !!(blockStyle & 32); - cod.transformation = data[j++]; - if (cod.entropyCoderWithCustomPrecincts) { - var precinctsSizes = {}; + qcd.noQuantization = spqcdSize == 8; + qcd.scalarExpounded = scalarExpounded; + qcd.guardBits = sqcd >> 5; + var spqcds = []; while (j < length + position) { - var precinctsSize = data[j]; - precinctsSizes.push({ - PPx: precinctsSize & 0xF, - PPy: precinctsSize >> 4 - }); + var spqcd = {}; + if (spqcdSize == 8) { + spqcd.epsilon = data[j++] >> 3; + spqcd.mu = 0; + } else { + spqcd.epsilon = data[j] >> 3; + spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; + j += 2; + } + spqcds.push(spqcd); } - cod.precinctsSizes = precinctsSizes; - } + qcd.SPqcds = spqcds; + if (context.mainHeader) + context.QCD = qcd; + else { + context.currentTile.QCD = qcd; + context.currentTile.QCC = []; + } + break; + case 0xFF5D: // Quantization component (QCC) + length = readUint16(data, position); + var qcc = {}; + j = position + 2; + var cqcc; + if (context.SIZ.Csiz < 257) + cqcc = data[j++]; + else { + cqcc = readUint16(data, j); + j += 2; + } + var sqcd = data[j++]; + var spqcdSize, scalarExpounded; + switch (sqcd & 0x1F) { + case 0: + spqcdSize = 8; + scalarExpounded = true; + break; + case 1: + spqcdSize = 16; + scalarExpounded = false; + break; + case 2: + spqcdSize = 16; + scalarExpounded = true; + break; + default: + throw 'Invalid SQcd value ' + sqcd; + } + qcc.noQuantization = spqcdSize == 8; + qcc.scalarExpounded = scalarExpounded; + qcc.guardBits = sqcd >> 5; + var spqcds = []; + while (j < length + position) { + var spqcd = {}; + if (spqcdSize == 8) { + spqcd.epsilon = data[j++] >> 3; + spqcd.mu = 0; + } else { + spqcd.epsilon = data[j] >> 3; + spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; + j += 2; + } + spqcds.push(spqcd); + } + qcc.SPqcds = spqcds; + if (context.mainHeader) + context.QCC[cqcc] = qcc; + else + context.currentTile.QCC[cqcc] = qcc; + break; + case 0xFF52: // Coding style default (COD) + length = readUint16(data, position); + var cod = {}; + j = position + 2; + var scod = data[j++]; + cod.entropyCoderWithCustomPrecincts = !!(scod & 1); + cod.sopMarkerUsed = !!(scod & 2); + cod.ephMarkerUsed = !!(scod & 4); + var codingStyle = {}; + cod.progressionOrder = data[j++]; + cod.layersCount = readUint16(data, j); + j += 2; + cod.multipleComponentTransform = data[j++]; - if (cod.sopMarkerUsed || cod.ephMarkerUsed || - cod.selectiveArithmeticCodingBypass || - cod.resetContextProbabilities || - cod.terminationOnEachCodingPass || - cod.verticalyStripe || cod.predictableTermination || - cod.segmentationSymbolUsed) - error('JPX error: Unsupported COD options: ' + uneval(cod)); + cod.decompositionLevelsCount = data[j++]; + cod.xcb = (data[j++] & 0xF) + 2; + cod.ycb = (data[j++] & 0xF) + 2; + var blockStyle = data[j++]; + cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1); + cod.resetContextProbabilities = !!(blockStyle & 2); + cod.terminationOnEachCodingPass = !!(blockStyle & 4); + cod.verticalyStripe = !!(blockStyle & 8); + cod.predictableTermination = !!(blockStyle & 16); + cod.segmentationSymbolUsed = !!(blockStyle & 32); + cod.transformation = data[j++]; + if (cod.entropyCoderWithCustomPrecincts) { + var precinctsSizes = {}; + while (j < length + position) { + var precinctsSize = data[j]; + precinctsSizes.push({ + PPx: precinctsSize & 0xF, + PPy: precinctsSize >> 4 + }); + } + cod.precinctsSizes = precinctsSizes; + } - if (context.mainHeader) - context.COD = cod; - else { - context.currentTile.COD = cod; - context.currentTile.COC = []; - } - break; - case 0xFF90: // Start of tile-part (SOT) - length = readUint16(data, position); - var tile = {}; - tile.index = readUint16(data, position + 2); - tile.length = readUint32(data, position + 4); - tile.dataEnd = tile.length + position - 2; - tile.partIndex = data[position + 8]; - tile.partsCount = data[position + 9]; + if (cod.sopMarkerUsed || cod.ephMarkerUsed || + cod.selectiveArithmeticCodingBypass || + cod.resetContextProbabilities || + cod.terminationOnEachCodingPass || + cod.verticalyStripe || cod.predictableTermination || + cod.segmentationSymbolUsed) + throw 'Unsupported COD options: ' + uneval(cod); - context.mainHeader = false; - if (tile.partIndex == 0) { - // reset component specific settings - tile.COD = context.COD; - tile.COC = context.COC.slice(0); // clone of the global COC - tile.QCD = context.QCD; - tile.QCC = context.QCC.slice(0); // clone of the global COC - } - context.currentTile = tile; - break; - case 0xFF93: // Start of data (SOD) - var tile = context.currentTile; - if (tile.partIndex == 0) { - initializeTile(context, tile.index); - buildPackets(context); - } + if (context.mainHeader) + context.COD = cod; + else { + context.currentTile.COD = cod; + context.currentTile.COC = []; + } + break; + case 0xFF90: // Start of tile-part (SOT) + length = readUint16(data, position); + var tile = {}; + tile.index = readUint16(data, position + 2); + tile.length = readUint32(data, position + 4); + tile.dataEnd = tile.length + position - 2; + tile.partIndex = data[position + 8]; + tile.partsCount = data[position + 9]; - // moving to the end of the data - length = tile.dataEnd - position; + context.mainHeader = false; + if (tile.partIndex == 0) { + // reset component specific settings + tile.COD = context.COD; + tile.COC = context.COC.slice(0); // clone of the global COC + tile.QCD = context.QCD; + tile.QCC = context.QCC.slice(0); // clone of the global COC + } + context.currentTile = tile; + break; + case 0xFF93: // Start of data (SOD) + var tile = context.currentTile; + if (tile.partIndex == 0) { + initializeTile(context, tile.index); + buildPackets(context); + } - parseTilePackets(context, data, position, length); - break; - case 0xFF64: // Comment (COM) - length = readUint16(data, position); - // skipping content - break; - default: - error('JPX error: Unknown codestream code: ' + code.toString(16)); + // moving to the end of the data + length = tile.dataEnd - position; + + parseTilePackets(context, data, position, length); + break; + case 0xFF64: // Comment (COM) + length = readUint16(data, position); + // skipping content + break; + default: + throw 'Unknown codestream code: ' + code.toString(16); + } + position += length; } - position += length; + } catch (e) { + if (this.failOnCorruptedImage) + error('JPX error: ' + e); + else + warn('JPX error: ' + e + '. Trying to recover'); } this.tiles = transformComponents(context); this.width = context.SIZ.Xsiz - context.SIZ.XOsiz; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index e0926492b..9460cfbec 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -24,3 +24,4 @@ !type4psfunc.pdf !S2.pdf !zerowidthline.pdf +!issue925.pdf diff --git a/test/pdfs/issue1243.pdf.link b/test/pdfs/issue1243.pdf.link new file mode 100644 index 000000000..89187f785 --- /dev/null +++ b/test/pdfs/issue1243.pdf.link @@ -0,0 +1 @@ +http://www.nsa.gov/public_info/_files/nash_letters/nash_letters1.pdf diff --git a/test/pdfs/issue1257.pdf.link b/test/pdfs/issue1257.pdf.link new file mode 100644 index 000000000..5a8bdd15c --- /dev/null +++ b/test/pdfs/issue1257.pdf.link @@ -0,0 +1 @@ +http://hse-econ.fi/tervio/MediocritiesAndSuperstars.pdf diff --git a/test/pdfs/issue925.pdf b/test/pdfs/issue925.pdf new file mode 100755 index 000000000..16329cbef Binary files /dev/null and b/test/pdfs/issue925.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index e09f16da9..23a0f8ed9 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -460,6 +460,13 @@ "link": true, "type": "eq" }, + { "id": "issue925", + "file": "pdfs/issue925.pdf", + "md5": "f58fe943090aff89dcc8e771bc0db4c2", + "rounds": 1, + "link": true, + "type": "eq" + }, { "id": "issue1133", "file": "pdfs/issue1133.pdf", "md5": "d1b61580cb100e3df93d33703af1773a", @@ -480,5 +487,21 @@ "rounds": 1, "link": true, "type": "eq" + }, + { "id": "issue1243", + "file": "pdfs/issue1243.pdf", + "md5": "130c849b83513d5ac5e03c6421fc7489", + "rounds": 1, + "pageLimit": 2, + "link": true, + "type": "eq" + }, + { "id": "issue1257", + "file": "pdfs/issue1257.pdf", + "md5": "9111533826bc21ed774e8e01603a2f54", + "rounds": 1, + "pageLimit": 2, + "link": true, + "type": "eq" } ]