From 21ee01eb7c75e3727dea6a1a498fa85c11e684d1 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 17 Jun 2011 23:38:01 +0200 Subject: [PATCH 01/24] Fix wrong order of the 'div' command --- fonts.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/fonts.js b/fonts.js index b9ef5b330..6c3d13051 100644 --- a/fonts.js +++ b/fonts.js @@ -1235,7 +1235,7 @@ CFF.prototype = { case "div": var num2 = aCharstring[i - 1]; var num1 = aCharstring[i - 2]; - aCharstring.splice(i - 2, 3, num2 / num1); + aCharstring.splice(i - 2, 3, num1 / num2); i -= 2; break; @@ -1354,7 +1354,6 @@ CFF.prototype = { } var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true); - charstringsIndex = charstringsIndex.join(" ").split(" "); // XXX why? //Top Dict Index var topDictIndex = [ @@ -1388,7 +1387,6 @@ CFF.prototype = { var privateOffset = charstringsOffset + charstringsIndex.length; topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset)); topDictIndex.push(18); // Private - topDictIndex = topDictIndex.join(" ").split(" "); var indexes = [ topDictIndex, stringsIndex, @@ -1418,7 +1416,6 @@ CFF.prototype = { 139, 12, 14, 28, 0, 55, 19 ]); - privateData = privateData.join(" ").split(" "); cff.set(privateData, currentOffset); currentOffset += privateData.length; From 9eae948c2e31f15d3d9af4032a284e5720234565 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 17 Jun 2011 23:39:17 +0200 Subject: [PATCH 02/24] Replace clearTimeout by clearInterval, otherwise we could end up drawing the page in an infinite loop --- test.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test.js b/test.js index 59d8167a2..a8e7ae6e1 100644 --- a/test.js +++ b/test.js @@ -50,8 +50,7 @@ function gotoPage(num) { } function displayPage(num) { - if (pageNum != num) - window.clearTimeout(pageInterval); + window.clearInterval(pageInterval); document.getElementById("pageNumber").value = num; @@ -94,7 +93,7 @@ function displayPage(num) { if (Fonts[font.name].loading) return; } - clearInterval(pageInterval); + window.clearInterval(pageInterval); var t3 = Date.now(); From 8d9ffb1872e4380b9409f237baf29e8950e18a97 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sat, 18 Jun 2011 21:54:53 +0200 Subject: [PATCH 03/24] Use measureText instead of mozMeasureText (deprecated) --- fonts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fonts.js b/fonts.js index 951cbd0ed..16c1246d9 100644 --- a/fonts.js +++ b/fonts.js @@ -203,7 +203,7 @@ Font.prototype = { } } ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - var textWidth = ctx.mozMeasureText(testString); + var textWidth = ctx.measureText(testString); if (debug) ctx.fillText(testString, 20, 20); @@ -218,7 +218,7 @@ Font.prototype = { window.clearInterval(interval); Fonts[fontName].loading = false; warn("Is " + fontName + " for charset: " + charset + " loaded?"); - } else if (textWidth != ctx.mozMeasureText(testString)) { + } else if (textWidth != ctx.measureText(testString)) { window.clearInterval(interval); Fonts[fontName].loading = false; } From 59bd7620de62350bdc22543617dcee1097f1c737 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sun, 19 Jun 2011 02:25:21 +0200 Subject: [PATCH 04/24] Fill informations for the 'name' table in order to pass the sanitizer (Mozilla bug 660088) --- fonts.js | 64 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/fonts.js b/fonts.js index 16c1246d9..219ada8ca 100644 --- a/fonts.js +++ b/fonts.js @@ -94,7 +94,7 @@ var Font = function(aName, aFile, aProperties) { this.mimetype = "font/otf"; // Wrap the CFF data inside an OTF font file - this.font = this.cover(cff, aProperties); + this.font = this.cover(aName, cff, aProperties); break; case "TrueType": @@ -376,7 +376,61 @@ Font.prototype = { idDeltas, idRangeOffsets, glyphsIdsArray); }, - cover: function font_cover(aFont, aProperties) { + _createNameTable: function font_createNameTable(aName) { + var names = [ + "See original licence", // Copyright + aName, // Font family + "undefined", // Font subfamily (font weight) + "uniqueID", // Unique ID + aName, // Full font name + "0.1", // Version + "undefined", // Postscript name + "undefined", // Trademark + "undefined", // Manufacturer + "undefined" // Designer + ]; + + name = [ + 0x00, 0x00, // format + 0x00, 0x0A, // Number of names Record + 0x00, 0x7E // Storage + ]; + + // Build the name records field + var strOffset = 0; + for (var i = 0; i < names.length; i++) { + var str = names[i]; + + var nameRecord = [ + 0x00, 0x01, // platform ID + 0x00, 0x00, // encoding ID + 0x00, 0x00, // language ID + 0x00, 0x00 // name ID + ]; + + nameRecord = nameRecord.concat( + FontsUtils.integerToBytes(str.length, 2), + FontsUtils.integerToBytes(strOffset, 2) + ); + name = name.concat(nameRecord); + + strOffset += str.length; + } + + // Add the name records data + for (var i = 0; i < names.length; i++) { + var str = names[i]; + var strBytes = []; + for (var j = 0; j < str.length; j++) { + strBytes.push(str.charCodeAt(j)); + } + name = name.concat(strBytes); + } + + return name; + }, + + cover: function font_cover(aName, aFont, aProperties) { var otf = new Uint8Array(kMaxFontFileSize); // Required Tables @@ -524,11 +578,7 @@ Font.prototype = { this._createTableEntry(otf, offsets, "maxp", maxp); /** NAME */ - name = [ - 0x00, 0x00, // format - 0x00, 0x00, // Number of names Record - 0x00, 0x00 // Storage - ]; + var name = this._createNameTable(aName); this._createTableEntry(otf, offsets, "name", name); /** POST */ From e62bd48a9e99ff27e30923d4f35cd44605b61499 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sun, 19 Jun 2011 02:35:28 +0200 Subject: [PATCH 05/24] Use strict mode for js files --- fonts.js | 5 ++++- glyphlist.js | 2 ++ pdf.js | 17 ++++++++++------- utils/fonts_utils.js | 1 + viewer.js | 8 +++++--- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/fonts.js b/fonts.js index 219ada8ca..576e35cb9 100644 --- a/fonts.js +++ b/fonts.js @@ -1,6 +1,8 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +"use strict"; + /** * Maximum file size of the font. */ @@ -1092,7 +1094,8 @@ var Type1Parser = function() { this.extractFontProgram = function t1_extractFontProgram(aStream) { var eexecString = decrypt(aStream, kEexecEncryptionKey, 4); var subrs = [], glyphs = []; - var inSubrs = inGlyphs = false; + var inSubrs = false; + var inGlyphs = false; var glyph = ""; var token = ""; diff --git a/glyphlist.js b/glyphlist.js index 1a0190133..f638ff888 100644 --- a/glyphlist.js +++ b/glyphlist.js @@ -1,3 +1,5 @@ +"use strict"; + var GlyphsUnicode = { A: 0x0041, AE: 0x00C6, diff --git a/pdf.js b/pdf.js index 9c4344fab..02159f94b 100644 --- a/pdf.js +++ b/pdf.js @@ -1,6 +1,8 @@ /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +"use strict"; + var ERRORS = 0, WARNINGS = 1, TODOS = 5; var verbosity = WARNINGS; @@ -246,7 +248,7 @@ var FlateStream = (function() { this.eof = false; this.codeSize = 0; this.codeBuf = 0; - + this.pos = 0; this.bufferLength = 0; } @@ -432,6 +434,11 @@ var FlateStream = (function() { litCodeTable = fixedLitCodeTab; distCodeTable = fixedDistCodeTab; } else if (hdr == 2) { // compressed block, dynamic codes + var repeat = function repeat(stream, array, len, offset, what) { + var repeat = stream.getBits(len) + offset; + while (repeat-- > 0) + array[i++] = what; + } var numLitCodes = this.getBits(5) + 257; var numDistCodes = this.getBits(5) + 1; var numCodeLenCodes = this.getBits(4) + 4; @@ -449,11 +456,6 @@ var FlateStream = (function() { var codes = numLitCodes + numDistCodes; var codeLengths = new Array(codes); while (i < codes) { - function repeat(stream, array, len, offset, what) { - var repeat = stream.getBits(len) + offset; - while (repeat-- > 0) - array[i++] = what; - } var code = this.getCode(codeLenCodeTab); if (code == 16) { repeat(this, codeLengths, 2, 3, len); @@ -725,6 +727,7 @@ var Lexer = (function() { var done = false; var str = ""; var stream = this.stream; + var ch = null; do { switch (ch = stream.getChar()) { case undefined: @@ -1429,7 +1432,7 @@ var Catalog = (function() { return shadow(this, "toplevelPagesDict", obj); }, get numPages() { - obj = this.toplevelPagesDict.get("Count"); + var obj = this.toplevelPagesDict.get("Count"); assertWellFormed(IsInt(obj), "page count in top level pages object is not an integer"); // shadow the prototype getter diff --git a/utils/fonts_utils.js b/utils/fonts_utils.js index 086648fe2..45f92a28b 100644 --- a/utils/fonts_utils.js +++ b/utils/fonts_utils.js @@ -7,6 +7,7 @@ * CharString or to understand the structure of the CFF format. */ +"use strict"; /** * Build a charset by assigning the glyph name and the human readable form diff --git a/viewer.js b/viewer.js index a8e7ae6e1..41aaf354c 100644 --- a/viewer.js +++ b/viewer.js @@ -1,12 +1,14 @@ /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ -var pdfDocument, canvas, pageDisplay, pageNum, pageInterval; +"use strict"; + +var pdfDocument, canvas, pageDisplay, pageNum, numPages, pageInterval; function load(userInput) { canvas = document.getElementById("canvas"); canvas.mozOpaque = true; pageNum = parseInt(queryParams().page) || 1; - fileName = userInput; + var fileName = userInput; if (!userInput) { fileName = queryParams().file || "compressed.tracemonkey-pldi-09.pdf"; } @@ -26,7 +28,7 @@ function queryParams() { function open(url) { document.title = url; - req = new XMLHttpRequest(); + var req = new XMLHttpRequest(); req.open("GET", url); req.mozResponseType = req.responseType = "arraybuffer"; req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200; From 7517256e5408063c562fa24dc3675ec3a30703e8 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sun, 19 Jun 2011 02:36:47 +0200 Subject: [PATCH 06/24] Adding myself as a contributor --- LICENSE | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 6b2bc4d61..4d5a7b2dc 100644 --- a/LICENSE +++ b/LICENSE @@ -4,6 +4,7 @@ Contributors: Andreas Gal Chris G Jones Shaon Barman + Vivien Nicolas <21@vingtetun.org> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -21,4 +22,4 @@ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. \ No newline at end of file + DEALINGS IN THE SOFTWARE. From a46ebe73fff518ca5088408b52b2da39844a29e5 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sun, 19 Jun 2011 03:04:15 +0200 Subject: [PATCH 07/24] Ask chromium to shut up on font/otf mimetype by using another wrong mimetype and fix a few more 'strict mode' errrors --- fonts.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/fonts.js b/fonts.js index 576e35cb9..aa0ad2933 100644 --- a/fonts.js +++ b/fonts.js @@ -93,7 +93,7 @@ var Font = function(aName, aFile, aProperties) { switch (aProperties.type) { case "Type1": var cff = new CFF(aName, aFile, aProperties); - this.mimetype = "font/otf"; + this.mimetype = "font/opentype"; // Wrap the CFF data inside an OTF font file this.font = this.cover(aName, cff, aProperties); @@ -112,7 +112,7 @@ var Font = function(aName, aFile, aProperties) { cache: Object.create(null) }; - this.mimetype = "font/ttf"; + this.mimetype = "font/opentype"; var ttf = new TrueType(aFile); this.font = ttf.data; break; @@ -392,7 +392,7 @@ Font.prototype = { "undefined" // Designer ]; - name = [ + var name = [ 0x00, 0x00, // format 0x00, 0x0A, // Number of names Record 0x00, 0x7E // Storage @@ -703,7 +703,7 @@ var TrueType = function(aFile) { // missing, which means that we need to rebuild the font in order to pass // the sanitizer. if (requiredTables.length && requiredTables[0] == "OS/2") { - OS2 = [ + var OS2 = [ 0x00, 0x03, // version 0x02, 0x24, // xAvgCharWidth 0x01, 0xF4, // usWeightClass @@ -793,10 +793,14 @@ var TrueType = function(aFile) { offsets.currentOffset++; } - this.data = ttf; + var fontData = []; + for (var i = 0; i < offsets.currentOffset; i++) + fontData.push(ttf[i]); + + this.data = fontData; return; } else if (requiredTables.lenght) { - error("Table " + requiredTables[0] + " is missing from the TruType font"); + error("Table " + requiredTables[0] + " is missing from the TrueType font"); } else { this.data = aFile; } From 2670aa680b0f43f8c44b48fde46cfb65a11cb343 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sun, 19 Jun 2011 03:08:43 +0200 Subject: [PATCH 08/24] Another undeclared variable caught by strict mode --- pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index 02159f94b..399708b84 100644 --- a/pdf.js +++ b/pdf.js @@ -2384,7 +2384,7 @@ var CanvasGraphics = (function() { error("No support for array of functions"); else if (!IsPDFFunction(fnObj)) error("Invalid function"); - fn = new PDFFunction(this.xref, fnObj); + var fn = new PDFFunction(this.xref, fnObj); var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); var step = (t1 - t0) / 10; From 7e3c1340ffe316eae69da62f08120ed011cc0409 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sun, 19 Jun 2011 04:35:25 +0200 Subject: [PATCH 09/24] Re-enable TrueType and add a TODO about translating the ToUnicode entry (note TrueType still fails on cmap) --- fonts.js | 18 +++--------------- pdf.js | 12 ++++++++++-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/fonts.js b/fonts.js index aa0ad2933..8bc169aa6 100644 --- a/fonts.js +++ b/fonts.js @@ -100,18 +100,6 @@ var Font = function(aName, aFile, aProperties) { break; case "TrueType": - // TrueType is disabled for the moment since the sanitizer prevent it - // from loading because of an overdated cmap table - return Fonts[aName] = { - data: null, - properties: { - encoding: {}, - charset: null - }, - loading: false, - cache: Object.create(null) - }; - this.mimetype = "font/opentype"; var ttf = new TrueType(aFile); this.font = ttf.data; @@ -184,7 +172,7 @@ Font.prototype = { document.body.appendChild(canvas); // Retrieve font charset - var charset = Fonts[fontName].charset || []; + var charset = Fonts[fontName].properties.charset || []; // if the charset is too small make it repeat a few times var count = 30; while (count-- && charset.length <= 30) @@ -205,7 +193,7 @@ Font.prototype = { } } ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - var textWidth = ctx.measureText(testString); + var textWidth = ctx.measureText(testString).width; if (debug) ctx.fillText(testString, 20, 20); @@ -220,7 +208,7 @@ Font.prototype = { window.clearInterval(interval); Fonts[fontName].loading = false; warn("Is " + fontName + " for charset: " + charset + " loaded?"); - } else if (textWidth != ctx.measureText(testString)) { + } else if (textWidth != ctx.measureText(testString).width) { window.clearInterval(interval); Fonts[fontName].loading = false; } diff --git a/pdf.js b/pdf.js index 399708b84..2288654f3 100644 --- a/pdf.js +++ b/pdf.js @@ -1902,6 +1902,7 @@ var CanvasGraphics = (function() { // Generate the custom cmap of the font if needed var encodingMap = {}; + var charset = []; if (fontDict.has("Encoding")) { var encoding = xref.fetchIfRef(fontDict.get("Encoding")); if (IsDict(encoding)) { @@ -1923,16 +1924,23 @@ var CanvasGraphics = (function() { if (!encoding) error("Unknown font encoding"); - var widths = xref.fetchIfRef(fontDict.get("Widths")); var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); + + var index = 0; + for (var j = 0; j < encoding.length; j++) { + encodingMap[firstChar + index++] = GlyphsUnicode[encoding[j]]; + } + + var widths = xref.fetchIfRef(fontDict.get("Widths")); assertWellFormed(IsArray(widths) && IsInt(firstChar), "invalid font Widths or FirstChar"); - var charset = []; for (var j = 0; j < widths.length; j++) { if (widths[j]) charset.push(encoding[j + firstChar]); } } + } else if (fontDict.has("ToUnicode")) { + TODO("ToUnicode stream translation not implemented"); } var subType = fontDict.get("Subtype"); From 8a24a967c365883541a2247697f79282c2d3acdd Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sun, 19 Jun 2011 09:29:28 +0200 Subject: [PATCH 10/24] Make Helvetica and Helvetica bold from page 2 to pass the sanitizer --- fonts.js | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 7 deletions(-) diff --git a/fonts.js b/fonts.js index 8bc169aa6..3e3af250b 100644 --- a/fonts.js +++ b/fonts.js @@ -101,7 +101,7 @@ var Font = function(aName, aFile, aProperties) { case "TrueType": this.mimetype = "font/opentype"; - var ttf = new TrueType(aFile); + var ttf = new TrueType(aName, aFile, aProperties); this.font = ttf.data; break; @@ -656,7 +656,7 @@ var FontsUtils = { * is deprecated and not supported by the sanitizer... * */ -var TrueType = function(aFile) { +var TrueType = function(aName, aFile, aProperties) { var header = this._readOpenTypeHeader(aFile); var numTables = header.numTables; @@ -682,11 +682,6 @@ var TrueType = function(aFile) { tables.push(table); } - // Tables needs to be written by ascendant alphabetic order - 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. @@ -758,6 +753,31 @@ var TrueType = function(aFile) { data: OS2 }); + // If the font is missing a OS/2 table it's could be an old mac font + // without a 3-1-4 Unicode BMP table, so let's rewrite it. + var charset = aProperties.charset; + var glyphs = []; + for (var i = 0; i < charset.length; i++) { + glyphs.push({ + unicode: GlyphsUnicode[aProperties.encoding[charset[i]]] + }); + } + + // Replace the old CMAP table + var cmap = this._createCMAPTable(glyphs); + for (var i = 0; i < tables.length; i++) { + var table = tables[i]; + if (table.tag == "cmap") { + table.data = cmap; + break; + } + } + + // Tables needs to be written by ascendant alphabetic order + tables.sort(function(a, b) { + return a.tag > b.tag; + }); + // rewrite the tables but tweak offsets for (var i = 0; i < tables.length; i++) { var table = tables[i]; @@ -893,6 +913,88 @@ TrueType.prototype = { offset: length, data: data } + }, + + _createCMAPTable: function font_createCMAPTable(aGlyphs) { + var characters = new Uint16Array(kMaxGlyphsCount); + for (var i = 0; i < aGlyphs.length; i++) + characters[aGlyphs[i].unicode] = i + 1; + + // Separate the glyphs into continuous range of codes, aka segment. + var ranges = []; + var range = []; + var count = characters.length; + for (var i = 0; i < count; i++) { + if (characters[i]) { + range.push(i); + } else if (range.length) { + ranges.push(range.slice()); + range = []; + } + } + + // The size in bytes of the header is equal to the size of the + // different fields * length of a short + (size of the 4 parallels arrays + // describing segments * length of a short). + var headerSize = (12 * 2 + (ranges.length * 4 * 2)); + + var segCount = ranges.length + 1; + var segCount2 = segCount * 2; + var searchRange = FontsUtils.getMaxPower2(segCount) * 2; + var searchEntry = Math.log(segCount) / Math.log(2); + var rangeShift = 2 * segCount - searchRange; + var cmap = [].concat( + [ + 0x00, 0x00, // version + 0x00, 0x01, // numTables + 0x00, 0x03, // platformID + 0x00, 0x01, // encodingID + 0x00, 0x00, 0x00, 0x0C, // start of the table record + 0x00, 0x04 // format + ], + FontsUtils.integerToBytes(headerSize, 2), // length + [0x00, 0x00], // language + FontsUtils.integerToBytes(segCount2, 2), + FontsUtils.integerToBytes(searchRange, 2), + FontsUtils.integerToBytes(searchEntry, 2), + FontsUtils.integerToBytes(rangeShift, 2) + ); + + // Fill up the 4 parallel arrays describing the segments. + var startCount = []; + var endCount = []; + var idDeltas = []; + var idRangeOffsets = []; + var glyphsIdsArray = []; + var bias = 0; + for (var i = 0; i < segCount - 1; i++) { + var range = ranges[i]; + var start = FontsUtils.integerToBytes(range[0], 2); + var end = FontsUtils.integerToBytes(range[range.length - 1], 2); + + var delta = FontsUtils.integerToBytes(((range[0] - 1) - bias) % 65536, 2); + bias += range.length; + + // deltas are signed shorts + delta[0] ^= 0xFF; + delta[1] ^= 0xFF; + delta[1] += 1; + + startCount.push(start[0], start[1]); + endCount.push(end[0], end[1]); + idDeltas.push(delta[0], delta[1]); + idRangeOffsets.push(0x00, 0x00); + + for (var j = 0; j < range.length; j++) + glyphsIdsArray.push(range[j]); + } + startCount.push(0xFF, 0xFF); + endCount.push(0xFF, 0xFF); + idDeltas.push(0x00, 0x01); + idRangeOffsets.push(0x00, 0x00); + + return cmap.concat(endCount, [0x00, 0x00], startCount, + idDeltas, idRangeOffsets, glyphsIdsArray); } }; From bf835f7aa53f34c4df0d3d6b7431ce8ca618a304 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sun, 19 Jun 2011 11:07:03 +0200 Subject: [PATCH 11/24] Really pass the sanitizer (encoding is broken) --- fonts.js | 49 +++++++++++++++++++++++++++---------------------- pdf.js | 2 +- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/fonts.js b/fonts.js index 3e3af250b..281eb94a8 100644 --- a/fonts.js +++ b/fonts.js @@ -82,6 +82,7 @@ var Fonts = { */ var Font = function(aName, aFile, aProperties) { this.name = aName; + this.encoding = aProperties.encoding; // If the font has already been decoded simply return if (Fonts[aName]) { @@ -134,6 +135,7 @@ Font.prototype = { name: null, font: null, mimetype: null, + encoding: null, bind: function font_bind() { var data = this.font; @@ -185,10 +187,11 @@ Font.prototype = { // When debugging use the characters provided by the charsets to visually // see what's happening if (debug) { + var encoding = this.encoding; for (var i = 0; i < charset.length; i++) { var unicode = GlyphsUnicode[charset[i]]; if (!unicode) - error("Unicode for " + charset[i] + " is has not been found in the glyphs list"); + continue; testString += String.fromCharCode(unicode); } } @@ -672,12 +675,18 @@ var TrueType = function(aName, aFile, aProperties) { "post" ]; + var originalCMAP = null; + var tables = []; for (var i = 0; i < numTables; i++) { var table = this._readTableEntry(aFile); var index = requiredTables.indexOf(table.tag); - if (index != -1) + if (index != -1) { + if (table.tag == "cmap") + originalCMAP = table; + requiredTables.splice(index, 1); + } tables.push(table); } @@ -726,10 +735,25 @@ var TrueType = function(aName, aFile, aProperties) { 0x00, 0x02 // usMaxContext ]; + // If the font is missing a OS/2 table it's could be an old mac font + // without a 3-1-4 Unicode BMP table, so let's rewrite it. + var charset = aProperties.charset; + var glyphs = []; + for (var i = 0; i < charset.length; i++) { + glyphs.push({ + unicode: GlyphsUnicode[charset[i]] + }); + } + + // Replace the old CMAP table + var rewrittedCMAP = this._createCMAPTable(glyphs); + var cmapDelta = rewrittedCMAP.length - originalCMAP.data.length; + originalCMAP.data = rewrittedCMAP; + // 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); + var ttf = new Uint8Array(stream.length + 16 + OS2.length + cmapDelta); // The new numbers of tables will be the last one plus the num of missing // tables @@ -753,25 +777,6 @@ var TrueType = function(aName, aFile, aProperties) { data: OS2 }); - // If the font is missing a OS/2 table it's could be an old mac font - // without a 3-1-4 Unicode BMP table, so let's rewrite it. - var charset = aProperties.charset; - var glyphs = []; - for (var i = 0; i < charset.length; i++) { - glyphs.push({ - unicode: GlyphsUnicode[aProperties.encoding[charset[i]]] - }); - } - - // Replace the old CMAP table - var cmap = this._createCMAPTable(glyphs); - for (var i = 0; i < tables.length; i++) { - var table = tables[i]; - if (table.tag == "cmap") { - table.data = cmap; - break; - } - } // Tables needs to be written by ascendant alphabetic order tables.sort(function(a, b) { diff --git a/pdf.js b/pdf.js index 2288654f3..fc2dcf7ea 100644 --- a/pdf.js +++ b/pdf.js @@ -1928,7 +1928,7 @@ var CanvasGraphics = (function() { var index = 0; for (var j = 0; j < encoding.length; j++) { - encodingMap[firstChar + index++] = GlyphsUnicode[encoding[j]]; + encodingMap[index++] = GlyphsUnicode[encoding[j]]; } var widths = xref.fetchIfRef(fontDict.get("Widths")); From b3b03224460a2fc7095a27ad95cfc46beb51499e Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Mon, 20 Jun 2011 00:09:50 +0200 Subject: [PATCH 12/24] Fix the encoding problem for TrueType --- pdf.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pdf.js b/pdf.js index fc2dcf7ea..631c1a74a 100644 --- a/pdf.js +++ b/pdf.js @@ -1608,6 +1608,9 @@ var CanvasExtraState = (function() { const Encodings = { get ExpertEncoding() { return shadow(this, "ExpertEncoding", [ + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, "space","exclamsmall","Hungarumlautsmall",,"dollaroldstyle","dollarsuperior", "ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior", "twodotenleader","onedotenleader","comma","hyphen","period","fraction", @@ -1643,6 +1646,9 @@ const Encodings = { }, get MacExpertEncoding() { return shadow(this, "MacExpertEncoding", [ + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, "space","exclamsmall","Hungarumlautsmall","centoldstyle","dollaroldstyle", "dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior", "parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period", @@ -1677,6 +1683,9 @@ const Encodings = { }, get MacRomanEncoding() { return shadow(this, "MacRomanEncoding", [ + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, "space","exclam","quotedbl","numbersign","dollar","percent","ampersand", "quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen", "period","slash","zero","one","two","three","four","five","six","seven","eight", @@ -1707,6 +1716,9 @@ const Encodings = { }, get StandardEncoding() { return shadow(this, "StandardEncoding", [ + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, "space","exclam","quotedbl","numbersign","dollar","percent","ampersand", "quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period", "slash","zero","one","two","three","four","five","six","seven","eight","nine", @@ -1727,6 +1739,9 @@ const Encodings = { }, get WinAnsiEncoding() { return shadow(this, "WinAnsiEncoding", [ + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, "space","exclam","quotedbl","numbersign","dollar","percent","ampersand", "quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen", "period","slash","zero","one","two","three","four","five","six","seven","eight", @@ -1758,6 +1773,9 @@ const Encodings = { }, get zapfDingbatsEncoding() { return shadow(this, "zapfDingbatsEncoding", [ + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, "space","a1","a2","a202","a3","a4","a5","a119","a118","a117","a11","a12","a13", "a14","a15","a16","a105","a17","a18","a19","a20","a21","a22","a23","a24","a25", "a26","a27","a28","a6","a7","a8","a9","a10","a29","a30","a31","a32","a33","a34", @@ -1934,6 +1952,7 @@ var CanvasGraphics = (function() { var widths = xref.fetchIfRef(fontDict.get("Widths")); assertWellFormed(IsArray(widths) && IsInt(firstChar), "invalid font Widths or FirstChar"); + for (var j = 0; j < widths.length; j++) { if (widths[j]) charset.push(encoding[j + firstChar]); From 0197697d1f23391360052b74ff555251d48d3f35 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Mon, 20 Jun 2011 02:52:30 +0200 Subject: [PATCH 13/24] Add a basic embedded CMap reader --- pdf.js | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/pdf.js b/pdf.js index 631c1a74a..7414ac7ea 100644 --- a/pdf.js +++ b/pdf.js @@ -1942,13 +1942,12 @@ var CanvasGraphics = (function() { if (!encoding) error("Unknown font encoding"); - var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); - var index = 0; for (var j = 0; j < encoding.length; j++) { encodingMap[index++] = GlyphsUnicode[encoding[j]]; } + var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); var widths = xref.fetchIfRef(fontDict.get("Widths")); assertWellFormed(IsArray(widths) && IsInt(firstChar), "invalid font Widths or FirstChar"); @@ -1959,8 +1958,66 @@ var CanvasGraphics = (function() { } } } else if (fontDict.has("ToUnicode")) { - TODO("ToUnicode stream translation not implemented"); - } + var cmapObj = xref.fetchIfRef(fontDict.get("ToUnicode")); + if (IsName(cmapObj)) { + error("ToUnicode basic cmap translation not implemented"); + encodingMap = {}; + } else if (IsStream(cmapObj)) { + var tokens = []; + var token = ""; + + var cmap = cmapObj.getBytes(cmapObj.length); + for (var i =0; i < cmap.length; i++) { + var byte = cmap[i]; + if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) { + switch (token) { + case "useCMap": + error("useCMap is not implemented"); + break; + + case "begincodespacerange": + case "beginbfrange": + token = ""; + tokens = []; + break; + + case "endcodespacerange": + TODO("Support CMap ranges"); + break; + + case "endbfrange": + for (var j = 0; j < tokens.length; j+=3) { + var startRange = parseInt("0x" + tokens[j]); + var endRange = parseInt("0x" + tokens[j+1]); + var code = parseInt("0x" + tokens[j+2]); + + for (var k = startRange; k <= endRange; k++) { + encodingMap[k] = code; + charset.push(code++); + } + } + break; + + case "beginfbchar": + case "endfbchar": + error("fbchar parsing is not implemented"); + break; + + default: + if (token.length) { + tokens.push(token); + token = ""; + } + break; + } + } else if (byte == 0x5B || byte == 0x5D) { + error("CMAP list parsing is not implemented"); + } else { + token += String.fromCharCode(byte); + } + } + } + } var subType = fontDict.get("Subtype"); var bbox = descriptor.get("FontBBox"); From ad7a06b1d5af4f8480e474d7cab16c0dcbc8fee1 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Mon, 20 Jun 2011 03:01:09 +0200 Subject: [PATCH 14/24] Check for more missing tables in TrueType (sigh) --- fonts.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fonts.js b/fonts.js index 281eb94a8..72fabd2e3 100644 --- a/fonts.js +++ b/fonts.js @@ -694,7 +694,7 @@ var TrueType = function(aName, aFile, aProperties) { // 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") { + if (requiredTables.length == 1 && requiredTables[0] == "OS/2") { var OS2 = [ 0x00, 0x03, // version 0x02, 0x24, // xAvgCharWidth @@ -812,11 +812,10 @@ var TrueType = function(aName, aFile, aProperties) { this.data = fontData; return; - } else if (requiredTables.lenght) { - error("Table " + requiredTables[0] + " is missing from the TrueType font"); - } else { - this.data = aFile; + } else if (requiredTables.length) { + warn("Missing " + requiredTables + " in the TrueType font"); } + this.data = aFile; }; TrueType.prototype = { From 75f09304653625d8e973398d33fd92b2741d1d3d Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Mon, 20 Jun 2011 08:20:31 +0200 Subject: [PATCH 15/24] Add more TrueType rewriting magic ('post' table) --- fonts.js | 38 +++++++++++++++++++++++++++++++++++--- pdf.js | 45 +++++++++++++++------------------------------ 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/fonts.js b/fonts.js index 72fabd2e3..172d6fa3c 100644 --- a/fonts.js +++ b/fonts.js @@ -23,6 +23,7 @@ var kMaxWaitForFontFace = 1000; * many fonts are loaded. */ var fontCount = 0; +var fontName = ""; /** * Hold a map of decoded fonts and of the standard fourteen Type1 fonts and @@ -38,6 +39,7 @@ var Fonts = { }, set active(aName) { + fontName = aName; this._active = this[aName]; }, @@ -694,7 +696,7 @@ var TrueType = function(aName, aFile, aProperties) { // 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 == 1 && requiredTables[0] == "OS/2") { + if (requiredTables.length && requiredTables[0] == "OS/2") { var OS2 = [ 0x00, 0x03, // version 0x02, 0x24, // xAvgCharWidth @@ -747,13 +749,43 @@ var TrueType = function(aName, aFile, aProperties) { // Replace the old CMAP table var rewrittedCMAP = this._createCMAPTable(glyphs); - var cmapDelta = rewrittedCMAP.length - originalCMAP.data.length; + var offsetDelta = rewrittedCMAP.length - originalCMAP.data.length; originalCMAP.data = rewrittedCMAP; + // Rewrite the 'post' table if needed + var postTable = null; + for (var i = 0; i < tables.length; i++) { + var table = tables[i]; + if (table.tag == "post") { + postTable = table; + break; + } + } + + if (!postTable) { + var post = [ + 0x00, 0x03, 0x00, 0x00, // Version number + 0x00, 0x00, 0x01, 0x00, // italicAngle + 0x00, 0x00, // underlinePosition + 0x00, 0x00, // underlineThickness + 0x00, 0x00, 0x00, 0x00, // isFixedPitch + 0x00, 0x00, 0x00, 0x00, // minMemType42 + 0x00, 0x00, 0x00, 0x00, // maxMemType42 + 0x00, 0x00, 0x00, 0x00, // minMemType1 + 0x00, 0x00, 0x00, 0x00 // maxMemType1 + ]; + + offsetDelta += post.length; + tables.unshift({ + tag: "post", + data: post + }); + } + // 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 + cmapDelta); + var ttf = new Uint8Array(stream.length + 1024); // The new numbers of tables will be the last one plus the num of missing // tables diff --git a/pdf.js b/pdf.js index 7414ac7ea..cc84115cf 100644 --- a/pdf.js +++ b/pdf.js @@ -1607,10 +1607,7 @@ var CanvasExtraState = (function() { const Encodings = { get ExpertEncoding() { - return shadow(this, "ExpertEncoding", [ - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, + return shadow(this, "ExpertEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "space","exclamsmall","Hungarumlautsmall",,"dollaroldstyle","dollarsuperior", "ampersandsmall","Acutesmall","parenleftsuperior","parenrightsuperior", "twodotenleader","onedotenleader","comma","hyphen","period","fraction", @@ -1645,10 +1642,7 @@ const Encodings = { ]); }, get MacExpertEncoding() { - return shadow(this, "MacExpertEncoding", [ - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, + return shadow(this, "MacExpertEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "space","exclamsmall","Hungarumlautsmall","centoldstyle","dollaroldstyle", "dollarsuperior","ampersandsmall","Acutesmall","parenleftsuperior", "parenrightsuperior","twodotenleader","onedotenleader","comma","hyphen","period", @@ -1682,10 +1676,7 @@ const Encodings = { ]); }, get MacRomanEncoding() { - return shadow(this, "MacRomanEncoding", [ - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, + return shadow(this, "MacRomanEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "space","exclam","quotedbl","numbersign","dollar","percent","ampersand", "quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen", "period","slash","zero","one","two","three","four","five","six","seven","eight", @@ -1715,10 +1706,7 @@ const Encodings = { ]); }, get StandardEncoding() { - return shadow(this, "StandardEncoding", [ - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, + return shadow(this, "StandardEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "space","exclam","quotedbl","numbersign","dollar","percent","ampersand", "quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period", "slash","zero","one","two","three","four","five","six","seven","eight","nine", @@ -1738,10 +1726,7 @@ const Encodings = { ]); }, get WinAnsiEncoding() { - return shadow(this, "WinAnsiEncoding", [ - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, + return shadow(this, "WinAnsiEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "space","exclam","quotedbl","numbersign","dollar","percent","ampersand", "quotesingle","parenleft","parenright","asterisk","plus","comma","hyphen", "period","slash","zero","one","two","three","four","five","six","seven","eight", @@ -1772,10 +1757,7 @@ const Encodings = { ]); }, get zapfDingbatsEncoding() { - return shadow(this, "zapfDingbatsEncoding", [ - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, + return shadow(this, "zapfDingbatsEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "space","a1","a2","a202","a3","a4","a5","a119","a118","a117","a11","a12","a13", "a14","a15","a16","a105","a17","a18","a19","a20","a21","a22","a23","a24","a25", "a26","a27","a28","a6","a7","a8","a9","a10","a29","a30","a31","a32","a33","a34", @@ -1918,13 +1900,12 @@ var CanvasGraphics = (function() { error("FontFile not found for font: " + fontName); fontFile = xref.fetchIfRef(fontFile); - // Generate the custom cmap of the font if needed var encodingMap = {}; var charset = []; if (fontDict.has("Encoding")) { var encoding = xref.fetchIfRef(fontDict.get("Encoding")); if (IsDict(encoding)) { - // Build an map between codes and glyphs + // Build a map between codes and glyphs var differences = encoding.get("Differences"); var index = 0; for (var j = 0; j < differences.length; j++) { @@ -1960,9 +1941,13 @@ var CanvasGraphics = (function() { } else if (fontDict.has("ToUnicode")) { var cmapObj = xref.fetchIfRef(fontDict.get("ToUnicode")); if (IsName(cmapObj)) { - error("ToUnicode basic cmap translation not implemented"); - encodingMap = {}; + error("ToUnicode file cmap translation not implemented"); } else if (IsStream(cmapObj)) { + var encoding = Encodings["WinAnsiEncoding"]; + var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); + for (var i = firstChar; i < encoding.length; i++) + encodingMap[i] = new Name(encoding[i]); + var tokens = []; var token = ""; @@ -1992,8 +1977,8 @@ var CanvasGraphics = (function() { var code = parseInt("0x" + tokens[j+2]); for (var k = startRange; k <= endRange; k++) { - encodingMap[k] = code; - charset.push(code++); + encodingMap[k] = GlyphsUnicode[encoding[code]]; + charset.push(encoding[code++]); } } break; From d7edbe28e97a0be7d951f9111f27fbd4baf00265 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Mon, 20 Jun 2011 21:14:33 +0200 Subject: [PATCH 16/24] Add a way to disable fonts that won't load --- fonts.js | 27 ++++++++++++++++++++++++--- pdf.js | 17 +++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/fonts.js b/fonts.js index 172d6fa3c..273ef5ea6 100644 --- a/fonts.js +++ b/fonts.js @@ -25,6 +25,12 @@ var kMaxWaitForFontFace = 1000; var fontCount = 0; var fontName = ""; +/** + * If for some reason one want to debug without fonts activated, it just need + * to turn this pref to true/false. + */ +var kDisableFonts = false; + /** * Hold a map of decoded fonts and of the standard fourteen Type1 fonts and * their acronyms. @@ -93,6 +99,16 @@ var Font = function(aName, aFile, aProperties) { } fontCount++; + if (aProperties.ignore || kDisableFonts) { + Fonts[aName] = { + data: aFile, + loading: false, + properties: {}, + cache: Object.create(null) + } + return; + } + switch (aProperties.type) { case "Type1": var cff = new CFF(aName, aFile, aProperties); @@ -203,19 +219,21 @@ Font.prototype = { if (debug) ctx.fillText(testString, 20, 20); - var start = Date.now(); var interval = window.setInterval(function canvasInterval(self) { + this.start = this.start || Date.now(); ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; // For some reasons the font has not loaded, so mark it loaded for the // page to proceed but cry - if ((Date.now() - start) >= kMaxWaitForFontFace) { + if ((Date.now() - this.start) >= kMaxWaitForFontFace) { window.clearInterval(interval); Fonts[fontName].loading = false; warn("Is " + fontName + " for charset: " + charset + " loaded?"); + this.start = 0; } else if (textWidth != ctx.measureText(testString).width) { window.clearInterval(interval); Fonts[fontName].loading = false; + this.start = 0; } if (debug) @@ -747,9 +765,12 @@ var TrueType = function(aName, aFile, aProperties) { }); } + + var offsetDelta = 0; + // Replace the old CMAP table var rewrittedCMAP = this._createCMAPTable(glyphs); - var offsetDelta = rewrittedCMAP.length - originalCMAP.data.length; + offsetDelta = rewrittedCMAP.length - originalCMAP.data.length; originalCMAP.data = rewrittedCMAP; // Rewrite the 'post' table if needed diff --git a/pdf.js b/pdf.js index cc84115cf..d39579f74 100644 --- a/pdf.js +++ b/pdf.js @@ -1900,9 +1900,16 @@ var CanvasGraphics = (function() { error("FontFile not found for font: " + fontName); fontFile = xref.fetchIfRef(fontFile); + // Fonts with an embedded cmap but without any assignment in + // it are not yet supported, so ask the fonts loader to ignore + // them to not pay a stupid one sec latence. + var ignoreFont = true; + var encodingMap = {}; var charset = []; if (fontDict.has("Encoding")) { + ignoreFont = false; + var encoding = xref.fetchIfRef(fontDict.get("Encoding")); if (IsDict(encoding)) { // Build a map between codes and glyphs @@ -1960,8 +1967,9 @@ var CanvasGraphics = (function() { error("useCMap is not implemented"); break; - case "begincodespacerange": case "beginbfrange": + ignoreFont = false; + case "begincodespacerange": token = ""; tokens = []; break; @@ -2002,7 +2010,7 @@ var CanvasGraphics = (function() { } } } - } + } var subType = fontDict.get("Subtype"); var bbox = descriptor.get("FontBBox"); @@ -2013,7 +2021,8 @@ var CanvasGraphics = (function() { type: subType.name, encoding: encodingMap, charset: charset, - bbox: bbox + bbox: bbox, + ignore: ignoreFont }; return { @@ -2275,7 +2284,7 @@ var CanvasGraphics = (function() { } this.current.fontSize = size; - this.ctx.font = this.current.fontSize +'px "' + fontName + '"'; + this.ctx.font = this.current.fontSize +'px "' + fontName + '", Symbol'; }, setTextRenderingMode: function(mode) { TODO("text rendering mode"); From ab4ecdcb7381244845deaea905f93b78aeac33be Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Tue, 21 Jun 2011 03:01:59 +0200 Subject: [PATCH 17/24] Merge with master --- pdf.js | 133 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 97 insertions(+), 36 deletions(-) diff --git a/pdf.js b/pdf.js index 2ce6b48a8..d6c478199 100644 --- a/pdf.js +++ b/pdf.js @@ -48,6 +48,14 @@ function shadow(obj, prop, value) { return value; } +function bytesToString(bytes) { + var str = ""; + var length = bytes.length; + for (var n = 0; n < length; ++n) + str += String.fromCharCode(bytes[n]); + return str; +} + var Stream = (function() { function constructor(arrayBuffer, start, length, dict) { this.bytes = Uint8Array(arrayBuffer); @@ -74,9 +82,9 @@ var Stream = (function() { var pos = this.pos; var end = pos + length; var strEnd = this.end; - if (end > strEnd) + if (!end || end > strEnd) end = strEnd; - + this.pos = end; return bytes.subarray(pos, end); }, @@ -233,10 +241,12 @@ var FlateStream = (function() { ]), 5]; function constructor(stream) { - this.stream = stream; + var bytes = stream.getBytes(); + var bytesPos = 0; + this.dict = stream.dict; - var cmf = stream.getByte(); - var flg = stream.getByte(); + var cmf = bytes[bytesPos++]; + var flg = bytes[bytesPos++]; if (cmf == -1 || flg == -1) error("Invalid header in flate stream"); if ((cmf & 0x0f) != 0x08) @@ -245,6 +255,9 @@ var FlateStream = (function() { error("Bad FCHECK in flate stream"); if (flg & 0x20) error("FDICT bit set in flate stream"); + + this.bytes = bytes; + this.bytesPos = bytesPos; this.eof = false; this.codeSize = 0; this.codeBuf = 0; @@ -255,12 +268,14 @@ var FlateStream = (function() { constructor.prototype = { getBits: function(bits) { - var stream = this.stream; var codeSize = this.codeSize; var codeBuf = this.codeBuf; + var bytes = this.bytes; + var bytesPos = this.bytesPos; + var b; while (codeSize < bits) { - if ((b = stream.getByte()) == -1) + if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad encoding in flate stream"); codeBuf |= b << codeSize; codeSize += 8; @@ -268,6 +283,7 @@ var FlateStream = (function() { b = codeBuf & ((1 << bits) - 1); this.codeBuf = codeBuf >> bits; this.codeSize = codeSize -= bits; + this.bytesPos = bytesPos; return b; }, getCode: function(table) { @@ -275,10 +291,12 @@ var FlateStream = (function() { var maxLen = table[1]; var codeSize = this.codeSize; var codeBuf = this.codeBuf; - var stream = this.stream; + var bytes = this.bytes; + var bytesPos = this.bytesPos; + while (codeSize < maxLen) { var b; - if ((b = stream.getByte()) == -1) + if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad encoding in flate stream"); codeBuf |= (b << codeSize); codeSize += 8; @@ -290,6 +308,7 @@ var FlateStream = (function() { error("Bad encoding in flate stream"); this.codeBuf = (codeBuf >> codeLen); this.codeSize = (codeSize - codeLen); + this.bytesPos = bytesPos; return codeVal; }, ensureBuffer: function(requested) { @@ -306,9 +325,8 @@ var FlateStream = (function() { return this.buffer = buffer2; }, getByte: function() { - var bufferLength = this.bufferLength; var pos = this.pos; - if (bufferLength <= pos) { + while (this.bufferLength <= pos) { if (this.eof) return; this.readBlock(); @@ -331,9 +349,8 @@ var FlateStream = (function() { return this.buffer.subarray(pos, end) }, lookChar: function() { - var bufferLength = this.bufferLength; var pos = this.pos; - if (bufferLength <= pos) { + while (this.bufferLength <= pos) { if (this.eof) return; this.readBlock(); @@ -342,16 +359,15 @@ var FlateStream = (function() { }, getChar: function() { var ch = this.lookChar(); - if (!ch) - return; + // shouldnt matter what the position is if we get past the eof + // so no need to check if ch is undefined this.pos++; return ch; }, skip: function(n) { if (!n) n = 1; - while (n-- > 0) - this.getChar(); + this.pos += n; }, generateHuffmanTable: function(lengths) { var n = lengths.length; @@ -397,7 +413,8 @@ var FlateStream = (function() { array[i++] = what; } - var stream = this.stream; + var bytes = this.bytes; + var bytesPos = this.bytesPos; // read block header var hdr = this.getBits(3); @@ -407,16 +424,16 @@ var FlateStream = (function() { var b; if (hdr == 0) { // uncompressed block - if ((b = stream.getByte()) == -1) + if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad block header in flate stream"); var blockLen = b; - if ((b = stream.getByte()) == -1) + if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad block header in flate stream"); blockLen |= (b << 8); - if ((b = stream.getByte()) == -1) + if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad block header in flate stream"); var check = b; - if ((b = stream.getByte()) == -1) + if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad block header in flate stream"); check |= (b << 8); if (check != (~this.blockLen & 0xffff)) @@ -425,7 +442,7 @@ var FlateStream = (function() { var buffer = this.ensureBuffer(bufferLength + blockLen); this.bufferLength = bufferLength + blockLen; for (var n = bufferLength; n < blockLen; ++n) { - if ((b = stream.getByte()) == -1) { + if (typeof (b = bytes[bytesPos++]) == "undefined") { this.eof = true; break; } @@ -509,9 +526,32 @@ var FlateStream = (function() { return constructor; })(); +// A JpegStream can't be read directly. We use the platform to render the underlying +// JPEG data for us. +var JpegStream = (function() { + function constructor(bytes, dict) { + // TODO: per poppler, some images may have "junk" before that need to be removed + this.dict = dict; + + // create DOM image + var img = new Image(); + img.src = "data:image/jpeg;base64," + window.btoa(bytesToString(bytes)); + this.domImage = img; + } + + constructor.prototype = { + getImage: function() { + return this.domImage; + } + }; + + return constructor; +})(); + var PredictorStream = (function() { function constructor(stream, params) { this.stream = stream; + this.dict = stream.dict; this.predictor = params.get("Predictor") || 1; if (this.predictor <= 1) { return stream; // no prediction @@ -1177,15 +1217,15 @@ var Parser = (function() { this.encAlgorithm, this.keyLength); } - stream = this.filter(stream, dict); + stream = this.filter(stream, dict, length); stream.parameters = dict; return stream; }, - filter: function(stream, dict) { + filter: function(stream, dict, length) { var filter = dict.get2("Filter", "F"); var params = dict.get2("DecodeParms", "DP"); if (IsName(filter)) - return this.makeFilter(stream, filter.name, params); + return this.makeFilter(stream, filter.name, length, params); if (IsArray(filter)) { var filterArray = filter; var paramsArray = params; @@ -1196,18 +1236,21 @@ var Parser = (function() { params = null; if (IsArray(paramsArray) && (i in paramsArray)) params = paramsArray[i]; - stream = this.makeFilter(stream, filter.name, params); + stream = this.makeFilter(stream, filter.name, length, params); } } } return stream; }, - makeFilter: function(stream, name, params) { + makeFilter: function(stream, name, length, params) { if (name == "FlateDecode" || name == "Fl") { if (params) { return new PredictorStream(new FlateStream(stream), params); } return new FlateStream(stream); + } else if (name == "DCTDecode") { + var bytes = stream.getBytes(length); + return new JpegStream(bytes, stream.dict); } else { error("filter '" + name + "' not supported yet"); } @@ -2475,12 +2518,19 @@ var CanvasGraphics = (function() { var fontName = ""; var fontDescriptor = font.get("FontDescriptor"); - if (fontDescriptor.num) { + if (fontDescriptor && fontDescriptor.num) { var fontDescriptor = this.xref.fetchIfRef(fontDescriptor); fontName = fontDescriptor.get("FontName").name.replace("+", "_"); Fonts.active = fontName; } + if (!fontName) { + // TODO: fontDescriptor is not available, fallback to default font + this.current.fontSize = size; + this.ctx.font = this.current.fontSize + 'px sans-serif'; + return; + } + this.current.fontSize = size; this.ctx.font = this.current.fontSize +'px "' + fontName + '", Symbol'; }, @@ -2508,9 +2558,9 @@ var CanvasGraphics = (function() { }, showText: function(text) { this.ctx.save(); - this.ctx.translate(0, 2 * this.current.y); - this.ctx.scale(1, -1); this.ctx.transform.apply(this.ctx, this.current.textMatrix); + this.ctx.scale(1, -1); + this.ctx.translate(0, -2 * this.current.y); this.ctx.fillText(Fonts.chars2Unicode(text), this.current.x, this.current.y); this.current.x += this.ctx.measureText(text).width; @@ -2882,10 +2932,21 @@ var CanvasGraphics = (function() { if (w < 1 || h < 1) error("Invalid image width or height"); - + var ctx = this.ctx; // scale the image to the unit square - ctx.scale(1/w, 1/h); + ctx.scale(1/w, -1/h); + + // If the platform can render the image format directly, the + // stream has a getImage property which directly returns a + // suitable DOM Image object. + if (image.getImage) { + var domImage = image.getImage(); + ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, + 0, -h, w, h); + this.restore(); + return; + } var interpolate = dict.get2("Interpolate", "I"); if (!IsBool(interpolate)) @@ -2991,7 +3052,7 @@ var CanvasGraphics = (function() { switch (numComps) { case 1: for (var i = 0; i < length; i += 4) { - var p = imgArray[imageIdx++]; + var p = imgArray[imgIdx++]; pixels[i] = p; pixels[i+1] = p; pixels[i+2] = p; @@ -3018,7 +3079,7 @@ var CanvasGraphics = (function() { switch (numComps) { case 1: for (var i = 0; i < length; i += 4) { - var p = imgArray[imageIdx++]; + var p = imgArray[imgIdx++]; pixels[i] = p; pixels[i+1] = p; pixels[i+2] = p; @@ -3038,7 +3099,7 @@ var CanvasGraphics = (function() { } } tmpCtx.putImageData(imgData, 0, 0); - ctx.drawImage(tmpCanvas, 0, 0); + ctx.drawImage(tmpCanvas, 0, -h); this.restore(); }, From 69c71c9332ce8d79c97ca7127e7a1ed531b12bd6 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Tue, 21 Jun 2011 03:05:28 +0200 Subject: [PATCH 18/24] Fix some leftovers --- pdf.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index d6c478199..b140ef6c1 100644 --- a/pdf.js +++ b/pdf.js @@ -2532,7 +2532,7 @@ var CanvasGraphics = (function() { } this.current.fontSize = size; - this.ctx.font = this.current.fontSize +'px "' + fontName + '", Symbol'; + this.ctx.font = this.current.fontSize +'px "' + fontName + '"'; }, setTextRenderingMode: function(mode) { TODO("text rendering mode"); @@ -2720,7 +2720,7 @@ var CanvasGraphics = (function() { // normalize transform matrix so each step // takes up the entire tmpCanvas (need to remove white borders) if (matrix[1] === 0 && matrix[2] === 0) { - matrix[0] = tmpCanvas.width / xstep; + matrix[0] = tmpCanvas.width / xstep; matrix[3] = tmpCanvas.height / ystep; topLeft = applyMatrix([x0,y0], matrix); } From d37af0a800927f1273b31776c0203345567fe880 Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo Date: Mon, 20 Jun 2011 21:08:50 -0400 Subject: [PATCH 19/24] Replaced zoom control with standard HTML + Zoom -
-
    -
  • 50%
  • -
  • 75%
  • -
  • 100%
  • -
  • 125%
  • -
  • 150%
  • -
  • 200%
  • -
-
diff --git a/multi-page-viewer.js b/multi-page-viewer.js index 6cb46a08a..9d9cec702 100644 --- a/multi-page-viewer.js +++ b/multi-page-viewer.js @@ -11,7 +11,7 @@ var PDFViewer = { previousPageButton: null, nextPageButton: null, pageNumberInput: null, - scaleInput: null, + scaleSelect: null, willJumpToPage: false, @@ -66,92 +66,103 @@ var PDFViewer = { removePage: function(num) { var div = document.getElementById('pageContainer' + num); - if (div && div.hasChildNodes()) { - while (div.childNodes.length > 0) { + if (div) { + while (div.hasChildNodes()) { div.removeChild(div.firstChild); } } }, drawPage: function(num) { - if (PDFViewer.pdf) { - var page = PDFViewer.pdf.getPage(num); - var div = document.getElementById('pageContainer' + num); - - if (div && !div.hasChildNodes()) { - var canvas = document.createElement('canvas'); - canvas.id = 'page' + num; - canvas.mozOpaque = true; - - // Canvas dimensions must be specified in CSS pixels. CSS pixels - // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. - canvas.width = PDFViewer.pageWidth(); - canvas.height = PDFViewer.pageHeight(); - - var ctx = canvas.getContext('2d'); - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var gfx = new CanvasGraphics(ctx); - var fonts = []; + if (!PDFViewer.pdf) { + return; + } - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - page.compile(gfx, fonts); - - var fontsReady = true; - - // Inspect fonts and translate the missing one - var fontCount = fonts.length; - - for (var i = 0; i < fontCount; i++) { - var font = fonts[i]; - - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; - } + var div = document.getElementById('pageContainer' + num); + var canvas = document.createElement('canvas'); + + if (div && !div.hasChildNodes()) { + div.appendChild(canvas); + + var page = PDFViewer.pdf.getPage(num); - new Font(font.name, font.file, font.properties); - - fontsReady = false; + canvas.id = 'page' + num; + canvas.mozOpaque = true; + + // Canvas dimensions must be specified in CSS pixels. CSS pixels + // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. + canvas.width = PDFViewer.pageWidth(); + canvas.height = PDFViewer.pageHeight(); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + var gfx = new CanvasGraphics(ctx); + var fonts = []; + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + page.compile(gfx, fonts); + + var areFontsReady = true; + + // Inspect fonts and translate the missing one + var fontCount = fonts.length; + + for (var i = 0; i < fontCount; i++) { + var font = fonts[i]; + + if (Fonts[font.name]) { + areFontsReady = areFontsReady && !Fonts[font.name].loading; + continue; } - var pageInterval; - var delayLoadFont = function() { - for (var i = 0; i < fontCount; i++) { - if (Fonts[font.name].loading) { - return; - } - } - - clearInterval(pageInterval); - - PDFViewer.drawPage(num); - } - - if (!fontsReady) { - pageInterval = setInterval(delayLoadFont, 10); - return; - } - - page.display(gfx); - div.appendChild(canvas); + new Font(font.name, font.file, font.properties); + + areFontsReady = false; } + + var pageInterval; + + var delayLoadFont = function() { + for (var i = 0; i < fontCount; i++) { + if (Fonts[font.name].loading) { + return; + } + } + + clearInterval(pageInterval); + + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + + PDFViewer.drawPage(num); + } + + if (!areFontsReady) { + pageInterval = setInterval(delayLoadFont, 10); + return; + } + + page.display(gfx); } }, changeScale: function(num) { - while (PDFViewer.element.childNodes.length > 0) { + while (PDFViewer.element.hasChildNodes()) { PDFViewer.element.removeChild(PDFViewer.element.firstChild); } PDFViewer.scale = num / 100; + var i; + if (PDFViewer.pdf) { - for (var i = 1; i <= PDFViewer.numberOfPages; i++) { + for (i = 1; i <= PDFViewer.numberOfPages; i++) { PDFViewer.createPage(i); } @@ -160,7 +171,21 @@ var PDFViewer = { } } - PDFViewer.scaleInput.value = Math.floor(PDFViewer.scale * 100) + '%'; + for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { + var option = PDFViewer.scaleSelect.childNodes[i]; + + if (option.value == num) { + if (!option.selected) { + option.selected = 'selected'; + } + } else { + if (option.selected) { + option.removeAttribute('selected'); + } + } + } + + PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; }, goToPage: function(num) { @@ -217,6 +242,11 @@ var PDFViewer = { if (PDFViewer.numberOfPages > 0) { PDFViewer.drawPage(1); } + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; } }; @@ -320,40 +350,14 @@ window.onload = function() { this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; }; - PDFViewer.scaleInput = document.getElementById('scale'); - PDFViewer.scaleInput.buttonElement = document.getElementById('scaleComboBoxButton'); - PDFViewer.scaleInput.buttonElement.listElement = document.getElementById('scaleComboBoxList'); - PDFViewer.scaleInput.onchange = function(evt) { + PDFViewer.scaleSelect = document.getElementById('scaleSelect'); + PDFViewer.scaleSelect.onchange = function(evt) { PDFViewer.changeScale(parseInt(this.value)); }; - PDFViewer.scaleInput.buttonElement.onclick = function(evt) { - this.listElement.style.display = (this.listElement.style.display === 'block') ? 'none' : 'block'; - }; - PDFViewer.scaleInput.buttonElement.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.scaleInput.buttonElement.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.scaleInput.buttonElement.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - var listItems = PDFViewer.scaleInput.buttonElement.listElement.getElementsByTagName('LI'); - - for (var i = 0; i < listItems.length; i++) { - var listItem = listItems[i]; - listItem.onclick = function(evt) { - PDFViewer.changeScale(parseInt(this.innerHTML)); - PDFViewer.scaleInput.buttonElement.listElement.style.display = 'none'; - }; - } - PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; - PDFViewer.scale = parseInt(PDFViewer.scaleInput.value) / 100 || 1.0; + PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; + PDFViewer.open(PDFViewer.queryParams.file || PDFViewer.url); window.onscroll = function(evt) { From 81532e9c03e9a7b1d3c5b8e02a39bb30ccf98ee2 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Tue, 21 Jun 2011 04:30:28 +0200 Subject: [PATCH 20/24] Rename s2a, s16, s32 and do some dance inside the bind() code --- fonts.js | 223 ++++++++++++++++++++++++++++--------------------------- pdf.js | 2 +- 2 files changed, 116 insertions(+), 109 deletions(-) diff --git a/fonts.js b/fonts.js index 53db045f0..2f8a6c3df 100644 --- a/fonts.js +++ b/fonts.js @@ -43,38 +43,39 @@ var Fonts = { this._active = this[aName]; }, - chars2Unicode: function(chars) { + charsToUnicode: function fonts_chars2Unicode(chars) { var active = this._active; if (!active) return chars; // if we translated this string before, just grab it from the cache - var ret = active.cache[chars]; - if (ret) - return ret; + var str = active.cache[chars] || ""; + if (str) + return str; // translate the string using the font's encoding var encoding = active.properties.encoding; if (!encoding) return chars; - var ret = ""; for (var i = 0; i < chars.length; ++i) { - var ch = chars.charCodeAt(i); - var uc = encoding[ch]; - if (uc instanceof Name) // we didn't convert the glyph yet - uc = encoding[ch] = GlyphsUnicode[uc.name]; - if (uc > 0xffff) { // handle surrogate pairs - ret += String.fromCharCode(uc & 0xffff); - uc >>= 16; + var charcode = chars.charCodeAt(i); + var unicode = encoding[charcode]; + + // Check if the glyph has already been converted + if (unicode instanceof Name) + unicode = encoding[unicode] = GlyphsUnicode[unicode.name]; + + // Handle surrogate pairs + if (unicode > 0xFFFF) { + str += String.fromCharCode(unicode & 0xFFFF); + unicode >>= 16; } - ret += String.fromCharCode(uc); + str += String.fromCharCode(unicode); } - // enter the translated string into the cache - active.cache[chars] = ret; - - return ret; + // Enter the translated string into the cache + return active.cache[chars] = str; } }; @@ -99,6 +100,8 @@ var Font = (function () { fontCount++; fontName = aName; + // If the font is to be ignored, register it like an already loaded font + // to avoid the cost of waiting for it be be loaded by the platform. if (aProperties.ignore || kDisableFonts) { Fonts[aName] = { data: aFile, @@ -168,31 +171,12 @@ var Font = (function () { bind: function font_bind() { var data = this.font; - - // Get the base64 encoding of the binary font data - var str = ""; - var length = data.length; - for (var i = 0; i < length; ++i) - str += String.fromCharCode(data[i]); - - var dataBase64 = window.btoa(str); var fontName = this.name; /** Hack begin */ // Actually there is not event when a font has finished downloading so - // the following tons of code are a dirty hack to 'guess' when a font is - // ready - var debug = false; - - if (debug) { - var name = document.createElement("font"); - name.setAttribute("style", "position: absolute; left: 20px; top: " + - (100 * fontCount + 60) + "px"); - name.innerHTML = fontName; - document.body.appendChild(name); - } - + // the following code are a dirty hack to 'guess' when a font is ready var canvas = document.createElement("canvas"); var style = "border: 1px solid black; position:absolute; top: " + (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px"; @@ -201,33 +185,42 @@ var Font = (function () { canvas.setAttribute("heigth", 100); document.body.appendChild(canvas); - // Retrieve font charset - var charset = Fonts[fontName].properties.charset || []; - - // if the charset is too small make it repeat a few times - var count = 30; - while (count-- && charset.length <= 30) - charset = charset.concat(charset.slice()); - // Get the font size canvas think it will be for 'spaces' var ctx = canvas.getContext("2d"); - var testString = " "; + ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + var testString = " "; // When debugging use the characters provided by the charsets to visually - // see what's happening + // see what's happening instead of 'spaces' + var debug = false; if (debug) { + var name = document.createElement("font"); + name.setAttribute("style", "position: absolute; left: 20px; top: " + + (100 * fontCount + 60) + "px"); + name.innerHTML = fontName; + document.body.appendChild(name); + + // Retrieve font charset + var charset = Fonts[fontName].properties.charset || []; + + // if the charset is too small make it repeat a few times + var count = 30; + while (count-- && charset.length <= 30) + charset = charset.concat(charset.slice()); + for (var i = 0; i < charset.length; i++) { var unicode = GlyphsUnicode[charset[i]]; if (!unicode) continue; testString += String.fromCharCode(unicode); } - } - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - var textWidth = ctx.measureText(testString).width; - if (debug) ctx.fillText(testString, 20, 20); + } + + // Periodicaly check for the width of the testString, it will be + // different once the real font has loaded + var textWidth = ctx.measureText(testString).width; var interval = window.setInterval(function canvasInterval(self) { this.start = this.start || Date.now(); @@ -248,12 +241,20 @@ var Font = (function () { if (debug) ctx.fillText(testString, 20, 50); - }, 50, this); + }, 30, this); /** Hack end */ + // Get the base64 encoding of the binary font data + var str = ""; + var length = data.length; + for (var i = 0; i < length; ++i) + str += String.fromCharCode(data[i]); + + var base64 = window.btoa(str); + // Add the @font-face rule to the document - var url = "url(data:" + this.mimetype + ";base64," + dataBase64 + ");"; + var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.length); @@ -262,20 +263,23 @@ var Font = (function () { cover: function font_cover(aName, aFont, aProperties) { var otf = Uint8Array(kMaxFontFileSize); - function s2a(s) { - var a = []; - for (var i = 0; i < s.length; ++i) - a[i] = s.charCodeAt(i); - return a; + function stringToArray(str) { + var array = []; + for (var i = 0; i < str.length; ++i) + array[i] = str.charCodeAt(i); + return array; } - function s16(value) { - return String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff); + function string16(value) { + return String.fromCharCode((value >> 8) & 0xff) + + String.fromCharCode(value & 0xff); } - function s32(value) { - return String.fromCharCode((value >> 24) & 0xff) + String.fromCharCode((value >> 16) & 0xff) + - String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff); + function string32(value) { + return String.fromCharCode((value >> 24) & 0xff) + + String.fromCharCode((value >> 16) & 0xff) + + String.fromCharCode((value >> 8) & 0xff) + + String.fromCharCode(value & 0xff); } function createOpenTypeHeader(aFile, aOffsets, numTables) { @@ -285,20 +289,20 @@ var Font = (function () { header += "\x4F\x54\x54\x4F"; // numTables (2 bytes) - header += s16(numTables); + header += string16(numTables); // searchRange (2 bytes) var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables); var searchRange = tablesMaxPower2 * 16; - header += s16(searchRange); + header += string16(searchRange); // entrySelector (2 bytes) - header += s16(Math.log(tablesMaxPower2) / Math.log(2)); + header += string16(Math.log(tablesMaxPower2) / Math.log(2)); // rangeShift (2 bytes) - header += s16(numTables * 16 - searchRange); + header += string16(numTables * 16 - searchRange); - aFile.set(s2a(header), aOffsets.currentOffset); + aFile.set(stringToArray(header), aOffsets.currentOffset); aOffsets.currentOffset += header.length; aOffsets.virtualOffset += header.length; } @@ -322,25 +326,27 @@ var Font = (function () { offset + length; - var tableEntry = aTag + s32(checksum) + s32(offset) + s32(length); - tableEntry = s2a(tableEntry); + var tableEntry = aTag + string32(checksum) + string32(offset) + string32(length); + tableEntry = stringToArray(tableEntry); aFile.set(tableEntry, aOffsets.currentOffset); + aOffsets.currentOffset += tableEntry.length; aOffsets.virtualOffset += aData.length; } function createNameTable(aName) { - var names = - "See original licence" + // Copyright - aName + // Font family - "undefined" + // Font subfamily (font weight) - "uniqueID" + // Unique ID - aName + // Full font name - "0.1" + // Version - "undefined" + // Postscript name - "undefined" + // Trademark - "undefined" + // Manufacturer - "undefined"; // Designer + var names = [ + "See original licence", // Copyright + aName, // Font family + "undefined", // Font subfamily (font weight) + "uniqueID", // Unique ID + aName, // Full font name + "0.1", // Version + "undefined", // Postscript name + "undefined", // Trademark + "undefined", // Manufacturer + "undefined" // Designer + ]; var name = "\x00\x00" + // format @@ -357,14 +363,14 @@ var Font = (function () { "\x00\x00" + // encoding ID "\x00\x00" + // language ID "\x00\x00" + // name ID - s16(str.length) + - s16(strOffset); + string16(str.length) + + string16(strOffset); name += nameRecord; strOffset += str.length; } - name += names; + name += names.join(""); return name; } @@ -407,12 +413,12 @@ var Font = (function () { "\x00\x01" + // encodingID "\x00\x00\x00\x0C" + // start of the table record "\x00\x04" + // format - s16(headerSize) + // length + string16(headerSize) + // length "\x00\x00" + // languages - s16(segCount2) + - s16(searchRange) + - s16(searchEntry) + - s16(rangeShift); + string16(segCount2) + + string16(searchRange) + + string16(searchEntry) + + string16(rangeShift); // Fill up the 4 parallel arrays describing the segments. var startCount = ""; @@ -428,10 +434,10 @@ var Font = (function () { var delta = (((start - 1) - bias) ^ 0xffff) + 1; bias += (end - start + 1); - startCount += s16(start); - endCount += s16(end); - idDeltas += s16(delta); - idRangeOffsets += s16(0); + startCount += string16(start); + endCount += string16(end); + idDeltas += string16(delta); + idRangeOffsets += string16(0); for (var j = start; j <= end; j++) glyphsIds += String.fromCharCode(j); @@ -442,7 +448,7 @@ var Font = (function () { idDeltas += "\x00\x01"; idRangeOffsets += "\x00\x00"; - return s2a(cmap + endCount + "\x00\x00" + startCount + + return stringToArray(cmap + endCount + "\x00\x00" + startCount + idDeltas + idRangeOffsets + glyphsIds); } @@ -477,7 +483,7 @@ var Font = (function () { createTableEntry(otf, offsets, "CFF ", CFF); /** OS/2 */ - OS2 = s2a( + OS2 = stringToArray( "\x00\x03" + // version "\x02\x24" + // xAvgCharWidth "\x01\xF4" + // usWeightClass @@ -526,7 +532,7 @@ var Font = (function () { createTableEntry(otf, offsets, "cmap", cmap); /** HEAD */ - head = s2a( + head = stringToArray( "\x00\x01\x00\x00" + // Version number "\x00\x00\x50\x00" + // fontRevision "\x00\x00\x00\x00" + // checksumAdjustement @@ -548,7 +554,7 @@ var Font = (function () { createTableEntry(otf, offsets, "head", head); /** HHEA */ - hhea = s2a( + hhea = stringToArray( "\x00\x01\x00\x00" + // Version number "\x00\x00" + // Typographic Ascent "\x00\x00" + // Typographic Descent @@ -565,7 +571,7 @@ var Font = (function () { "\x00\x00" + // -reserved- "\x00\x00" + // -reserved- "\x00\x00" + // metricDataFormat - s16(charstrings.length) + string16(charstrings.length) ); createTableEntry(otf, offsets, "hhea", hhea); @@ -575,19 +581,19 @@ var Font = (function () { var charstring = charstrings[i].charstring; var width = charstring[1]; var lsb = charstring[0]; - hmtx += s16(width) + s16(lsb); + hmtx += string16(width) + string16(lsb); } - hmtx = s2a(hmtx); + hmtx = stringToArray(hmtx); createTableEntry(otf, offsets, "hmtx", hmtx); /** MAXP */ maxp = "\x00\x00\x50\x00" + // Version number - s16(charstrings.length + 1); // Num of glyphs (+1 to pass the sanitizer...) - maxp = s2a(maxp); + string16(charstrings.length + 1); // Num of glyphs (+1 to pass the sanitizer...) + maxp = stringToArray(maxp); createTableEntry(otf, offsets, "maxp", maxp); /** NAME */ - name = s2a(createNameTable(aName)); + name = stringToArray(createNameTable(aName)); createTableEntry(otf, offsets, "name", name); /** POST */ @@ -601,7 +607,7 @@ var Font = (function () { "\x00\x00\x00\x00" + // maxMemType42 "\x00\x00\x00\x00" + // minMemType1 "\x00\x00\x00\x00"; // maxMemType1 - post = s2a(post); + post = stringToArray(post); createTableEntry(otf, offsets, "post", post); // Once all the table entries header are written, dump the data! @@ -622,6 +628,7 @@ var Font = (function () { return constructor; })(); + /** * FontsUtils is a static class dedicated to hold codes that are not related * to fonts in particular and needs to be share between them. @@ -1300,6 +1307,10 @@ var Type1Parser = function() { } }; +/** + * The CFF class takes a Type1 file and wrap it into a 'Compact Font Format', + * which itself embed Type2 charstrings. + */ const CFFStrings = [ ".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand", "quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period", @@ -1358,10 +1369,6 @@ const CFFStrings = [ "001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold" ]; -/** - * Take a Type1 file as input and wrap it into a Compact Font Format (CFF) - * wrapping Type2 charstrings. - */ var CFF = function(aName, aFile, aProperties) { // Get the data block containing glyphs and subrs informations var length1 = aFile.dict.get("Length1"); diff --git a/pdf.js b/pdf.js index b140ef6c1..5c49f14dd 100644 --- a/pdf.js +++ b/pdf.js @@ -2561,7 +2561,7 @@ var CanvasGraphics = (function() { this.ctx.transform.apply(this.ctx, this.current.textMatrix); this.ctx.scale(1, -1); this.ctx.translate(0, -2 * this.current.y); - this.ctx.fillText(Fonts.chars2Unicode(text), this.current.x, this.current.y); + this.ctx.fillText(Fonts.charsToUnicode(text), this.current.x, this.current.y); this.current.x += this.ctx.measureText(text).width; this.ctx.restore(); From 083256634c8b6286d7847fcf204f5f16f9abc8b6 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Tue, 21 Jun 2011 04:35:28 +0200 Subject: [PATCH 21/24] Add a name to some anonyous functions --- fonts.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fonts.js b/fonts.js index 2f8a6c3df..ff749a3f5 100644 --- a/fonts.js +++ b/fonts.js @@ -88,7 +88,7 @@ var Fonts = { * type1Font.bind(); */ var Font = (function () { - var constructor = function(aName, aFile, aProperties) { + var constructor = function font_constructor(aName, aFile, aProperties) { this.name = aName; this.encoding = aProperties.encoding; @@ -833,7 +833,7 @@ var TrueType = function(aName, aFile, aProperties) { }); // Tables needs to be written by ascendant alphabetic order - tables.sort(function(a, b) { + tables.sort(function tables_sort(a, b) { return a.tag > b.tag; }); @@ -1452,7 +1452,7 @@ CFF.prototype = { } }; - charstrings.sort(function(a, b) { + charstrings.sort(function charstrings_sort(a, b) { return a.unicode > b.unicode; }); return charstrings; @@ -1480,7 +1480,7 @@ CFF.prototype = { "hvcurveto": 31, }, - flattenCharstring: function(aGlyph, aCharstring, aSubrs) { + flattenCharstring: function flattenCharstring(aGlyph, aCharstring, aSubrs) { var i = 0; while (true) { var obj = aCharstring[i]; @@ -1584,7 +1584,7 @@ CFF.prototype = { error("failing with i = " + i + " in charstring:" + aCharstring + "(" + aCharstring.length + ")"); }, - wrap: function(aName, aProperties) { + wrap: function wrap(aName, aProperties) { var charstrings = this.getOrderedCharStrings(aProperties.glyphs); // Starts the conversion of the Type1 charstrings to Type2 From 825f9249b23d247b78ab8eb6fa685c373362e4fa Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Mon, 20 Jun 2011 22:39:49 -0400 Subject: [PATCH 22/24] restore getChar in JpegStream --- pdf.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pdf.js b/pdf.js index 40044300c..1beeb6ca4 100644 --- a/pdf.js +++ b/pdf.js @@ -542,6 +542,9 @@ var JpegStream = (function() { constructor.prototype = { getImage: function() { return this.domImage; + }, + getChar: function() { + error("internal error: getChar is not valid on JpegStream"); } }; From cfda084714a466e7ac060aca678492f809a9a856 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Mon, 20 Jun 2011 22:55:32 -0500 Subject: [PATCH 23/24] Fixes 09061a2cc48da67768b regression, spaces, and default value for type --- pdf.js | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/pdf.js b/pdf.js index 1beeb6ca4..40e05cf98 100644 --- a/pdf.js +++ b/pdf.js @@ -1444,26 +1444,29 @@ var XRef = (function() { for (i = 0; i < n; ++i) { var type = 0, offset = 0, generation = 0; for (j = 0; j < typeFieldWidth; ++j) - type = (type << 8) | stream.getByte(); + type = (type << 8) | stream.getByte(); + // if type field is absent, its default value = 1 + if (typeFieldWidth == 0) + type = 1; for (j = 0; j < offsetFieldWidth; ++j) - offset = (offset << 8) | stream.getByte(); + offset = (offset << 8) | stream.getByte(); for (j = 0; j < generationFieldWidth; ++j) - generation = (generation << 8) | stream.getByte(); - var entry = new Ref(offset, generation); - if (typeFieldWidth > 0) { - switch (type) { - case 0: - entry.free = true; - break; - case 1: - entry.uncompressed = true; - break; - case 2: - break; - default: - error("Invalid XRef entry type"); - break; - } + generation = (generation << 8) | stream.getByte(); + var entry = {} + entry.offset = offset; + entry.gen = generation; + switch (type) { + case 0: + entry.free = true; + break; + case 1: + entry.uncompressed = true; + break; + case 2: + break; + default: + error("Invalid XRef entry type"); + break; } if (!this.entries[first + i]) this.entries[first + i] = entry; From ff6aaa02eea318dd2df2de8aefdf2644683d2197 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Tue, 21 Jun 2011 06:49:59 +0200 Subject: [PATCH 24/24] Get rid of the TrueType class, adapt the code to conventions and new code --- fonts.js | 1513 +++++++++++++++++++++++------------------------------- pdf.js | 3 +- 2 files changed, 647 insertions(+), 869 deletions(-) diff --git a/fonts.js b/fonts.js index ff749a3f5..ad3d4fd35 100644 --- a/fonts.js +++ b/fonts.js @@ -39,8 +39,8 @@ var Fonts = { return this._active; }, - set active(aName) { - this._active = this[aName]; + set active(name) { + this._active = this[name]; }, charsToUnicode: function fonts_chars2Unicode(chars) { @@ -49,7 +49,7 @@ var Fonts = { return chars; // if we translated this string before, just grab it from the cache - var str = active.cache[chars] || ""; + var str = active.cache[chars]; if (str) return str; @@ -58,6 +58,7 @@ var Fonts = { if (!encoding) return chars; + str = ""; for (var i = 0; i < chars.length; ++i) { var charcode = chars.charCodeAt(i); var unicode = encoding[charcode]; @@ -88,23 +89,23 @@ var Fonts = { * type1Font.bind(); */ var Font = (function () { - var constructor = function font_constructor(aName, aFile, aProperties) { - this.name = aName; - this.encoding = aProperties.encoding; + var constructor = function font_constructor(name, file, properties) { + this.name = name; + this.encoding = properties.encoding; // If the font has already been decoded simply return it - if (Fonts[aName]) { - this.font = Fonts[aName].data; + if (Fonts[name]) { + this.font = Fonts[name].data; return; } fontCount++; - fontName = aName; + fontName = name; // If the font is to be ignored, register it like an already loaded font // to avoid the cost of waiting for it be be loaded by the platform. - if (aProperties.ignore || kDisableFonts) { - Fonts[aName] = { - data: aFile, + if (properties.ignore || properties.type == "TrueType" || kDisableFonts) { + Fonts[name] = { + data: file, loading: false, properties: {}, cache: Object.create(null) @@ -112,41 +113,31 @@ var Font = (function () { return; } - switch (aProperties.type) { + switch (properties.type) { case "Type1": - var cff = new CFF(aName, aFile, aProperties); + var cff = new CFF(name, file, properties); this.mimetype = "font/opentype"; // Wrap the CFF data inside an OTF font file - this.font = this.cover(aName, cff, aProperties); + this.font = this.convert(name, cff, properties); break; case "TrueType": - // TrueType is disabled for the moment since the sanitizer prevent it - // from loading due to missing tables - return Fonts[aName] = { - data: null, - properties: { - encoding: {}, - charset: null - }, - loading: false, - cache: Object.create(null) - }; - this.mimetype = "font/opentype"; - var ttf = new TrueType(aName, aFile, aProperties); - this.font = ttf.data; + + // Repair the TrueType file if it is can be damaged in the point of + // view of the sanitizer + this.font = this.checkAndRepair(name, file, properties); break; default: - warn("Font " + aProperties.type + " is not supported"); + warn("Font " + properties.type + " is not supported"); break; } - Fonts[aName] = { + Fonts[name] = { data: this.font, - properties: aProperties, + properties: properties, loading: true, cache: Object.create(null) } @@ -155,6 +146,200 @@ var Font = (function () { this.bind(); }; + function stringToArray(str) { + var array = []; + for (var i = 0; i < str.length; ++i) + array[i] = str.charCodeAt(i); + + return array; + }; + + function string16(value) { + return String.fromCharCode((value >> 8) & 0xff) + + String.fromCharCode(value & 0xff); + }; + + function string32(value) { + return String.fromCharCode((value >> 24) & 0xff) + + String.fromCharCode((value >> 16) & 0xff) + + String.fromCharCode((value >> 8) & 0xff) + + String.fromCharCode(value & 0xff); + }; + + function createOpenTypeHeader(sfnt, file, offsets, numTables) { + // sfnt version (4 bytes) + var header = sfnt; + + // numTables (2 bytes) + header += string16(numTables); + + // searchRange (2 bytes) + var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables); + var searchRange = tablesMaxPower2 * 16; + header += string16(searchRange); + + // entrySelector (2 bytes) + header += string16(Math.log(tablesMaxPower2) / Math.log(2)); + + // rangeShift (2 bytes) + header += string16(numTables * 16 - searchRange); + + file.set(stringToArray(header), offsets.currentOffset); + offsets.currentOffset += header.length; + offsets.virtualOffset += header.length; + }; + + function createTableEntry(file, offsets, tag, data) { + // offset + var offset = offsets.virtualOffset; + + // Per spec tables must be 4-bytes align so add padding as needed + while (data.length & 3) + data.push(0x00); + + while (offsets.virtualOffset & 3) + offsets.virtualOffset++; + + // length + var length = data.length; + + // checksum + var checksum = tag.charCodeAt(0) + + tag.charCodeAt(1) + + tag.charCodeAt(2) + + tag.charCodeAt(3) + + offset + + length; + + var tableEntry = tag + string32(checksum) + string32(offset) + string32(length); + tableEntry = stringToArray(tableEntry); + file.set(tableEntry, offsets.currentOffset); + + offsets.currentOffset += tableEntry.length; + offsets.virtualOffset += data.length; + }; + + function getRanges(glyphs) { + // Array.sort() sorts by characters, not numerically, so convert to an + // array of characters. + var codes = []; + var length = glyphs.length; + for (var n = 0; n < length; ++n) + codes.push(String.fromCharCode(glyphs[n].unicode)) + codes.sort(); + + // Split the sorted codes into ranges. + var ranges = []; + for (var n = 0; n < length; ) { + var start = codes[n++].charCodeAt(0); + var end = start; + while (n < length && end + 1 == codes[n].charCodeAt(0)) { + ++end; + ++n; + } + ranges.push([start, end]); + } + return ranges; + }; + + function createCMAPTable(glyphs) { + var ranges = getRanges(glyphs); + + var headerSize = (12 * 2 + (ranges.length * 4 * 2)); + var segCount = ranges.length + 1; + var segCount2 = segCount * 2; + var searchRange = FontsUtils.getMaxPower2(segCount) * 2; + var searchEntry = Math.log(segCount) / Math.log(2); + var rangeShift = 2 * segCount - searchRange; + + var cmap = "\x00\x00" + // version + "\x00\x01" + // numTables + "\x00\x03" + // platformID + "\x00\x01" + // encodingID + "\x00\x00\x00\x0C" + // start of the table record + "\x00\x04" + // format + string16(headerSize) + // length + "\x00\x00" + // languages + string16(segCount2) + + string16(searchRange) + + string16(searchEntry) + + string16(rangeShift); + + // Fill up the 4 parallel arrays describing the segments. + var startCount = ""; + var endCount = ""; + var idDeltas = ""; + var idRangeOffsets = ""; + var glyphsIds = ""; + var bias = 0; + for (var i = 0; i < segCount - 1; i++) { + var range = ranges[i]; + var start = range[0]; + var end = range[1]; + var delta = (((start - 1) - bias) ^ 0xffff) + 1; + bias += (end - start + 1); + + startCount += string16(start); + endCount += string16(end); + idDeltas += string16(delta); + idRangeOffsets += string16(0); + + for (var j = start; j <= end; j++) + glyphsIds += String.fromCharCode(j); + } + + startCount += "\xFF\xFF"; + endCount += "\xFF\xFF"; + idDeltas += "\x00\x01"; + idRangeOffsets += "\x00\x00"; + + return stringToArray(cmap + endCount + "\x00\x00" + startCount + + idDeltas + idRangeOffsets + glyphsIds); + }; + + function createOS2Table() { + var OS2 = stringToArray( + "\x00\x03" + // version + "\x02\x24" + // xAvgCharWidth + "\x01\xF4" + // usWeightClass + "\x00\x05" + // usWidthClass + "\x00\x00" + // fstype + "\x02\x8A" + // ySubscriptXSize + "\x02\xBB" + // ySubscriptYSize + "\x00\x00" + // ySubscriptXOffset + "\x00\x8C" + // ySubscriptYOffset + "\x02\x8A" + // ySuperScriptXSize + "\x02\xBB" + // ySuperScriptYSize + "\x00\x00" + // ySuperScriptXOffset + "\x01\xDF" + // ySuperScriptYOffset + "\x00\x31" + // yStrikeOutSize + "\x01\x02" + // yStrikeOutPosition + "\x00\x00" + // sFamilyClass + "\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose + "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31) + "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63) + "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95) + "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127) + "\x2A\x32\x31\x2A" + // achVendID + "\x00\x20" + // fsSelection + "\x00\x2D" + // usFirstCharIndex + "\x00\x7A" + // usLastCharIndex + "\x00\x03" + // sTypoAscender + "\x00\x20" + // sTypeDescender + "\x00\x38" + // sTypoLineGap + "\x00\x5A" + // usWinAscent + "\x02\xB4" + // usWinDescent + "\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31) + "\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63) + "\x00\x00" + // sxHeight + "\x00\x00" + // sCapHeight + "\x00\x01" + // usDefaultChar + "\x00\xCD" + // usBreakChar + "\x00\x02" // usMaxContext + ); + return OS2; + }; + /** * A bunch of the OpenType code is duplicate between this class and the * TrueType code, this is intentional and will merge in a future version @@ -169,6 +354,347 @@ var Font = (function () { mimetype: null, encoding: null, + checkAndRepair: function font_checkAndRepair(name, font, properties) { + function readTableEntry(file) { + // tag + var tag = file.getBytes(4); + tag = String.fromCharCode(tag[0]) + + String.fromCharCode(tag[1]) + + String.fromCharCode(tag[2]) + + String.fromCharCode(tag[3]); + + var checksum = FontsUtils.bytesToInteger(file.getBytes(4)); + var offset = FontsUtils.bytesToInteger(file.getBytes(4)); + var length = FontsUtils.bytesToInteger(file.getBytes(4)); + + // Read the table associated data + var currentPosition = file.pos; + file.pos = file.start + offset; + + var data = file.getBytes(length); + file.pos = currentPosition; + + return { + tag: tag, + checksum: checksum, + length: offset, + offset: length, + data: data + } + }; + + function readOpenTypeHeader(ttf) { + return { + version: ttf.getBytes(4), + numTables: FontsUtils.bytesToInteger(ttf.getBytes(2)), + searchRange: FontsUtils.bytesToInteger(ttf.getBytes(2)), + entrySelector: FontsUtils.bytesToInteger(ttf.getBytes(2)), + rangeShift: FontsUtils.bytesToInteger(ttf.getBytes(2)) + } + }; + + // Check that required tables are present + var requiredTables = [ "OS/2", "cmap", "head", "hhea", + "hmtx", "maxp", "name", "post" ]; + + 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; + + var tables = []; + for (var i = 0; i < numTables; i++) { + var table = readTableEntry(font); + var index = requiredTables.indexOf(table.tag); + if (index != -1) { + if (table.tag == "cmap") + cmap = table; + else if (table.tag == "post") + post = 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 = 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 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 + var OS2 = createOS2Table(); + tables.push({ + tag: "OS/2", + data: OS2 + }); + + // If the font is missing a OS/2 table it's could be an old mac font + // without a 3-1-4 Unicode BMP table, so let's rewrite it. + var charset = properties.charset; + var glyphs = []; + for (var i = 0; i < charset.length; i++) { + glyphs.push({ + unicode: GlyphsUnicode[charset[i]] + }); + } + + // Replace the old CMAP table with a shiny new one + cmap.data = createCMAPTable(glyphs); + + // Rewrite the 'post' table if needed + if (!post) { + post = + "\x00\x03\x00\x00" + // Version number + "\x00\x00\x01\x00" + // italicAngle + "\x00\x00" + // underlinePosition + "\x00\x00" + // underlineThickness + "\x00\x00\x00\x00" + // isFixedPitch + "\x00\x00\x00\x00" + // minMemType42 + "\x00\x00\x00\x00" + // maxMemType42 + "\x00\x00\x00\x00" + // minMemType1 + "\x00\x00\x00\x00"; // maxMemType1 + + tables.unshift({ + tag: "post", + data: stringToArray(post) + }); + } + + // Tables needs to be written by ascendant alphabetic order + tables.sort(function tables_sort(a, b) { + return 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(); + }, + + convert: function font_convert(name, font, properties) { + var otf = Uint8Array(kMaxFontFileSize); + + function createNameTable(name) { + var names = [ + "See original licence", // Copyright + name, // Font family + "undefined", // Font subfamily (font weight) + "uniqueID", // Unique ID + name, // Full font name + "0.1", // Version + "undefined", // Postscript name + "undefined", // Trademark + "undefined", // Manufacturer + "undefined" // Designer + ]; + + var name = + "\x00\x00" + // format + "\x00\x0A" + // Number of names Record + "\x00\x7E"; // Storage + + // Build the name records field + var strOffset = 0; + for (var i = 0; i < names.length; i++) { + var str = names[i]; + + var nameRecord = + "\x00\x01" + // platform ID + "\x00\x00" + // encoding ID + "\x00\x00" + // language ID + "\x00\x00" + // name ID + string16(str.length) + + string16(strOffset); + name += nameRecord; + + strOffset += str.length; + } + + name += names.join(""); + return name; + } + + // Required Tables + var CFF = + font.data, // PostScript Font Program + OS2, // OS/2 and Windows Specific metrics + cmap, // Character to glyphs mapping + head, // Font eader + hhea, // Horizontal header + hmtx, // Horizontal metrics + maxp, // Maximum profile + name, // Naming tables + post; // PostScript informations + var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post]; + + // 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: tables.length * (4 * 4) + }; + + // It there is only one font, offset table is the first bytes of the file + createOpenTypeHeader("\x4F\x54\x54\x4F", otf, offsets, tables.length); + + /** CFF */ + createTableEntry(otf, offsets, "CFF ", CFF); + + /** OS/2 */ + OS2 = createOS2Table(); + createTableEntry(otf, offsets, "OS/2", OS2); + + //XXX Getting charstrings here seems wrong since this is another CFF glue + var charstrings = font.getOrderedCharStrings(properties.glyphs); + + /** CMAP */ + cmap = createCMAPTable(charstrings); + createTableEntry(otf, offsets, "cmap", cmap); + + /** HEAD */ + head = stringToArray( + "\x00\x01\x00\x00" + // Version number + "\x00\x00\x50\x00" + // fontRevision + "\x00\x00\x00\x00" + // checksumAdjustement + "\x5F\x0F\x3C\xF5" + // magicNumber + "\x00\x00" + // Flags + "\x03\xE8" + // unitsPerEM (defaulting to 1000) + "\x00\x00\x00\x00\x00\x00\x00\x00" + // creation date + "\x00\x00\x00\x00\x00\x00\x00\x00" + // modifification date + "\x00\x00" + // xMin + "\x00\x00" + // yMin + "\x00\x00" + // xMax + "\x00\x00" + // yMax + "\x00\x00" + // macStyle + "\x00\x00" + // lowestRecPPEM + "\x00\x00" + // fontDirectionHint + "\x00\x00" + // indexToLocFormat + "\x00\x00" // glyphDataFormat + ); + createTableEntry(otf, offsets, "head", head); + + /** HHEA */ + hhea = stringToArray( + "\x00\x01\x00\x00" + // Version number + "\x00\x00" + // Typographic Ascent + "\x00\x00" + // Typographic Descent + "\x00\x00" + // Line Gap + "\xFF\xFF" + // advanceWidthMax + "\x00\x00" + // minLeftSidebearing + "\x00\x00" + // minRightSidebearing + "\x00\x00" + // xMaxExtent + "\x00\x00" + // caretSlopeRise + "\x00\x00" + // caretSlopeRun + "\x00\x00" + // caretOffset + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // metricDataFormat + string16(charstrings.length) + ); + createTableEntry(otf, offsets, "hhea", hhea); + + /** HMTX */ + hmtx = "\x01\xF4\x00\x00"; + for (var i = 0; i < charstrings.length; i++) { + var charstring = charstrings[i].charstring; + var width = charstring[1]; + var lsb = charstring[0]; + hmtx += string16(width) + string16(lsb); + } + hmtx = stringToArray(hmtx); + createTableEntry(otf, offsets, "hmtx", hmtx); + + /** MAXP */ + maxp = "\x00\x00\x50\x00" + // Version number + string16(charstrings.length + 1); // Num of glyphs (+1 to pass the sanitizer...) + maxp = stringToArray(maxp); + createTableEntry(otf, offsets, "maxp", maxp); + + /** NAME */ + name = stringToArray(createNameTable(name)); + createTableEntry(otf, offsets, "name", name); + + /** POST */ + // TODO: get those informations from the FontInfo structure + post = "\x00\x03\x00\x00" + // Version number + "\x00\x00\x01\x00" + // italicAngle + "\x00\x00" + // underlinePosition + "\x00\x00" + // underlineThickness + "\x00\x00\x00\x00" + // isFixedPitch + "\x00\x00\x00\x00" + // minMemType42 + "\x00\x00\x00\x00" + // maxMemType42 + "\x00\x00\x00\x00" + // minMemType1 + "\x00\x00\x00\x00"; // maxMemType1 + post = stringToArray(post); + createTableEntry(otf, offsets, "post", post); + + // Once all the table entries header are written, dump the data! + var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post]; + for (var i = 0; i < tables.length; i++) { + var table = tables[i]; + otf.set(table, offsets.currentOffset); + offsets.currentOffset += table.length; + } + + var fontData = []; + for (var i = 0; i < offsets.currentOffset; i++) + fontData.push(otf[i]); + return fontData; + }, + bind: function font_bind() { var data = this.font; var fontName = this.name; @@ -258,370 +784,6 @@ var Font = (function () { var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.length); - }, - - cover: function font_cover(aName, aFont, aProperties) { - var otf = Uint8Array(kMaxFontFileSize); - - function stringToArray(str) { - var array = []; - for (var i = 0; i < str.length; ++i) - array[i] = str.charCodeAt(i); - return array; - } - - function string16(value) { - return String.fromCharCode((value >> 8) & 0xff) + - String.fromCharCode(value & 0xff); - } - - function string32(value) { - return String.fromCharCode((value >> 24) & 0xff) + - String.fromCharCode((value >> 16) & 0xff) + - String.fromCharCode((value >> 8) & 0xff) + - String.fromCharCode(value & 0xff); - } - - function createOpenTypeHeader(aFile, aOffsets, numTables) { - var header = ""; - - // sfnt version (4 bytes) - header += "\x4F\x54\x54\x4F"; - - // numTables (2 bytes) - header += string16(numTables); - - // searchRange (2 bytes) - var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables); - var searchRange = tablesMaxPower2 * 16; - header += string16(searchRange); - - // entrySelector (2 bytes) - header += string16(Math.log(tablesMaxPower2) / Math.log(2)); - - // rangeShift (2 bytes) - header += string16(numTables * 16 - searchRange); - - aFile.set(stringToArray(header), aOffsets.currentOffset); - aOffsets.currentOffset += header.length; - aOffsets.virtualOffset += header.length; - } - - function createTableEntry(aFile, aOffsets, aTag, aData) { - // offset - var offset = aOffsets.virtualOffset; - - // Per spec tables must be 4-bytes align so add padding as needed - while (aData.length & 3) - aData.push(0x00); - - // length - var length = aData.length; - - // checksum - var checksum = aTag.charCodeAt(0) + - aTag.charCodeAt(1) + - aTag.charCodeAt(2) + - aTag.charCodeAt(3) + - offset + - length; - - var tableEntry = aTag + string32(checksum) + string32(offset) + string32(length); - tableEntry = stringToArray(tableEntry); - aFile.set(tableEntry, aOffsets.currentOffset); - - aOffsets.currentOffset += tableEntry.length; - aOffsets.virtualOffset += aData.length; - } - - function createNameTable(aName) { - var names = [ - "See original licence", // Copyright - aName, // Font family - "undefined", // Font subfamily (font weight) - "uniqueID", // Unique ID - aName, // Full font name - "0.1", // Version - "undefined", // Postscript name - "undefined", // Trademark - "undefined", // Manufacturer - "undefined" // Designer - ]; - - var name = - "\x00\x00" + // format - "\x00\x0A" + // Number of names Record - "\x00\x7E"; // Storage - - // Build the name records field - var strOffset = 0; - for (var i = 0; i < names.length; i++) { - var str = names[i]; - - var nameRecord = - "\x00\x01" + // platform ID - "\x00\x00" + // encoding ID - "\x00\x00" + // language ID - "\x00\x00" + // name ID - string16(str.length) + - string16(strOffset); - name += nameRecord; - - strOffset += str.length; - } - - name += names.join(""); - return name; - } - - function getRanges(glyphs) { - // Array.sort() sorts by characters, not numerically, so convert to an - // array of characters. - var codes = []; - var length = glyphs.length; - for (var n = 0; n < length; ++n) - codes.push(String.fromCharCode(glyphs[n].unicode)) - codes.sort(); - - // Split the sorted codes into ranges. - var ranges = []; - for (var n = 0; n < length; ) { - var start = codes[n++].charCodeAt(0); - var end = start; - while (n < length && end + 1 == codes[n].charCodeAt(0)) { - ++end; - ++n; - } - ranges.push([start, end]); - } - return ranges; - } - - function createCMAPTable(aGlyphs) { - var ranges = getRanges(aGlyphs); - - var headerSize = (12 * 2 + (ranges.length * 4 * 2)); - var segCount = ranges.length + 1; - var segCount2 = segCount * 2; - var searchRange = FontsUtils.getMaxPower2(segCount) * 2; - var searchEntry = Math.log(segCount) / Math.log(2); - var rangeShift = 2 * segCount - searchRange; - - var cmap = "\x00\x00" + // version - "\x00\x01" + // numTables - "\x00\x03" + // platformID - "\x00\x01" + // encodingID - "\x00\x00\x00\x0C" + // start of the table record - "\x00\x04" + // format - string16(headerSize) + // length - "\x00\x00" + // languages - string16(segCount2) + - string16(searchRange) + - string16(searchEntry) + - string16(rangeShift); - - // Fill up the 4 parallel arrays describing the segments. - var startCount = ""; - var endCount = ""; - var idDeltas = ""; - var idRangeOffsets = ""; - var glyphsIds = ""; - var bias = 0; - for (var i = 0; i < segCount - 1; i++) { - var range = ranges[i]; - var start = range[0]; - var end = range[1]; - var delta = (((start - 1) - bias) ^ 0xffff) + 1; - bias += (end - start + 1); - - startCount += string16(start); - endCount += string16(end); - idDeltas += string16(delta); - idRangeOffsets += string16(0); - - for (var j = start; j <= end; j++) - glyphsIds += String.fromCharCode(j); - } - - startCount += "\xFF\xFF"; - endCount += "\xFF\xFF"; - idDeltas += "\x00\x01"; - idRangeOffsets += "\x00\x00"; - - return stringToArray(cmap + endCount + "\x00\x00" + startCount + - idDeltas + idRangeOffsets + glyphsIds); - } - - // Required Tables - var CFF = aFont.data, // PostScript Font Program - OS2 = [], // OS/2 and Windows Specific metrics - cmap = [], // Character to glyphs mapping - head = [], // Font eader - hhea = [], // Horizontal header - hmtx = [], // Horizontal metrics - maxp = [], // Maximum profile - name = [], // Naming tables - post = []; // PostScript informations - var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post]; - - // 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: tables.length * (4 * 4) - }; - - // For files with only one font the offset table is the first thing of the - // file - createOpenTypeHeader(otf, offsets, tables.length); - - // TODO: It is probable that in a future we want to get rid of this glue - // between the CFF and the OTF format in order to be able to embed TrueType - // data. - createTableEntry(otf, offsets, "CFF ", CFF); - - /** OS/2 */ - OS2 = stringToArray( - "\x00\x03" + // version - "\x02\x24" + // xAvgCharWidth - "\x01\xF4" + // usWeightClass - "\x00\x05" + // usWidthClass - "\x00\x00" + // fstype - "\x02\x8A" + // ySubscriptXSize - "\x02\xBB" + // ySubscriptYSize - "\x00\x00" + // ySubscriptXOffset - "\x00\x8C" + // ySubscriptYOffset - "\x02\x8A" + // ySuperScriptXSize - "\x02\xBB" + // ySuperScriptYSize - "\x00\x00" + // ySuperScriptXOffset - "\x01\xDF" + // ySuperScriptYOffset - "\x00\x31" + // yStrikeOutSize - "\x01\x02" + // yStrikeOutPosition - "\x00\x00" + // sFamilyClass - "\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose - "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31) - "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63) - "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95) - "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127) - "\x2A\x32\x31\x2A" + // achVendID - "\x00\x20" + // fsSelection - "\x00\x2D" + // usFirstCharIndex - "\x00\x7A" + // usLastCharIndex - "\x00\x03" + // sTypoAscender - "\x00\x20" + // sTypeDescender - "\x00\x38" + // sTypoLineGap - "\x00\x5A" + // usWinAscent - "\x02\xB4" + // usWinDescent - "\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31) - "\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63) - "\x00\x00" + // sxHeight - "\x00\x00" + // sCapHeight - "\x00\x01" + // usDefaultChar - "\x00\xCD" + // usBreakChar - "\x00\x02" // usMaxContext - ); - createTableEntry(otf, offsets, "OS/2", OS2); - - //XXX Getting charstrings here seems wrong since this is another CFF glue - var charstrings = aFont.getOrderedCharStrings(aProperties.glyphs); - - /** CMAP */ - cmap = createCMAPTable(charstrings); - createTableEntry(otf, offsets, "cmap", cmap); - - /** HEAD */ - head = stringToArray( - "\x00\x01\x00\x00" + // Version number - "\x00\x00\x50\x00" + // fontRevision - "\x00\x00\x00\x00" + // checksumAdjustement - "\x5F\x0F\x3C\xF5" + // magicNumber - "\x00\x00" + // Flags - "\x03\xE8" + // unitsPerEM (defaulting to 1000) - "\x00\x00\x00\x00\x00\x00\x00\x00" + // creation date - "\x00\x00\x00\x00\x00\x00\x00\x00" + // modifification date - "\x00\x00" + // xMin - "\x00\x00" + // yMin - "\x00\x00" + // xMax - "\x00\x00" + // yMax - "\x00\x00" + // macStyle - "\x00\x00" + // lowestRecPPEM - "\x00\x00" + // fontDirectionHint - "\x00\x00" + // indexToLocFormat - "\x00\x00" // glyphDataFormat - ); - createTableEntry(otf, offsets, "head", head); - - /** HHEA */ - hhea = stringToArray( - "\x00\x01\x00\x00" + // Version number - "\x00\x00" + // Typographic Ascent - "\x00\x00" + // Typographic Descent - "\x00\x00" + // Line Gap - "\xFF\xFF" + // advanceWidthMax - "\x00\x00" + // minLeftSidebearing - "\x00\x00" + // minRightSidebearing - "\x00\x00" + // xMaxExtent - "\x00\x00" + // caretSlopeRise - "\x00\x00" + // caretSlopeRun - "\x00\x00" + // caretOffset - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // metricDataFormat - string16(charstrings.length) - ); - createTableEntry(otf, offsets, "hhea", hhea); - - /** HMTX */ - hmtx = "\x01\xF4\x00\x00"; - for (var i = 0; i < charstrings.length; i++) { - var charstring = charstrings[i].charstring; - var width = charstring[1]; - var lsb = charstring[0]; - hmtx += string16(width) + string16(lsb); - } - hmtx = stringToArray(hmtx); - createTableEntry(otf, offsets, "hmtx", hmtx); - - /** MAXP */ - maxp = "\x00\x00\x50\x00" + // Version number - string16(charstrings.length + 1); // Num of glyphs (+1 to pass the sanitizer...) - maxp = stringToArray(maxp); - createTableEntry(otf, offsets, "maxp", maxp); - - /** NAME */ - name = stringToArray(createNameTable(aName)); - createTableEntry(otf, offsets, "name", name); - - /** POST */ - // TODO: get those informations from the FontInfo structure - post = "\x00\x03\x00\x00" + // Version number - "\x00\x00\x01\x00" + // italicAngle - "\x00\x00" + // underlinePosition - "\x00\x00" + // underlineThickness - "\x00\x00\x00\x00" + // isFixedPitch - "\x00\x00\x00\x00" + // minMemType42 - "\x00\x00\x00\x00" + // maxMemType42 - "\x00\x00\x00\x00" + // minMemType1 - "\x00\x00\x00\x00"; // maxMemType1 - post = stringToArray(post); - createTableEntry(otf, offsets, "post", post); - - // Once all the table entries header are written, dump the data! - var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post]; - for (var i = 0; i < tables.length; i++) { - var table = tables[i]; - otf.set(table, offsets.currentOffset); - offsets.currentOffset += table.length; - } - - var fontData = []; - for (var i = 0; i < offsets.currentOffset; i++) - fontData.push(otf[i]); - return fontData; } }; @@ -635,31 +797,31 @@ var Font = (function () { */ var FontsUtils = { _bytesArray: new Uint8Array(4), - integerToBytes: function fu_integerToBytes(aValue, aBytesCount) { + integerToBytes: function fu_integerToBytes(value, bytesCount) { var bytes = this._bytesArray; - if (aBytesCount == 1) { - bytes.set([aValue]); + if (bytesCount == 1) { + bytes.set([value]); return bytes[0]; - } else if (aBytesCount == 2) { - bytes.set([aValue >> 8, aValue]); + } else if (bytesCount == 2) { + bytes.set([value >> 8, value]); return [bytes[0], bytes[1]]; - } else if (aBytesCount == 4) { - bytes.set([aValue >> 24, aValue >> 16, aValue >> 8, aValue]); + } else if (bytesCount == 4) { + bytes.set([value >> 24, value >> 16, value >> 8, value]); return [bytes[0], bytes[1], bytes[2], bytes[3]]; } }, - bytesToInteger: function fu_bytesToInteger(aBytesArray) { + bytesToInteger: function fu_bytesToInteger(bytesArray) { var value = 0; - for (var i = 0; i < aBytesArray.length; i++) - value = (value << 8) + aBytesArray[i]; + for (var i = 0; i < bytesArray.length; i++) + value = (value << 8) + bytesArray[i]; return value; }, - getMaxPower2: function fu_getMaxPower2(aNumber) { + getMaxPower2: function fu_getMaxPower2(number) { var maxPower = 0; - var value = aNumber; + var value = number; while (value >= 2) { value /= 2; maxPower++; @@ -673,391 +835,6 @@ var FontsUtils = { }; -/** - * The TrueType class verify that the ttf embedded inside the PDF is correct in - * the point of view of the OTS sanitizer and rewrite it on the fly otherwise. - * - * At the moment the rewiting only support rewriting missing 'OS/2' table. - * This class is unused at the moment since the 'cmap' table of the test - * document is not missing but use and old version of the 'cmap' table that - * is deprecated and not supported by the sanitizer... - * - */ -var TrueType = function(aName, aFile, aProperties) { - var header = this._readOpenTypeHeader(aFile); - var numTables = header.numTables; - - // Check that required tables are present - var requiredTables = [ - "OS/2", - "cmap", - "head", - "hhea", - "hmtx", - "maxp", - "name", - "post" - ]; - - var originalCMAP = null; - - var tables = []; - for (var i = 0; i < numTables; i++) { - var table = this._readTableEntry(aFile); - var index = requiredTables.indexOf(table.tag); - if (index != -1) { - if (table.tag == "cmap") - originalCMAP = table; - - 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") { - var 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 - ]; - - // If the font is missing a OS/2 table it's could be an old mac font - // without a 3-1-4 Unicode BMP table, so let's rewrite it. - var charset = aProperties.charset; - var glyphs = []; - for (var i = 0; i < charset.length; i++) { - glyphs.push({ - unicode: GlyphsUnicode[charset[i]] - }); - } - - - var offsetDelta = 0; - - // Replace the old CMAP table - var rewrittedCMAP = this._createCMAPTable(glyphs); - offsetDelta = rewrittedCMAP.length - originalCMAP.data.length; - originalCMAP.data = rewrittedCMAP; - - // Rewrite the 'post' table if needed - var postTable = null; - for (var i = 0; i < tables.length; i++) { - var table = tables[i]; - if (table.tag == "post") { - postTable = table; - break; - } - } - - if (!postTable) { - var post = [ - 0x00, 0x03, 0x00, 0x00, // Version number - 0x00, 0x00, 0x01, 0x00, // italicAngle - 0x00, 0x00, // underlinePosition - 0x00, 0x00, // underlineThickness - 0x00, 0x00, 0x00, 0x00, // isFixedPitch - 0x00, 0x00, 0x00, 0x00, // minMemType42 - 0x00, 0x00, 0x00, 0x00, // maxMemType42 - 0x00, 0x00, 0x00, 0x00, // minMemType1 - 0x00, 0x00, 0x00, 0x00 // maxMemType1 - ]; - - offsetDelta += post.length; - tables.unshift({ - tag: "post", - data: post - }); - } - - // 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 + 1024); - - // 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 - }); - - // Tables needs to be written by ascendant alphabetic order - tables.sort(function tables_sort(a, b) { - return 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]); - 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; - - // 4-byte aligned data - while (offsets.currentOffset & 3) - offsets.currentOffset++; - } - - var fontData = []; - for (var i = 0; i < offsets.currentOffset; i++) - fontData.push(ttf[i]); - - this.data = fontData; - return; - } else if (requiredTables.length) { - error("Table " + requiredTables[0] + " is missing from the TruType font"); - } else { - this.data = aFile.getBytes(); - } -}; - -TrueType.prototype = { - _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; - }, - - _createCMAPTable: function font_createCMAPTable(aGlyphs) { - var characters = Uint16Array(65535); - for (var i = 0; i < aGlyphs.length; i++) - characters[aGlyphs[i].unicode] = i + 1; - - // Separate the glyphs into continuous range of codes, aka segment. - var ranges = []; - var range = []; - var count = characters.length; - for (var i = 0; i < count; i++) { - if (characters[i]) { - range.push(i); - } else if (range.length) { - ranges.push(range.slice()); - range = []; - } - } - - // The size in bytes of the header is equal to the size of the - // different fields * length of a short + (size of the 4 parallels arrays - // describing segments * length of a short). - var headerSize = (12 * 2 + (ranges.length * 4 * 2)); - - var segCount = ranges.length + 1; - var segCount2 = segCount * 2; - var searchRange = FontsUtils.getMaxPower2(segCount) * 2; - var searchEntry = Math.log(segCount) / Math.log(2); - var rangeShift = 2 * segCount - searchRange; - var cmap = [].concat( - [ - 0x00, 0x00, // version - 0x00, 0x01, // numTables - 0x00, 0x03, // platformID - 0x00, 0x01, // encodingID - 0x00, 0x00, 0x00, 0x0C, // start of the table record - 0x00, 0x04 // format - ], - FontsUtils.integerToBytes(headerSize, 2), // length - [0x00, 0x00], // language - FontsUtils.integerToBytes(segCount2, 2), - FontsUtils.integerToBytes(searchRange, 2), - FontsUtils.integerToBytes(searchEntry, 2), - FontsUtils.integerToBytes(rangeShift, 2) - ); - - // Fill up the 4 parallel arrays describing the segments. - var startCount = []; - var endCount = []; - var idDeltas = []; - var idRangeOffsets = []; - var glyphsIdsArray = []; - var bias = 0; - for (var i = 0; i < segCount - 1; i++) { - var range = ranges[i]; - var start = FontsUtils.integerToBytes(range[0], 2); - var end = FontsUtils.integerToBytes(range[range.length - 1], 2); - - var delta = FontsUtils.integerToBytes(((range[0] - 1) - bias) % 65536, 2); - bias += range.length; - - // deltas are signed shorts - delta[0] ^= 0xFF; - delta[1] ^= 0xFF; - delta[1] += 1; - - startCount.push(start[0], start[1]); - endCount.push(end[0], end[1]); - idDeltas.push(delta[0], delta[1]); - idRangeOffsets.push(0x00, 0x00); - - for (var j = 0; j < range.length; j++) - glyphsIdsArray.push(range[j]); - } - startCount.push(0xFF, 0xFF); - endCount.push(0xFF, 0xFF); - idDeltas.push(0x00, 0x01); - idRangeOffsets.push(0x00, 0x00); - - return cmap.concat(endCount, [0x00, 0x00], startCount, - idDeltas, idRangeOffsets, glyphsIdsArray); - }, - - _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)), - searchRange: FontsUtils.bytesToInteger(aFile.getBytes(2)), - 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 - } - } -}; - - /** * Type1Parser encapsulate the needed code for parsing a Type1 font * program. @@ -1072,18 +849,18 @@ var Type1Parser = function() { var kEexecEncryptionKey = 55665; var kCharStringsEncryptionKey = 4330; - function decrypt(aStream, aKey, aDiscardNumber) { - var r = aKey, c1 = 52845, c2 = 22719; + function decrypt(stream, key, discardNumber) { + var r = key, c1 = 52845, c2 = 22719; var decryptedString = []; var value = ""; - var count = aStream.length; + var count = stream.length; for (var i = 0; i < count; i++) { - value = aStream[i]; + value = stream[i]; decryptedString[i] = value ^ (r >> 8); r = ((value + r) * c1 + c2) & ((1 << 16) - 1); } - return decryptedString.slice(aDiscardNumber); + return decryptedString.slice(discardNumber); }; /* @@ -1193,18 +970,18 @@ var Type1Parser = function() { "31": "hvcurveto" }; - function decodeCharString(aArray) { + function decodeCharString(array) { var charString = []; var value = ""; - var count = aArray.length; + var count = array.length; for (var i = 0; i < count; i++) { - value = parseInt(aArray[i]); + value = parseInt(array[i]); if (value < 32) { var command = null; if (value == 12) { - var escape = aArray[++i]; + var escape = array[++i]; command = charStringDictionary["12"][escape]; } else { command = charStringDictionary[value]; @@ -1224,14 +1001,14 @@ var Type1Parser = function() { } else if (value <= 246) { value = parseInt(value) - 139; } else if (value <= 250) { - value = ((value - 247) * 256) + parseInt(aArray[++i]) + 108; + value = ((value - 247) * 256) + parseInt(array[++i]) + 108; } else if (value <= 254) { - value = -((value - 251) * 256) - parseInt(aArray[++i]) - 108; + value = -((value - 251) * 256) - parseInt(array[++i]) - 108; } else { - var byte = aArray[++i]; + var byte = array[++i]; var high = (byte >> 1); - value = (byte - high) << 24 | aArray[++i] << 16 | - aArray[++i] << 8 | aArray[++i]; + value = (byte - high) << 24 | array[++i] << 16 | + array[++i] << 8 | array[++i]; } charString.push(value); @@ -1244,8 +1021,8 @@ var Type1Parser = function() { * Returns an object containing a Subrs array and a CharStrings array * extracted from and eexec encrypted block of data */ - this.extractFontProgram = function t1_extractFontProgram(aStream) { - var eexecString = decrypt(aStream, kEexecEncryptionKey, 4); + this.extractFontProgram = function t1_extractFontProgram(stream) { + var eexecString = decrypt(stream, kEexecEncryptionKey, 4); var subrs = [], glyphs = []; var inGlyphs = false; var inSubrs = false; @@ -1369,26 +1146,26 @@ const CFFStrings = [ "001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold" ]; -var CFF = function(aName, aFile, aProperties) { +var CFF = function(name, file, properties) { // Get the data block containing glyphs and subrs informations - var length1 = aFile.dict.get("Length1"); - var length2 = aFile.dict.get("Length2"); - aFile.skip(length1); - var eexecBlock = aFile.getBytes(length2); + var length1 = file.dict.get("Length1"); + var length2 = file.dict.get("Length2"); + file.skip(length1); + var eexecBlock = file.getBytes(length2); // Decrypt the data blocks and retrieve the informations from it var parser = new Type1Parser(); var fontInfo = parser.extractFontProgram(eexecBlock); - aProperties.subrs = fontInfo.subrs; - aProperties.glyphs = fontInfo.charstrings; - this.data = this.wrap(aName, aProperties); + properties.subrs = fontInfo.subrs; + properties.glyphs = fontInfo.charstrings; + this.data = this.wrap(name, properties); }; CFF.prototype = { - createCFFIndexHeader: function(aObjects, aIsByte) { + createCFFIndexHeader: function(objects, isByte) { // First 2 bytes contains the number of objects contained into this index - var count = aObjects.length; + var count = objects.length; // If there is no object, just create an array saying that with another // offset byte. @@ -1412,33 +1189,33 @@ CFF.prototype = { for (var j = 0; j < bytes.length; j++) data.push(bytes[j]); - if (aObjects[i]) - relativeOffset += aObjects[i].length; + if (objects[i]) + relativeOffset += objects[i].length; } for (var i =0; i < count; i++) { - for (var j = 0; j < aObjects[i].length; j++) - data.push(aIsByte ? aObjects[i][j] : aObjects[i].charCodeAt(j)); + for (var j = 0; j < objects[i].length; j++) + data.push(isByte ? objects[i][j] : objects[i].charCodeAt(j)); } return data; }, - encodeNumber: function(aValue) { + encodeNumber: function(value) { var x = 0; - if (aValue >= -32768 && aValue <= 32767) { - return [ 28, aValue >> 8, aValue & 0xFF ]; - } else if (aValue >= (-2147483647-1) && aValue <= 2147483647) { - return [ 0xFF, aValue >> 24, Value >> 16, aValue >> 8, aValue & 0xFF ]; + if (value >= -32768 && value <= 32767) { + return [ 28, value >> 8, value & 0xFF ]; + } else if (value >= (-2147483647-1) && value <= 2147483647) { + return [ 0xFF, value >> 24, Value >> 16, value >> 8, value & 0xFF ]; } else { - error("Value: " + aValue + " is not allowed"); + error("Value: " + value + " is not allowed"); } }, - getOrderedCharStrings: function(aGlyphs) { + getOrderedCharStrings: function(glyphs) { var charstrings = []; - for (var i = 0; i < aGlyphs.length; i++) { - var glyph = aGlyphs[i].glyph; + for (var i = 0; i < glyphs.length; i++) { + var glyph = glyphs[i].glyph; var unicode = GlyphsUnicode[glyph]; if (!unicode) { if (glyph != ".notdef") @@ -1447,7 +1224,7 @@ CFF.prototype = { charstrings.push({ glyph: glyph, unicode: unicode, - charstring: aGlyphs[i].data.slice() + charstring: glyphs[i].data.slice() }); } }; @@ -1480,100 +1257,100 @@ CFF.prototype = { "hvcurveto": 31, }, - flattenCharstring: function flattenCharstring(aGlyph, aCharstring, aSubrs) { + flattenCharstring: function flattenCharstring(glyph, charstring, subrs) { var i = 0; while (true) { - var obj = aCharstring[i]; + var obj = charstring[i]; if (obj == null) return []; if (obj.charAt) { switch (obj) { case "callsubr": - var subr = aSubrs[aCharstring[i - 1]].slice(); + var subr = subrs[charstring[i - 1]].slice(); if (subr.length > 1) { - subr = this.flattenCharstring(aGlyph, subr, aSubrs); + subr = this.flattenCharstring(glyph, subr, subrs); subr.pop(); - aCharstring.splice(i - 1, 2, subr); + charstring.splice(i - 1, 2, subr); } else { - aCharstring.splice(i - 1, 2); + charstring.splice(i - 1, 2); } i -= 1; break; case "callothersubr": - var index = aCharstring[i - 1]; - var count = aCharstring[i - 2]; - var data = aCharstring[i - 3]; + var index = charstring[i - 1]; + var count = charstring[i - 2]; + var data = charstring[i - 3]; // XXX The callothersubr needs to support at least the 3 defaults // otherSubrs of the spec if (index != 3) - error("callothersubr for index: " + index + " (" + aCharstring + ")"); + error("callothersubr for index: " + index + " (" + charstring + ")"); if (!data) { - aCharstring.splice(i - 2, 4, "pop", 3); + charstring.splice(i - 2, 4, "pop", 3); i -= 3; } else { // 5 to remove the arguments, the callothersubr call and the pop command - aCharstring.splice(i - 3, 5, 3); + charstring.splice(i - 3, 5, 3); i -= 3; } break; case "div": - var num2 = aCharstring[i - 1]; - var num1 = aCharstring[i - 2]; - aCharstring.splice(i - 2, 3, num1 / num2); + var num2 = charstring[i - 1]; + var num1 = charstring[i - 2]; + charstring.splice(i - 2, 3, num1 / num2); i -= 2; break; case "pop": if (i) - aCharstring.splice(i - 2, 2); + charstring.splice(i - 2, 2); else - aCharstring.splice(i - 1, 1); + charstring.splice(i - 1, 1); i -= 1; break; case "hsbw": - var charWidthVector = aCharstring[i - 1]; - var leftSidebearing = aCharstring[i - 2]; + var charWidthVector = charstring[i - 1]; + var leftSidebearing = charstring[i - 2]; if (leftSidebearing) - aCharstring.splice(i - 2, 3, charWidthVector, leftSidebearing, "hmoveto"); + charstring.splice(i - 2, 3, charWidthVector, leftSidebearing, "hmoveto"); else - aCharstring.splice(i - 2, 3, charWidthVector); + charstring.splice(i - 2, 3, charWidthVector); break; case "endchar": case "return": // CharString is ready to be re-encode to commands number at this point - for (var j = 0; j < aCharstring.length; j++) { - var command = aCharstring[j]; + for (var j = 0; j < charstring.length; j++) { + var command = charstring[j]; if (parseFloat(command) == command) { - aCharstring.splice(j, 1, 28, command >> 8, command); + charstring.splice(j, 1, 28, command >> 8, command); j+= 2; } else if (command.charAt) { var command = this.commandsMap[command]; if (IsArray(command)) { - aCharstring.splice(j - 1, 1, command[0], command[1]); + charstring.splice(j - 1, 1, command[0], command[1]); j += 1; } else { - aCharstring[j] = command; + charstring[j] = command; } } else { - aCharstring.splice(j, 1); + charstring.splice(j, 1); // command has already been translated, just add them to the // charstring directly for (var k = 0; k < command.length; k++) - aCharstring.splice(j + k, 0, command[k]); + charstring.splice(j + k, 0, command[k]); j+= command.length - 1; } } - return aCharstring; + return charstring; default: break; @@ -1581,11 +1358,11 @@ CFF.prototype = { } i++; } - error("failing with i = " + i + " in charstring:" + aCharstring + "(" + aCharstring.length + ")"); + error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")"); }, - wrap: function wrap(aName, aProperties) { - var charstrings = this.getOrderedCharStrings(aProperties.glyphs); + wrap: function wrap(name, properties) { + var charstrings = this.getOrderedCharStrings(properties.glyphs); // Starts the conversion of the Type1 charstrings to Type2 var charstringsCount = 0; @@ -1595,7 +1372,7 @@ CFF.prototype = { var charstring = charstrings[i].charstring.slice(); var glyph = charstrings[i].glyph; - var flattened = this.flattenCharstring(glyph, charstring, aProperties.subrs); + var flattened = this.flattenCharstring(glyph, charstring, properties.subrs); glyphs.push(flattened); charstringsCount++; charstringsDataLength += flattened.length; @@ -1611,7 +1388,7 @@ CFF.prototype = { cff.set(header); // Names Index - var nameIndex = this.createCFFIndexHeader([aName]); + var nameIndex = this.createCFFIndexHeader([name]); cff.set(nameIndex, currentOffset); currentOffset += nameIndex.length; @@ -1655,7 +1432,7 @@ CFF.prototype = { 248, 31, 4 // Weight ]; - var fontBBox = aProperties.bbox; + var fontBBox = properties.bbox; for (var i = 0; i < fontBBox.length; i++) topDictIndex = topDictIndex.concat(this.encodeNumber(fontBBox[i])); topDictIndex.push(5) // FontBBox; diff --git a/pdf.js b/pdf.js index 5c49f14dd..4db4ef06f 100644 --- a/pdf.js +++ b/pdf.js @@ -80,6 +80,7 @@ var Stream = (function() { getBytes: function(length) { var bytes = this.bytes; var pos = this.pos; + var end = pos + length; var strEnd = this.end; if (!end || end > strEnd) @@ -2198,7 +2199,7 @@ var CanvasGraphics = (function() { var tokens = []; var token = ""; - var buffer = cmapObj.ensureBuffer(); + var buffer = cmapObj.ensureBuffer ? cmapObj.ensureBuffer() : cmapObj; var cmap = cmapObj.getBytes(buffer.byteLength); for (var i =0; i < cmap.length; i++) { var byte = cmap[i];