diff --git a/PDFFont.js b/PDFFont.js index 58c61e051..fd7e0d86f 100644 --- a/PDFFont.js +++ b/PDFFont.js @@ -581,11 +581,213 @@ var FontsUtils = { */ var TrueType = function(aFile) { var header = this._readOpenTypeHeader(aFile); - this.data = aFile; + var numTables = header.numTables; + + // Check that required tables are present + var requiredTables = [ + "OS/2", + "cmap", + "head", + "hhea", + "hmtx", + "maxp", + "name", + "post" + ]; + + var tables = []; + for (var i = 0; i < numTables; i++) { + var table = this._readTableEntry(aFile); + var index = requiredTables.indexOf(table.tag); + if (index != -1) + requiredTables.splice(index, 1); + + tables.push(table); + } + tables.sort(function(a, b) { + return a.tag > b.tag; + }); + + // If any tables are still in the array this means some required tables are + // missing, which means that we need to rebuild the font in order to pass + // the sanitizer. + if (requiredTables.length && requiredTables[0] == "OS/2") { + OS2 = [ + 0x00, 0x03, // version + 0x02, 0x24, // xAvgCharWidth + 0x01, 0xF4, // usWeightClass + 0x00, 0x05, // usWidthClass + 0x00, 0x00, // fstype + 0x02, 0x8A, // ySubscriptXSize + 0x02, 0xBB, // ySubscriptYSize + 0x00, 0x00, // ySubscriptXOffset + 0x00, 0x8C, // ySubscriptYOffset + 0x02, 0x8A, // ySuperScriptXSize + 0x02, 0xBB, // ySuperScriptYSize + 0x00, 0x00, // ySuperScriptXOffset + 0x01, 0xDF, // ySuperScriptYOffset + 0x00, 0x31, // yStrikeOutSize + 0x01, 0x02, // yStrikeOutPosition + 0x00, 0x00, // sFamilyClass + 0x02, 0x00, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Panose + 0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 0-31) + 0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 32-63) + 0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 64-95) + 0xFF, 0xFF, 0xFF, 0xFF, // ulUnicodeRange1 (Bits 96-127) + 0x2A, 0x32, 0x31, 0x2A, // achVendID + 0x00, 0x20, // fsSelection + 0x00, 0x2D, // usFirstCharIndex + 0x00, 0x7A, // usLastCharIndex + 0x00, 0x03, // sTypoAscender + 0x00, 0x20, // sTypeDescender + 0x00, 0x38, // sTypoLineGap + 0x00, 0x5A, // usWinAscent + 0x02, 0xB4, // usWinDescent + 0x00, 0xCE, 0x00, 0x00, // ulCodePageRange1 (Bits 0-31) + 0x00, 0x01, 0x00, 0x00, // ulCodePageRange2 (Bits 32-63) + 0x00, 0x00, // sxHeight + 0x00, 0x00, // sCapHeight + 0x00, 0x01, // usDefaultChar + 0x00, 0xCD, // usBreakChar + 0x00, 0x02 // usMaxContext + ]; + + // Create a new file to hold the new version of our truetype with a new + // header and new offsets + var stream = aFile.stream || aFile; + var ttf = new Uint8Array(stream.length + 16 + OS2.length); + + // The new numbers of tables will be the last one plus the num of missing + // tables + var numTables = header.numTables + 1; + + // The offsets object holds at the same time a representation of where + // to write the table entry information about a table and another offset + // representing the offset where to draw the actual data of a particular + // table + var offsets = { + currentOffset: 0, + virtualOffset: numTables * (4 * 4) + }; + + // Write the sfnt header with one more table + this._createOpenTypeHeader(ttf, offsets, numTables); + + // Insert the missing table + tables.unshift({ + tag: "OS/2", + data: OS2 + }); + + // rewrite the tables but tweak offsets + for (var i = 0; i < tables.length; i++) { + var table = tables[i]; + var data = []; + + var tableData = table.data; + for (var j = 0; j < tableData.length; j++) + data.push(tableData[j]); + this._createTableEntry(ttf, offsets, table.tag, data); + } + + // Add the table datas + for (var i = 0; i < tables.length; i++) { + var table = tables[i]; + var tableData = table.data; + ttf.set(tableData, offsets.currentOffset); + offsets.currentOffset += tableData.length; + + if (0) { + var data = []; + for (var j = 0; j < tableData.length; j++) + d.push(tableData[j]); + log("data for table: " + table.tag + ": " + data); + } + + // 4-byte aligned data + while (offsets.currentOffset & 3) + offsets.currentOffset++; + } + + var fontData = []; + for (var i = 0; i < ttf.length; i++) + fontData.push(ttf[i]); + + this.data = ttf; + //writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".ttf"); + return; + } else if (requiredTables.lenght) { + error("Table " + requiredTables[0] + " is missing from the TruType font"); + } else { + this.data = aFile; + } }; TrueType.prototype = { - _readOpenTypeHeader: function(aFile) { + _createOpenTypeHeader: function tt_createOpenTypeHeader(aFile, aOffsets, aNumTables) { + // sfnt version (4 bytes) + // XXX if we want to merge this function and the one from the Font class + // XXX this need to be adapted + var version = [0x00, 0x01, 0x00, 0X00]; + + // numTables (2 bytes) + var numTables = aNumTables; + + // searchRange (2 bytes) + var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables); + var searchRange = tablesMaxPower2 * 16; + + // entrySelector (2 bytes) + var entrySelector = Math.log(tablesMaxPower2) / Math.log(2); + + // rangeShift (2 bytes) + var rangeShift = numTables * 16 - searchRange; + + var header = [].concat(version, + FontsUtils.integerToBytes(numTables, 2), + FontsUtils.integerToBytes(searchRange, 2), + FontsUtils.integerToBytes(entrySelector, 2), + FontsUtils.integerToBytes(rangeShift, 2)); + aFile.set(header, aOffsets.currentOffset); + aOffsets.currentOffset += header.length; + aOffsets.virtualOffset += header.length; + }, + + _createTableEntry: function font_createTableEntry(aFile, aOffsets, aTag, aData) { + // tag + var tag = [ + aTag.charCodeAt(0), + aTag.charCodeAt(1), + aTag.charCodeAt(2), + aTag.charCodeAt(3) + ]; + + // Per spec tables must be 4-bytes align so add some 0x00 if needed + while (aData.length & 3) + aData.push(0x00); + + while (aOffsets.virtualOffset & 3) + aOffsets.virtualOffset++; + + // offset + var offset = aOffsets.virtualOffset; + + // length + var length = aData.length; + + // checksum + var checksum = FontsUtils.bytesToInteger(tag) + offset + length; + + var tableEntry = [].concat(tag, + FontsUtils.integerToBytes(checksum, 4), + FontsUtils.integerToBytes(offset, 4), + FontsUtils.integerToBytes(length, 4)); + aFile.set(tableEntry, aOffsets.currentOffset); + aOffsets.currentOffset += tableEntry.length; + aOffsets.virtualOffset += aData.length; + }, + + _readOpenTypeHeader: function tt_readOpenTypeHeader(aFile) { return { version: aFile.getBytes(4), numTables: FontsUtils.bytesToInteger(aFile.getBytes(2)), @@ -593,6 +795,33 @@ TrueType.prototype = { entrySelector: FontsUtils.bytesToInteger(aFile.getBytes(2)), rangeShift: FontsUtils.bytesToInteger(aFile.getBytes(2)) } + }, + + _readTableEntry: function tt_readTableEntry(aFile) { + // tag + var tag = aFile.getBytes(4); + tag = String.fromCharCode(tag[0]) + + String.fromCharCode(tag[1]) + + String.fromCharCode(tag[2]) + + String.fromCharCode(tag[3]); + + var checksum = FontsUtils.bytesToInteger(aFile.getBytes(4)); + var offset = FontsUtils.bytesToInteger(aFile.getBytes(4)); + var length = FontsUtils.bytesToInteger(aFile.getBytes(4)); + + // Read the table associated data + var currentPosition = aFile.pos; + aFile.pos = aFile.start + offset; + var data = aFile.getBytes(length); + aFile.pos = currentPosition; + + return { + tag: tag, + checksum: checksum, + length: offset, + offset: length, + data: data + } } }; diff --git a/pdf.js b/pdf.js index afcb39fff..f46362f7f 100644 --- a/pdf.js +++ b/pdf.js @@ -52,7 +52,7 @@ var Stream = (function() { this.bytes = new Uint8Array(arrayBuffer); this.start = start || 0; this.pos = this.start; - this.end = (start + length) || arrayBuffer.byteLength; + this.end = (start + length) || this.bytes.byteLength; this.dict = dict; } @@ -1411,11 +1411,13 @@ var Page = (function() { var resources = xref.fetchIfRef(this.resources); var fontsDict = new Dict(); - // Get the fonts use on the page + // Get the fonts use on the page if any var fontResources = resources.get("Font"); - fontResources.forEach(function(fontKey, fontData) { - fontsDict.set(fontKey, xref.fetch(fontData)) - }); + if (IsDict(fontResources)) { + fontResources.forEach(function(fontKey, fontData) { + fontsDict.set(fontKey, xref.fetch(fontData)) + }); + } // Get the fonts use on xobjects of the page if any var xobjs = xref.fetchIfRef(resources.get("XObject")); @@ -1864,7 +1866,11 @@ var CanvasGraphics = (function() { this.current.leading = leading; }, setFont: function(fontRef, size) { - var font = this.res.get("Font").get(fontRef.name); + var font = this.res.get("Font"); + if (!IsDict(font)) + return; + + font = font.get(fontRef.name); font = this.xref.fetchIfRef(font); if (!font) return; diff --git a/test.js b/test.js index 071b2f097..2b99dc136 100644 --- a/test.js +++ b/test.js @@ -85,7 +85,9 @@ function displayPage(num) { // Check if the font has been loaded or is still loading var font = Fonts[fontName]; if (!font) { - var fontFile = xref.fetchIfRef(descriptor.get2("FontFile", "FontFile2")); + var fontFile = descriptor.get2("FontFile", "FontFile2"); + fontFile = xref.fetchIfRef(fontFile); + // Generate the custom cmap of the font if needed var encodingMap = {}; if (fontDict.has("Encoding")) {