diff --git a/fonts.js b/fonts.js index 57e974e12..77ad85a00 100755 --- a/fonts.js +++ b/fonts.js @@ -22,8 +22,7 @@ var kMaxWaitForFontFace = 1000; */ var FontMeasure = (function FontMeasure() { - var kScalePrecision = 40; - + var kScalePrecision = 50; var ctx = document.createElement('canvas').getContext('2d'); ctx.scale(1 / kScalePrecision, 1); @@ -341,10 +340,9 @@ function getUnicodeRangeFor(value) { * var type1Font = new Font("MyFontName", binaryFile, propertiesObject); * type1Font.bind(); */ -var Font = (function() { +var Font = (function Font() { var constructor = function font_constructor(name, file, properties) { this.name = name; - this.textMatrix = properties.textMatrix || IDENTITY_MATRIX; this.encoding = properties.encoding; this.sizes = []; @@ -387,6 +385,7 @@ var Font = (function() { } this.data = data; + this.textMatrix = properties.textMatrix || IDENTITY_MATRIX; this.type = properties.type; this.loadedName = getUniqueName(); this.compositeFont = properties.compositeFont; @@ -511,11 +510,11 @@ var Font = (function() { } ranges.push([start, end]); } + return ranges; }; - function createCMapTable(glyphs) { - glyphs.push({ unicode: 0x0000 }); + function createCMapTable(glyphs, deltas) { var ranges = getRanges(glyphs); var numTables = 1; @@ -542,19 +541,18 @@ var Font = (function() { var range = ranges[i]; var start = range[0]; var end = range[1]; - var delta = (bias - start) & 0xffff; + var offset = (segCount - i) * 2 + bias * 2; bias += (end - start + 1); startCount += string16(start); endCount += string16(end); - idDeltas += string16(delta); - idRangeOffsets += string16(0); - - for (var j = start; j <= end; j++) { - glyphsIds += string16(j); - } + idDeltas += string16(0); + idRangeOffsets += string16(offset); } + for (var i = 0; i < glyphs.length; i++) + glyphsIds += string16(deltas ? deltas[i] : i + 1); + endCount += '\xFF\xFF'; startCount += '\xFF\xFF'; idDeltas += '\x00\x01'; @@ -753,8 +751,8 @@ var Font = (function() { return { tag: tag, checksum: checksum, - length: offset, - offset: length, + length: length, + offset: offset, data: data }; }; @@ -770,26 +768,66 @@ var Font = (function() { }; function replaceCMapTable(cmap, font, properties) { - font.pos = (font.start ? font.start : 0) + cmap.length; + var start = (font.start ? font.start : 0) + cmap.offset; + font.pos = start; var version = int16(font.getBytes(2)); - var numTables = int16(font.getBytes(2)); + var numRecords = int16(font.getBytes(2)); - for (var i = 0; i < numTables; i++) { - var platformID = int16(font.getBytes(2)); - var encodingID = int16(font.getBytes(2)); - var offset = int32(font.getBytes(4)); + var records = []; + for (var i = 0; i < numRecords; i++) { + records.push({ + platformID: int16(font.getBytes(2)), + encodingID: int16(font.getBytes(2)), + offset: int32(font.getBytes(4)) + }); + }; + + var encoding = properties.encoding; + var charset = properties.charset; + for (var i = 0; i < numRecords; i++) { + var table = records[i]; + font.pos = start + table.offset; + var format = int16(font.getBytes(2)); var length = int16(font.getBytes(2)); var language = int16(font.getBytes(2)); - if ((format == 0 && numTables == 1) || - (format == 6 && numTables == 1 && !properties.encoding.empty)) { + if (format == 0) { + // Characters below 0x20 are controls characters that are hardcoded + // into the platform so if some characters in the font are assigned + // under this limit they will not be displayed so let's rewrite the + // CMap. + var glyphs = []; + var deltas = []; + for (var j = 0; j < 256; j++) { + var index = font.getByte(); + if (index) { + deltas.push(index); + glyphs.push({ unicode : j }); + } + } + + var rewrite = false; + for (var code in encoding) { + if (code < 0x20 && encoding[code]) + rewrite = true; + + if (rewrite) + encoding[code] = parseInt(code) + 0x1F; + } + + if (rewrite) { + for (var j = 0; j < glyphs.length; j++) { + glyphs[j].unicode += 0x1F; + } + } + cmap.data = createCMapTable(glyphs, deltas); + } else if (format == 6 && numRecords == 1 && !encoding.empty) { // Format 0 alone is not allowed by the sanitizer so let's rewrite // that to a 3-1-4 Unicode BMP table TODO('Use an other source of informations than ' + 'charset here, it is not reliable'); - var charset = properties.charset; var glyphs = []; for (var j = 0; j < charset.length; j++) { glyphs.push({ @@ -798,7 +836,7 @@ var Font = (function() { } cmap.data = createCMapTable(glyphs); - } else if (format == 6 && numTables == 1) { + } else if (format == 6 && numRecords == 1) { // Format 6 is a 2-bytes dense mapping, which means the font data // lives glue together even if they are pretty far in the unicode // table. (This looks weird, so I can have missed something), this @@ -832,7 +870,6 @@ var Font = (function() { assert(ranges.length == 1, 'Got ' + ranges.length + ' ranges in a dense array'); - var encoding = properties.encoding; var denseRange = ranges[0]; var start = denseRange[0]; var end = denseRange[1]; @@ -851,10 +888,7 @@ var Font = (function() { var header = readOpenTypeHeader(font); var numTables = header.numTables; - // This keep a reference to the CMap and the post tables since they can - // be rewritted - var cmap, post, nameTable, maxp; - + var cmap, maxp, hhea, hmtx; var tables = []; for (var i = 0; i < numTables; i++) { var table = readTableEntry(font); @@ -862,148 +896,153 @@ var Font = (function() { if (index != -1) { if (table.tag == 'cmap') cmap = table; - else if (table.tag == 'post') - post = table; - else if (table.tag == 'name') - nameTable = table; else if (table.tag == 'maxp') maxp = table; + else if (table.tag == 'hhea') + hhea = table; + else if (table.tag == 'hmtx') + hmtx = table; requiredTables.splice(index, 1); } tables.push(table); } - // 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') { - // Create a new file to hold the new version of our truetype with a new - // header and new offsets - var ttf = new Uint8Array(kMaxFontFileSize); + // Create a new file to hold the new version of our truetype with a new + // header and new offsets + var ttf = new Uint8Array(kMaxFontFileSize); - // 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 put the actual data of a particular - // table - var numTables = header.numTables + requiredTables.length; - var offsets = { - currentOffset: 0, - virtualOffset: numTables * (4 * 4) - }; + // 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 put the actual data of a particular + // table + var numTables = header.numTables + requiredTables.length; + var offsets = { + currentOffset: 0, + virtualOffset: numTables * (4 * 4) + }; - // The new numbers of tables will be the last one plus the num - // of missing tables - createOpenTypeHeader('\x00\x01\x00\x00', ttf, offsets, numTables); + // The new numbers of tables will be the last one plus the num + // of missing tables + createOpenTypeHeader('\x00\x01\x00\x00', ttf, offsets, numTables); - // Insert the missing table + if (requiredTables.indexOf('OS/2') != -1) { tables.push({ tag: 'OS/2', data: stringToArray(createOS2Table(properties)) }); - - // Replace the old CMAP table with a shiny new one - if (properties.type == 'CIDFontType2') { - // Type2 composite fonts map characters directly to glyphs so the cmap - // table must be replaced. - - var glyphs = []; - var charset = properties.charset; - if (!charset.length) { - // PDF did not contain a GIDMap for the font so create an identity cmap - - // First get the number of glyphs from the maxp table - font.pos = (font.start ? font.start : 0) + maxp.length; - var version = int16(font.getBytes(4)); - var numGlyphs = int16(font.getBytes(2)); - - // Now create an identity mapping - for (var i = 1; i < numGlyphs; i++) { - glyphs.push({ - unicode: i - }); - } - } else { - for (var i = 1; i < charset.length; i++) { - var index = charset.indexOf(i); - if (index != -1) { - glyphs.push({ - unicode: index - }); - } else { - break; - } - } - } - - if (!cmap) { - // Font did not contain a cmap - tables.push({ - tag: 'cmap', - data: createCMapTable(glyphs) - }) - } else { - cmap.data = createCMapTable(glyphs); - } - } else { - replaceCMapTable(cmap, font, properties); - } - - // Rewrite the 'post' table if needed - if (!post) { - tables.push({ - tag: 'post', - data: stringToArray(createPostTable(properties)) - }); - } - - // Rewrite the 'name' table if needed - if (!nameTable) { - tables.push({ - tag: 'name', - data: stringToArray(createNameTable(this.name)) - }); - } - - // Tables needs to be written by ascendant alphabetic order - tables.sort(function tables_sort(a, b) { - return (a.tag > b.tag) - (a.tag < b.tag); - }); - - // 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]); - 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; - - // 4-byte aligned data - while (offsets.currentOffset & 3) - offsets.currentOffset++; - } - - var fontData = []; - for (var i = 0; i < offsets.currentOffset; i++) - fontData.push(ttf[i]); - - return fontData; - } else if (requiredTables.length) { - error('Table ' + requiredTables[0] + - ' is missing from the TrueType font'); } - return font.getBytes(); + // Ensure the hmtx tables contains an advance width and a sidebearing + // for the number of glyphs declared in the maxp table + font.pos = (font.start ? font.start : 0) + maxp.offset; + var version = int16(font.getBytes(4)); + var numGlyphs = int16(font.getBytes(2)); + + font.pos = (font.start ? font.start : 0) + hhea.offset; + font.pos += hhea.length - 2; + var numOfHMetrics = int16(font.getBytes(2)); + + var numOfSidebearings = numGlyphs - numOfHMetrics; + var numMissing = numOfSidebearings - (hmtx.length - numOfHMetrics * 4); + if (numMissing > 0) { + font.pos = (font.start ? font.start : 0) + hmtx.offset; + var metrics = ""; + for (var i = 0; i < hmtx.length; i++) + metrics += String.fromCharCode(font.getByte()); + for (var i = 0; i < numMissing; i++) + metrics += "\x00\x00"; + hmtx.data = stringToArray(metrics); + } + + + // Replace the old CMAP table with a shiny new one + if (properties.type == 'CIDFontType2') { + // Type2 composite fonts map characters directly to glyphs so the cmap + // table must be replaced. + + var glyphs = []; + var charset = properties.charset; + if (!charset.length) { + // Type2 composite fonts map characters directly to glyphs so the cmap + for (var i = 1; i < numGlyphs; i++) { + glyphs.push({ + unicode: i + }); + } + } else { + for (var i = 1; i < charset.length; i++) { + var index = charset.indexOf(i); + if (index == -1) + break; + + glyphs.push({ + unicode: index + }); + } + } + + if (!cmap) { + cmap = { + tag: 'cmap', + data: null + }; + tables.push(cmap); + } + cmap.data = createCMapTable(glyphs); + } else { + replaceCMapTable(cmap, font, properties); + } + + // Rewrite the 'post' table if needed + if (requiredTables.indexOf('post') != -1) { + tables.push({ + tag: 'post', + data: stringToArray(createPostTable(properties)) + }); + } + + // Rewrite the 'name' table if needed + if (requiredTables.indexOf('name') != -1) { + tables.push({ + tag: 'name', + data: stringToArray(createNameTable(this.name)) + }); + } + + // Tables needs to be written by ascendant alphabetic order + tables.sort(function tables_sort(a, b) { + return (a.tag > b.tag) - (a.tag < b.tag); + }); + + // 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]); + 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; + + // 4-byte aligned data + while (offsets.currentOffset & 3) + offsets.currentOffset++; + } + + var fontData = []; + for (var i = 0; i < offsets.currentOffset; i++) + fontData.push(ttf[i]); + + return fontData; }, convert: function font_convert(fontName, font, properties) { @@ -1726,8 +1765,10 @@ CFF.prototype = { String.fromCharCode((value >> 8) & 0xFF) + String.fromCharCode(value & 0xFF); } else if (value >= (-2147483648) && value <= 2147483647) { + value ^= 0xffffffff; + value += 1; return '\xff' + - String.fromCharCode((value >>> 24) & 0xFF) + + String.fromCharCode((value >> 24) & 0xFF) + String.fromCharCode((value >> 16) & 0xFF) + String.fromCharCode((value >> 8) & 0xFF) + String.fromCharCode(value & 0xFF); @@ -1833,7 +1874,14 @@ CFF.prototype = { charstring[i] = cmd; } } else { - charstring.splice(i, 1, 28, command >> 8, command & 0xff); + // Type1 charstring use a division for number above 32000 + if (command > 32000) { + var divisor = charstring[i + 1]; + command /= divisor; + charstring.splice(i, 3, 28, command >> 8, command & 0xff); + } else { + charstring.splice(i, 1, 28, command >> 8, command & 0xff); + } i += 2; } } diff --git a/pdf.js b/pdf.js index bed520748..3da771705 100644 --- a/pdf.js +++ b/pdf.js @@ -3660,7 +3660,7 @@ var PartialEvaluator = (function() { var fontName = descriptor.get('FontName'); assertWellFormed(IsName(fontName), 'invalid font name'); - fontName = fontName.name.replace('+', '_'); + fontName = fontName.name.replace(/[\+,\-]/g, '_'); var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3'); if (!fontFile) @@ -3708,9 +3708,8 @@ var PartialEvaluator = (function() { var baseName = encoding.get('BaseEncoding'); if (baseName) { var base = Encodings[baseName.name]; - var index = 0; for (var j = 0, end = base.length; j < end; j++) - encodingMap[index++] = GlyphsUnicode[base[j]]; + encodingMap[j] = GlyphsUnicode[base[j]] || 0; } else { TODO('need to load default encoding'); } @@ -3720,7 +3719,11 @@ var PartialEvaluator = (function() { var index = 0; for (var j = 0; j < differences.length; j++) { var data = differences[j]; - IsNum(data) ? index = data : encodingMap[index++] = data; + if (subType.name == 'TrueType') { + IsNum(data) ? index = data : encodingMap[index++] = j; + } else { + IsNum(data) ? index = data : encodingMap[index++] = GlyphsUnicode[data.name]; + } } // Get the font charset if any @@ -3770,6 +3773,7 @@ var PartialEvaluator = (function() { error('useCMap is not implemented'); break; + case 'beginbfchar': case 'beginbfrange': case 'begincodespacerange': token = ''; @@ -3787,17 +3791,18 @@ var PartialEvaluator = (function() { var code = parseInt('0x' + tokens[j + 2]); for (var k = startRange; k <= endRange; k++) { - // The encoding mapping table will be filled - // later during the building phase - //encodingMap[k] = GlyphsUnicode[encoding[code]]; charset.push(encoding[code++] || '.notdef'); } } break; - case 'beginfbchar': - case 'endfbchar': - error('fbchar parsing is not implemented'); + case 'endbfchar': + for (var j = 0; j < tokens.length; j += 2) { + var index = parseInt('0x' + tokens[j]); + var code = parseInt('0x' + tokens[j + 1]); + encodingMap[index] = GlyphsUnicode[encoding[code]]; + charset.push(encoding[code] || '.notdef'); + } break; default: @@ -3898,6 +3903,9 @@ function ScratchCanvas(width, height) { } var CanvasGraphics = (function() { + var kScalePrecision = 50; + var kRasterizerMin = 14; + function constructor(canvasCtx, imageCanvas) { this.ctx = canvasCtx; this.current = new CanvasExtraState(); @@ -4113,8 +4121,10 @@ var CanvasGraphics = (function() { if (this.ctx.$setFont) { this.ctx.$setFont(fontName, size); } else { - this.ctx.font = size + 'px "' + fontName + '"'; FontMeasure.setActive(fontObj, size); + + size = (size <= kRasterizerMin) ? size * kScalePrecision : size; + this.ctx.font = size + 'px "' + fontName + '"'; } }, setTextRenderingMode: function(mode) { @@ -4131,7 +4141,7 @@ var CanvasGraphics = (function() { } }, setLeadingMoveText: function(x, y) { - this.setLeading(-y); + this.setLeading(y); this.moveText(x, y); }, setTextMatrix: function(a, b, c, d, e, f) { @@ -4162,6 +4172,8 @@ var CanvasGraphics = (function() { ctx.translate(current.x, -1 * current.y); var font = this.current.font; if (font) { + if (this.current.fontSize < kRasterizerMin) + ctx.transform(1 / kScalePrecision, 0, 0, 1 / kScalePrecision, 0, 0); ctx.transform.apply(ctx, font.textMatrix); text = font.charsToUnicode(text); }