From 5cbc6875b367aee7a058db0b683a916f171b9013 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 1 Jul 2011 00:44:11 +0200 Subject: [PATCH 01/11] Open the CFF class road for Type1C font --- fonts.js | 134 ++++++++++++++++++++++++------------------------------- 1 file changed, 59 insertions(+), 75 deletions(-) diff --git a/fonts.js b/fonts.js index fdc5949a5..054185009 100644 --- a/fonts.js +++ b/fonts.js @@ -6,7 +6,7 @@ /** * Maximum file size of the font. */ -var kMaxFontFileSize = 40000; +var kMaxFontFileSize = 200000; /** * Maximum time to wait for a font to be loaded by @font-face @@ -34,7 +34,7 @@ var kDisableFonts = false; * http://cgit.freedesktop.org/poppler/poppler/tree/poppler/GfxFont.cc#n65 */ -var Fonts = (function () { +var Fonts = (function Fonts() { var kScalePrecision = 40; var fonts = Object.create(null); var ctx = document.createElement("canvas").getContext("2d"); @@ -1462,13 +1462,17 @@ var CFF = function(name, file, properties) { 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 it's content + var eexecBlock = file.getBytes(length2); var data = type1Parser.extractFontProgram(eexecBlock); - this.charstrings = this.getOrderedCharStrings(data.charstrings); - this.data = this.wrap(name, this.charstrings, data.subrs, properties); + var charstrings = this.getOrderedCharStrings(data.charstrings); + var type2Charstrings = this.getType2Charstrings(charstrings); + var subrs = this.getType2Subrs(data.subrs); + + this.charstrings = charstrings; + this.data = this.wrap(name, type2Charstrings, this.charstrings, subrs, properties); }; CFF.prototype = { @@ -1546,12 +1550,40 @@ CFF.prototype = { return charstrings; }, + getType2Charstrings: function cff_getType2Charstrings(type1Charstrings) { + var type2Charstrings = []; + var count = type1Charstrings.length; + for (var i = 0; i < count; i++) { + var charstring = type1Charstrings[i].charstring; + type2Charstrings.push(this.flattenCharstring(charstring.slice(), this.commandsMap)); + } + return type2Charstrings; + }, + + getType2Subrs: function cff_getType2Charstrings(type1Subrs) { + var bias = 0; + var count = type1Subrs.length; + if (count < 1240) + bias = 107; + else if (count < 33900) + bias = 1131; + else + bias = 32768; + + // Add a bunch of empty subrs to deal with the Type2 bias + var type2Subrs = []; + for (var i = 0; i < bias; i++) + type2Subrs.push([0x0B]); + + for (var i = 0; i < count; i++) + type2Subrs.push(this.flattenCharstring(type1Subrs[i], this.commandsMap)); + + return type2Subrs; + }, + /* * Flatten the commands by interpreting the postscript code and replacing * every 'callsubr', 'callothersubr' by the real commands. - * - * TODO This function also do a string to command number transformation - * that can probably be avoided if the Type1 decodeCharstring code is smarter */ commandsMap: { "hstem": 1, @@ -1573,58 +1605,27 @@ CFF.prototype = { "hvcurveto": 31, }, - flattenCharstring: function flattenCharstring(charstring) { - var i = 0; - while (true) { - var obj = charstring[i]; - if (obj == undefined) { - error("unknow charstring command for " + i + " in " + charstring); - } - if (obj.charAt) { - switch (obj) { - case "endchar": - case "return": - // CharString is ready to be re-encode to commands number at this point - for (var j = 0; j < charstring.length; j++) { - var command = charstring[j]; - if (parseFloat(command) == command) { - charstring.splice(j, 1, 28, command >> 8, command); - j+= 2; - } else if (command.charAt) { - var cmd = this.commandsMap[command]; - if (!cmd) - error("Unknow command: " + command); + flattenCharstring: function flattenCharstring(charstring, map) { + for (var i = 0; i < charstring.length; i++) { + var command = charstring[i]; + if (command.charAt) { + var cmd = map[command]; + assert(cmd, "Unknow command: " + command); - if (IsArray(cmd)) { - charstring.splice(j, 1, cmd[0], cmd[1]); - j += 1; - } else { - charstring[j] = cmd; - } - } - } - return charstring; - - default: - break; + if (IsArray(cmd)) { + charstring.splice(i++, 1, cmd[0], cmd[1]); + } else { + charstring[i] = cmd; } + } else { + charstring.splice(i, 1, 28, command >> 8, command & 0xff); + i+= 2; } - i++; } - error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")"); - return []; + return charstring; }, - wrap: function wrap(name, charstrings, subrs, properties) { - // Starts the conversion of the Type1 charstrings to Type2 - var glyphs = []; - var glyphsCount = charstrings.length; - for (var i = 0; i < glyphsCount; i++) { - var charstring = charstrings[i].charstring; - glyphs.push(this.flattenCharstring(charstring.slice())); - } - - // Create a CFF font data + wrap: function wrap(name, glyphs, charstrings, subrs, properties) { var cff = new Uint8Array(kMaxFontFileSize); var currentOffset = 0; @@ -1656,10 +1657,12 @@ CFF.prototype = { // Fill the charset header (first byte is the encoding) var charset = [0x00]; + var glyphsCount = glyphs.length; for (var i = 0; i < glyphsCount; i++) { var index = CFFStrings.indexOf(charstrings[i].glyph); if (index == -1) index = CFFStrings.length + strings.indexOf(charstrings[i].glyph); + var bytes = FontsUtils.integerToBytes(index, 2); charset.push(bytes[0]); charset.push(bytes[1]); @@ -1731,28 +1734,9 @@ CFF.prototype = { cff.set(privateData, currentOffset); currentOffset += privateData.length; - // Local Subrs - var flattenedSubrs = []; - var bias = 0; - var subrsCount = subrs.length; - if (subrsCount < 1240) - bias = 107; - else if (subrsCount < 33900) - bias = 1131; - else - bias = 32768; - - // Add a bunch of empty subrs to deal with the Type2 bias - for (var i = 0; i < bias; i++) - flattenedSubrs.push([0x0B]); - - for (var i = 0; i < subrsCount; i++) { - var subr = subrs[i]; - flattenedSubrs.push(this.flattenCharstring(subr)); - } - - var subrsData = this.createCFFIndexHeader(flattenedSubrs, true); + // Local subrs + var subrsData = this.createCFFIndexHeader(subrs, true); cff.set(subrsData, currentOffset); currentOffset += subrsData.length; From db30cc6de1a30f24d57f99e4f64d07e2cb082325 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 1 Jul 2011 00:57:17 +0200 Subject: [PATCH 02/11] Start converting CFF class to use strings instead of arrays --- fonts.js | 55 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/fonts.js b/fonts.js index 054185009..ad008c375 100644 --- a/fonts.js +++ b/fonts.js @@ -1483,24 +1483,24 @@ CFF.prototype = { // If there is no object, just create an array saying that with another // offset byte. if (count == 0) - return [0x00, 0x00, 0x00]; + return "\x00\x00\x00"; - var data = []; + var data = ""; var bytes = FontsUtils.integerToBytes(count, 2); for (var i = 0; i < bytes.length; i++) - data.push(bytes[i]); + data += String.fromCharCode(bytes[i]); // Next byte contains the offset size use to reference object in the file // Actually we're using 0x04 to be sure to be able to store everything // without thinking of it while coding. - data.push(0x04); + data += "\x04"; // Add another offset after this one because we need a new offset var relativeOffset = 1; for (var i = 0; i < count + 1; i++) { var bytes = FontsUtils.integerToBytes(relativeOffset, 4); for (var j = 0; j < bytes.length; j++) - data.push(bytes[j]); + data += String.fromCharCode(bytes[j]); if (objects[i]) relativeOffset += objects[i].length; @@ -1508,17 +1508,22 @@ CFF.prototype = { for (var i =0; i < count; i++) { for (var j = 0; j < objects[i].length; j++) - data.push(isByte ? objects[i][j] : objects[i].charCodeAt(j)); + data += isByte ? String.fromCharCode(objects[i][j]) : objects[i][j]; } return data; }, encodeNumber: function cff_encodeNumber(value) { - var x = 0; if (value >= -32768 && value <= 32767) { - return [ 28, value >> 8, value & 0xFF ]; + return "\x1c" + + String.fromCharCode(value >> 8) + + String.fromCharCode(value & 0xFF); } else if (value >= (-2147483647-1) && value <= 2147483647) { - return [ 0xFF, value >> 24, Value >> 16, value >> 8, value & 0xFF ]; + return "\xff" + + String.fromCharCode(value >> 24) + + String.fromCharCode(value >> 16) + + String.fromCharCode(value >> 8) + + String.fromCharCode(value & 0xFF); } error("Value: " + value + " is not allowed"); return null; @@ -1626,16 +1631,24 @@ CFF.prototype = { }, wrap: function wrap(name, glyphs, charstrings, subrs, properties) { + function stringToArray(str) { + var array = []; + for (var i = 0; i < str.length; ++i) + array[i] = str.charCodeAt(i); + + return array; + }; + var cff = new Uint8Array(kMaxFontFileSize); var currentOffset = 0; // Font header (major version, minor version, header size, offset size) - var header = [0x01, 0x00, 0x04, 0x04]; + var header = "\x01\x00\x04\x04"; currentOffset += header.length; - cff.set(header); + cff.set(stringToArray(header)); // Names Index - var nameIndex = this.createCFFIndexHeader([name]); + var nameIndex = stringToArray(this.createCFFIndexHeader([name])); cff.set(nameIndex, currentOffset); currentOffset += nameIndex.length; @@ -1649,11 +1662,11 @@ CFF.prototype = { var weight = ""; var strings = [version, notice, fullName, familyName, weight]; - var stringsIndex = this.createCFFIndexHeader(strings); + var stringsIndex = stringToArray(this.createCFFIndexHeader(strings)); var stringsDataLength = stringsIndex.length; // Create the global subroutines index - var globalSubrsIndex = this.createCFFIndexHeader([]); + var globalSubrsIndex = stringToArray(this.createCFFIndexHeader([])); // Fill the charset header (first byte is the encoding) var charset = [0x00]; @@ -1668,7 +1681,7 @@ CFF.prototype = { charset.push(bytes[1]); } - var charstringsIndex = this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), true); + var charstringsIndex = stringToArray(this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), true)); //Top Dict Index var topDictIndex = [ @@ -1682,25 +1695,25 @@ CFF.prototype = { var fontBBox = properties.bbox; for (var i = 0; i < fontBBox.length; i++) - topDictIndex = topDictIndex.concat(this.encodeNumber(fontBBox[i])); + topDictIndex = topDictIndex.concat(stringToArray(this.encodeNumber(fontBBox[i]))); topDictIndex.push(5) // FontBBox; var charsetOffset = currentOffset + (topDictIndex.length + (4 + 4 + 4 + 7)) + stringsIndex.length + globalSubrsIndex.length; - topDictIndex = topDictIndex.concat(this.encodeNumber(charsetOffset)); + topDictIndex = topDictIndex.concat(stringToArray(this.encodeNumber(charsetOffset))); topDictIndex.push(15); // charset topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding var charstringsOffset = charsetOffset + (glyphsCount * 2) + 1; - topDictIndex = topDictIndex.concat(this.encodeNumber(charstringsOffset)); + topDictIndex = topDictIndex.concat(stringToArray(this.encodeNumber(charstringsOffset))); topDictIndex.push(17); // charstrings topDictIndex = topDictIndex.concat([28, 0, 55]) var privateOffset = charstringsOffset + charstringsIndex.length; - topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset)); + topDictIndex = topDictIndex.concat(stringToArray(this.encodeNumber(privateOffset))); topDictIndex.push(18); // Private var indexes = [ @@ -1716,7 +1729,7 @@ CFF.prototype = { } // Private Data - var defaultWidth = this.encodeNumber(0); + var defaultWidth = stringToArray(this.encodeNumber(0)); var privateData = [].concat( defaultWidth, [20], [139, 21], // nominalWidth @@ -1736,7 +1749,7 @@ CFF.prototype = { // Local subrs - var subrsData = this.createCFFIndexHeader(subrs, true); + var subrsData = stringToArray(this.createCFFIndexHeader(subrs, true)); cff.set(subrsData, currentOffset); currentOffset += subrsData.length; From dae18a271007310ce07a2c2434fface62dcb8f16 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 1 Jul 2011 03:24:23 +0200 Subject: [PATCH 03/11] Use strings instead of arrays in CFF.wrap --- fonts.js | 213 ++++++++++++++++++++++++------------------------------- 1 file changed, 92 insertions(+), 121 deletions(-) diff --git a/fonts.js b/fonts.js index ad008c375..0c6545f0c 100644 --- a/fonts.js +++ b/fonts.js @@ -1631,133 +1631,104 @@ CFF.prototype = { }, wrap: function wrap(name, glyphs, charstrings, subrs, properties) { - function stringToArray(str) { - var array = []; - for (var i = 0; i < str.length; ++i) - array[i] = str.charCodeAt(i); + var fields = { + "header": "\x01\x00\x04\x04", // major version, minor version, header size, offset size - return array; + "names": this.createCFFIndexHeader([name]), + + "topDict": (function topDict(self) { + return function() { + var dict = + "\x00\x01\x01\x01\x30" + + "\xf8\x1b\x00" + // version + "\xf8\x1b\x01" + // Notice + "\xf8\x1b\x02" + // FullName + "\xf8\x1b\x03" + // FamilyName + "\xf8\x1b\x04" + // Weight + "\x1c\x00\x00\x10"; // Encoding + + var boundingBox = properties.bbox; + for (var i = 0; i < boundingBox.length; i++) + dict += self.encodeNumber(boundingBox[i]); + dict += "\x05"; // FontBBox; + + var offset = fields.header.length + + fields.names.length + + (dict.length + (4 + 4 + 7)) + + fields.strings.length + + fields.globalSubrs.length; + dict += self.encodeNumber(offset) + "\x0f"; // Charset + + offset = offset + (glyphs.length * 2) + 1; + dict += self.encodeNumber(offset) + "\x11"; // Charstrings + + dict += self.encodeNumber(fields.private.length); + var offset = offset + fields.charstrings.length; + dict += self.encodeNumber(offset) + "\x12"; // Private + + return dict; + }; + })(this), + + "strings": (function strings(self) { + var strings = [ + "Version 0.11", // Version + "See original notice", // Notice + name, // FullName + name, // FamilyName + "Medium" // Weight + ]; + return self.createCFFIndexHeader(strings); + })(this), + + "globalSubrs": this.createCFFIndexHeader([]), + + "charset": (function charset(self) { + var charset = "\x00"; // Encoding + + var count = glyphs.length; + for (var i = 0; i < count; i++) { + var index = CFFStrings.indexOf(charstrings[i].glyph.glyph); + // Some characters like asterikmath && circlecopyrt are missing from the + // original strings, for the moment let's map them to .notdef and see + // later if it cause any problems + if (index == -1) + index = 0; + + var bytes = FontsUtils.integerToBytes(index, 2); + charset += String.fromCharCode(bytes[0]) + String.fromCharCode(bytes[1]); + } + return charset; + })(this), + + "charstrings": this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), true), + + "private": (function(self) { + var data = + "\x8b\x14" + // defaultWidth + "\x8b\x15" + // nominalWidth + "\x8b\x0a" + // StdHW + "\x8b\x0a" + // StdVW + "\x8b\x8b\x0c\x0c" + // StemSnapH + "\x8b\x8b\x0c\x0d"; // StemSnapV + data += self.encodeNumber(data.length + 4) + "\x13"; // Subrs offset + + return data; + })(this), + + "localSubrs": this.createCFFIndexHeader(subrs, true) }; + fields.topDict = fields.topDict(); - var cff = new Uint8Array(kMaxFontFileSize); - var currentOffset = 0; - // Font header (major version, minor version, header size, offset size) - var header = "\x01\x00\x04\x04"; - currentOffset += header.length; - cff.set(stringToArray(header)); - - // Names Index - var nameIndex = stringToArray(this.createCFFIndexHeader([name])); - cff.set(nameIndex, currentOffset); - currentOffset += nameIndex.length; - - // Calculate strings before writing the TopDICT index in order - // to calculate correct relative offsets for storing 'charset' - // and 'charstrings' data - var version = ""; - var notice = ""; - var fullName = ""; - var familyName = ""; - var weight = ""; - var strings = [version, notice, fullName, - familyName, weight]; - var stringsIndex = stringToArray(this.createCFFIndexHeader(strings)); - var stringsDataLength = stringsIndex.length; - - // Create the global subroutines index - var globalSubrsIndex = stringToArray(this.createCFFIndexHeader([])); - - // Fill the charset header (first byte is the encoding) - var charset = [0x00]; - var glyphsCount = glyphs.length; - for (var i = 0; i < glyphsCount; i++) { - var index = CFFStrings.indexOf(charstrings[i].glyph); - if (index == -1) - index = CFFStrings.length + strings.indexOf(charstrings[i].glyph); - - var bytes = FontsUtils.integerToBytes(index, 2); - charset.push(bytes[0]); - charset.push(bytes[1]); + var cff = []; + for (var index in fields) { + var field = fields[index]; + for (var i = 0; i < field.length; i++) + cff.push(field.charCodeAt(i)); } - var charstringsIndex = stringToArray(this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), true)); - - //Top Dict Index - var topDictIndex = [ - 0x00, 0x01, 0x01, 0x01, 0x30, - 248, 27, 0, // version - 248, 28, 1, // Notice - 248, 29, 2, // FullName - 248, 30, 3, // FamilyName - 248, 31, 4 // Weight - ]; - - var fontBBox = properties.bbox; - for (var i = 0; i < fontBBox.length; i++) - topDictIndex = topDictIndex.concat(stringToArray(this.encodeNumber(fontBBox[i]))); - topDictIndex.push(5) // FontBBox; - - var charsetOffset = currentOffset + - (topDictIndex.length + (4 + 4 + 4 + 7)) + - stringsIndex.length + - globalSubrsIndex.length; - topDictIndex = topDictIndex.concat(stringToArray(this.encodeNumber(charsetOffset))); - topDictIndex.push(15); // charset - - topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding - - var charstringsOffset = charsetOffset + (glyphsCount * 2) + 1; - topDictIndex = topDictIndex.concat(stringToArray(this.encodeNumber(charstringsOffset))); - topDictIndex.push(17); // charstrings - - topDictIndex = topDictIndex.concat([28, 0, 55]) - var privateOffset = charstringsOffset + charstringsIndex.length; - topDictIndex = topDictIndex.concat(stringToArray(this.encodeNumber(privateOffset))); - topDictIndex.push(18); // Private - - var indexes = [ - topDictIndex, stringsIndex, - globalSubrsIndex, charset, - charstringsIndex - ]; - - for (var i = 0; i < indexes.length; i++) { - var index = indexes[i]; - cff.set(index, currentOffset); - currentOffset += index.length; - } - - // Private Data - var defaultWidth = stringToArray(this.encodeNumber(0)); - var privateData = [].concat( - defaultWidth, [20], - [139, 21], // nominalWidth - [ - 119, 159, 248, 97, 159, 247, 87, 159, 6, - 30, 10, 3, 150, 37, 255, 12, 9, - 139, 12, - 10, 172, 10, - 172, 150, 143, 146, 150, 146, 12, 12, - 247, 32, 11, - 247, 10, 161, 147, 154, 150, 143, 12, 13, - 139, 12, 14, - 28, 0, 55, 19 // Subrs offset - ]); - cff.set(privateData, currentOffset); - currentOffset += privateData.length; - - - // Local subrs - var subrsData = stringToArray(this.createCFFIndexHeader(subrs, true)); - cff.set(subrsData, currentOffset); - currentOffset += subrsData.length; - - var fontData = []; - for (var i = 0; i < currentOffset; i++) - fontData.push(cff[i]); - - return fontData; + return cff; } }; From ae2d130f40fda83f24c3222e953e448864e190f2 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 1 Jul 2011 05:16:27 +0200 Subject: [PATCH 04/11] Improve the extractInfo code to be more robust --- fonts.js | 78 +++++++++++++++++++++++++------------------------------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/fonts.js b/fonts.js index a3cb05a9f..bc20e22e1 100644 --- a/fonts.js +++ b/fonts.js @@ -1333,14 +1333,19 @@ var Type1Parser = function() { * extracted from and eexec encrypted block of data */ this.extractFontProgram = function t1_extractFontProgram(stream) { - var eexecString = decrypt(stream, kEexecEncryptionKey, 4); - var subrs = [], glyphs = []; - var inGlyphs = false; - var inSubrs = false; - var glyph = ""; + var eexec = decrypt(stream, kEexecEncryptionKey, 4); + var eexecString = ""; + for (var i = 0; i < eexec.length; i++) + eexecString += String.fromCharCode(eexec[i]); + var glyphsSection = false, subrsSection = false; + var extracted = { + subrs: [], + charstrings: [] + }; + + var glyph = ""; var token = ""; - var index = 0; var length = 0; var c = ""; @@ -1348,52 +1353,39 @@ var Type1Parser = function() { for (var i = 0; i < count; i++) { var c = eexecString[i]; - if (inSubrs && c == 0x52) { - length = parseInt(length); - var data = eexecString.slice(i + 3, i + 3 + length); - var encodedSubr = decrypt(data, kCharStringsEncryptionKey, 4); - var str = decodeCharString(encodedSubr); + if ((glyphsSection || subrsSection) && c == "R") { + var data = eexec.slice(i + 3, i + 3 + length); + var encoded = decrypt(data, kCharStringsEncryptionKey, 4); + var str = decodeCharString(encoded); - subrs.push(str.charstring); - i += 3 + length; - } else if (inGlyphs && c == 0x52) { - length = parseInt(length); - var data = eexecString.slice(i + 3, i + 3 + length); - var encodedCharstring = decrypt(data, kCharStringsEncryptionKey, 4); - var str = decodeCharString(encodedCharstring); - - glyphs.push({ + if (glyphsSection) { + extracted.charstrings.push({ glyph: glyph, data: str.charstring, lsb: str.lsb, width: str.width - }); - i += 3 + length; - } else if (inGlyphs && c == 0x2F) { + }); + } else { + extracted.subrs.push(str.charstring); + } + i += length + 3; + } else if (c == " ") { + length = parseInt(token); token = ""; - glyph = ""; - - while ((c = eexecString[++i]) != 0x20) - glyph += String.fromCharCode(c); - } else if (!inSubrs && !inGlyphs && c == 0x2F && eexecString[i+1] == 0x53) { - while ((c = eexecString[++i]) != 0x20) {}; - inSubrs = true; - } else if (c == 0x20) { - index = length; - length = token; - token = ""; - } else if (c == 0x2F && eexecString[i+1] == 0x43 && eexecString[i+2] == 0x68) { - while ((c = eexecString[++i]) != 0x20) {}; - inSubrs = false; - inGlyphs = true; } else { - token += String.fromCharCode(c); + token += c; + if (!glyphsSection) { + glyphsSection = token.indexOf("/CharString") != -1; + subrsSection = subrsSection || token.indexOf("Subrs") != -1; + } else if (c == "/") { + token = glyph = ""; + while ((c = eexecString[++i]) != " ") + glyph += c; + } } } - return { - subrs: subrs, - charstrings: glyphs - } + + return extracted; } }; From e13164eca6a37c71c01ffa5a3ea672a508de564b Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 1 Jul 2011 07:16:56 +0200 Subject: [PATCH 05/11] Read the text matrix from the Type1 font ascii header --- fonts.js | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- pdf.js | 6 ++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/fonts.js b/fonts.js index bc20e22e1..1e32dfbc3 100644 --- a/fonts.js +++ b/fonts.js @@ -8,7 +8,7 @@ var isWorker = (typeof window == "undefined"); /** * Maximum file size of the font. */ -var kMaxFontFileSize = 200000; +var kMaxFontFileSize = 40000; /** * Maximum time to wait for a font to be loaded by @font-face @@ -77,6 +77,9 @@ var Fonts = (function Fonts() { measureCache = sizes[size] = Object.create(null); ctx.font = (size * kScalePrecision) + 'px "' + fontName + '"'; }, + getActive: function fonts_getActive() { + return current; + }, charsToUnicode: function fonts_chars2Unicode(chars) { if (!charsCache) return chars; @@ -1386,7 +1389,57 @@ var Type1Parser = function() { } return extracted; - } + }, + + this.extractFontHeader = function t1_extractFontProgram(stream) { + var headerString = ""; + for (var i = 0; i < stream.length; i++) + headerString += String.fromCharCode(stream[i]); + + var info = { + textMatrix: null + }; + + function readNumberArray(str, index) { + var start = ++index; + var count = 0; + while ((c = str[index++]) != "]") + count++; + + var array = str.substr(start, count).split(" "); + for (var i = 0; i < array.length; i++) + array[i] = parseFloat(array[i]); + return array; + }; + + var token = ""; + var count = headerString.length; + for (var i = 0; i < count; i++) { + var c = headerString[i]; + if (c == " " || c == "\n") { + switch (token) { + case "/FontMatrix": + var matrix = readNumberArray(headerString, i + 1); + + // The FontMatrix is in unitPerEm, so make it pixels + for (var j = 0; j < matrix.length; j++) + matrix[j] *= 1000; + + // Make the angle into the right direction + matrix[2] *= -1; + + info.textMatrix = matrix; + break; + } + token = ""; + } else { + token += c; + } + } + + return info; + }; + }; /** @@ -1457,7 +1510,12 @@ var CFF = function(name, file, properties) { // Get the data block containing glyphs and subrs informations var length1 = file.dict.get("Length1"); var length2 = file.dict.get("Length2"); - file.skip(length1); + + var headerBlock = file.getBytes(length1); + var header = type1Parser.extractFontHeader(headerBlock); + for (var info in header) { + properties[info] = header[info]; + } // Decrypt the data blocks and retrieve it's content var eexecBlock = file.getBytes(length2); @@ -1700,6 +1758,7 @@ CFF.prototype = { "charstrings": this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), true), "private": (function(self) { + log(properties.stemSnapH); var data = "\x8b\x14" + // defaultWidth "\x8b\x15" + // nominalWidth diff --git a/pdf.js b/pdf.js index 3e3654087..f0e58b1b7 100644 --- a/pdf.js +++ b/pdf.js @@ -3544,7 +3544,8 @@ var CanvasGraphics = (function() { capHeight: descriptor.get("CapHeight"), flags: descriptor.get("Flags"), italicAngle: descriptor.get("ItalicAngle"), - fixedPitch: false + fixedPitch: false, + textMatrix: IDENTITY_MATRIX.slice() }; return { @@ -3861,7 +3862,6 @@ var CanvasGraphics = (function() { // TODO: apply charSpacing, wordSpacing, textHScale this.ctx.save(); - this.ctx.transform.apply(this.ctx, this.current.textMatrix); this.ctx.scale(1, -1); if (this.ctx.$showText) { @@ -3869,6 +3869,8 @@ var CanvasGraphics = (function() { } else { text = Fonts.charsToUnicode(text); this.ctx.translate(this.current.x, -1 * this.current.y); + var matrix = Fonts.lookup(this.current.fontName).properties.textMatrix; + this.ctx.transform.apply(this.ctx, matrix); this.ctx.fillText(text, 0, 0); this.current.x += Fonts.measureText(text); } From 22264c07c89f0539fc274fb6db0cf632b61adcc3 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 1 Jul 2011 07:24:48 +0200 Subject: [PATCH 06/11] Fix TrueType bustage --- pdf.js | 1 + 1 file changed, 1 insertion(+) diff --git a/pdf.js b/pdf.js index f0e58b1b7..12ab5acc3 100644 --- a/pdf.js +++ b/pdf.js @@ -3862,6 +3862,7 @@ var CanvasGraphics = (function() { // TODO: apply charSpacing, wordSpacing, textHScale this.ctx.save(); + this.ctx.transform.apply(this.ctx, this.current.textMatrix); this.ctx.scale(1, -1); if (this.ctx.$showText) { From 63e4f0293f6a0f39b8bb65ba3d0f60c87a973123 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 1 Jul 2011 11:28:22 +0200 Subject: [PATCH 07/11] Add support for stemHW/stemVW/stemSnapH/stemSnapV --- fonts.js | 78 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/fonts.js b/fonts.js index 1e32dfbc3..880480b29 100644 --- a/fonts.js +++ b/fonts.js @@ -1335,6 +1335,18 @@ var Type1Parser = function() { * Returns an object containing a Subrs array and a CharStrings array * extracted from and eexec encrypted block of data */ + function readNumberArray(str, index) { + var start = ++index; + var count = 0; + while (str[index++] != "]") + count++; + + var array = str.substr(start, count).split(" "); + for (var i = 0; i < array.length; i++) + array[i] = parseFloat(array[i] || 0); + return array; + }; + this.extractFontProgram = function t1_extractFontProgram(stream) { var eexec = decrypt(stream, kEexecEncryptionKey, 4); var eexecString = ""; @@ -1344,7 +1356,11 @@ var Type1Parser = function() { var glyphsSection = false, subrsSection = false; var extracted = { subrs: [], - charstrings: [] + charstrings: [], + properties: { + stemSnapH: [0, 0], + stemSnapV: [0, 0] + } }; var glyph = ""; @@ -1372,14 +1388,32 @@ var Type1Parser = function() { extracted.subrs.push(str.charstring); } i += length + 3; - } else if (c == " ") { + } else if (c == " " || c == "\n") { length = parseInt(token); token = ""; } else { token += c; if (!glyphsSection) { - glyphsSection = token.indexOf("/CharString") != -1; - subrsSection = subrsSection || token.indexOf("Subrs") != -1; + switch (token) { + case "/CharString": + glyphsSection = true; + break; + case "/Subrs": + subrsSection = true; + break; + case "/StdHW": + extracted.properties.stdHW = readNumberArray(eexecString, i + 2)[0]; + break; + case "/StdVW": + extracted.properties.stdVW = readNumberArray(eexecString, i + 2)[0]; + break; + case "/StemSnapH": + extracted.properties.stemSnapH = readNumberArray(eexecString, i + 2); + break; + case "/StemSnapV": + extracted.properties.stemSnapV = readNumberArray(eexecString, i + 2); + break; + } } else if (c == "/") { token = glyph = ""; while ((c = eexecString[++i]) != " ") @@ -1400,18 +1434,6 @@ var Type1Parser = function() { textMatrix: null }; - function readNumberArray(str, index) { - var start = ++index; - var count = 0; - while ((c = str[index++]) != "]") - count++; - - var array = str.substr(start, count).split(" "); - for (var i = 0; i < array.length; i++) - array[i] = parseFloat(array[i]); - return array; - }; - var token = ""; var count = headerString.length; for (var i = 0; i < count; i++) { @@ -1439,7 +1461,6 @@ var Type1Parser = function() { return info; }; - }; /** @@ -1513,13 +1534,14 @@ var CFF = function(name, file, properties) { var headerBlock = file.getBytes(length1); var header = type1Parser.extractFontHeader(headerBlock); - for (var info in header) { + for (var info in header) properties[info] = header[info]; - } // Decrypt the data blocks and retrieve it's content var eexecBlock = file.getBytes(length2); var data = type1Parser.extractFontProgram(eexecBlock); + for (var info in data.properties) + properties[info] = data.properties[info]; var charstrings = this.getOrderedCharStrings(data.charstrings); var type2Charstrings = this.getType2Charstrings(charstrings); @@ -1758,14 +1780,22 @@ CFF.prototype = { "charstrings": this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs), true), "private": (function(self) { - log(properties.stemSnapH); var data = "\x8b\x14" + // defaultWidth "\x8b\x15" + // nominalWidth - "\x8b\x0a" + // StdHW - "\x8b\x0a" + // StdVW - "\x8b\x8b\x0c\x0c" + // StemSnapH - "\x8b\x8b\x0c\x0d"; // StemSnapV + self.encodeNumber(properties.stdHW) + "\x0a" + // StdHW + self.encodeNumber(properties.stdVW) + "\x0b"; // StdVW + + var stemH = properties.stemSnapH; + for (var i = 0; i < stemH.length; i++) + data += self.encodeNumber(stemH[i]); + data += "\x0c\x0c"; // StemSnapH + + var stemV = properties.stemSnapV; + for (var i = 0; i < stemV.length; i++) + data += self.encodeNumber(stemV[i]); + data += "\x0c\x0d"; // StemSnapV + data += self.encodeNumber(data.length + 4) + "\x13"; // Subrs offset return data; From d10cf7c9298f5eccf7fff70a78b0b48f77375227 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sat, 2 Jul 2011 00:19:24 +0200 Subject: [PATCH 08/11] Reland commit 442d184 but make it works with uncompressed PDF --- fonts.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fonts.js b/fonts.js index 880480b29..afd48b401 100644 --- a/fonts.js +++ b/fonts.js @@ -624,6 +624,8 @@ var Font = (function () { }; function replaceCMapTable(cmap, font, properties) { + font.pos = (font.start ? font.start : 0) + cmap.length; + var version = FontsUtils.bytesToInteger(font.getBytes(2)); var numTables = FontsUtils.bytesToInteger(font.getBytes(2)); From 3dcf65d9df5e9557b78727663ef51d3ae0f1b09b Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sat, 2 Jul 2011 01:01:55 +0200 Subject: [PATCH 09/11] Fix a small issue in the waiting for font to load code --- fonts.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/fonts.js b/fonts.js index afd48b401..e484bc5b3 100644 --- a/fonts.js +++ b/fonts.js @@ -38,11 +38,11 @@ var kDisableFonts = false; var Fonts = (function Fonts() { var kScalePrecision = 40; - var fonts = Object.create(null); + var fonts = Object.create(null); if (!isWorker) { var ctx = document.createElement("canvas").getContext("2d"); - ctx.scale(1 / kScalePrecision, 1); + ctx.scale(1 / kScalePrecision, 1); } function Font(name, data, properties) { @@ -77,9 +77,6 @@ var Fonts = (function Fonts() { measureCache = sizes[size] = Object.create(null); ctx.font = (size * kScalePrecision) + 'px "' + fontName + '"'; }, - getActive: function fonts_getActive() { - return current; - }, charsToUnicode: function fonts_chars2Unicode(chars) { if (!charsCache) return chars; @@ -477,10 +474,11 @@ var Font = (function () { startCount += string16(start); endCount += string16(end); idDeltas += string16(delta); - idRangeOffsets += string16(0); + idRangeOffsets += string16(0); - for (var j = 0; j < range.length; j++) - glyphsIds += String.fromCharCode(range[j]); + for (var j = start; j < end; j++) { + glyphsIds += string16(j); + } } startCount += "\xFF\xFF"; @@ -1034,8 +1032,9 @@ var Font = (function () { // different once the real font has loaded var textWidth = ctx.measureText(testString).width; + var start = Date.now(); var interval = window.setInterval(function canvasInterval(self) { - this.start = this.start || Date.now(); + this.start = start; ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; // For some reasons the font has not loaded, so mark it loaded for the From 3726686d22deac5d299b890bd0f7359053717956 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sat, 2 Jul 2011 02:44:57 +0200 Subject: [PATCH 10/11] Add a format100 table for Mac --- fonts.js | 44 +++++++++++++++++++++++++++++--------------- utils/fonts_utils.js | 6 +----- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/fonts.js b/fonts.js index e484bc5b3..ff25a08ee 100644 --- a/fonts.js +++ b/fonts.js @@ -437,6 +437,24 @@ var Font = (function () { glyphs.push({ unicode: 0x0000 }); var ranges = getRanges(glyphs); + var numTables = 2; + var kFormat100ArraySize = 256; + var cmap = "\x00\x00" + // version + string16(numTables) + // numTables + "\x00\x01" + // platformID + "\x00\x00" + // encodingID + string32(4 + numTables * 8) + // start of the table record + "\x00\x03" + // platformID + "\x00\x01" + // encodingID + string32(4 + numTables * 8 + 6 + kFormat100ArraySize); // start of the table record + + var format100 = "\x00\x00" + // format + string16(6 + kFormat100ArraySize) + // length + "\x00\x00"; // language + + for (var i = 0; i < kFormat100ArraySize; i++) + format100 += String.fromCharCode(i); + var headerSize = (12 * 2 + (ranges.length * 4 * 2)); var segCount = ranges.length + 1; var segCount2 = segCount * 2; @@ -444,18 +462,13 @@ var Font = (function () { 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); + var format314 = "\x00\x04" + // format + string16(headerSize) + // length + "\x00\x00" + // language + string16(segCount2) + + string16(searchRange) + + string16(searchEntry) + + string16(rangeShift); // Fill up the 4 parallel arrays describing the segments. var startCount = ""; @@ -481,13 +494,14 @@ var Font = (function () { } } - startCount += "\xFF\xFF"; endCount += "\xFF\xFF"; + startCount += "\xFF\xFF"; idDeltas += "\x00\x01"; idRangeOffsets += "\x00\x00"; + format314 += endCount + "\x00\x00" + startCount + + idDeltas + idRangeOffsets + glyphsIds; - return stringToArray(cmap + endCount + "\x00\x00" + startCount + - idDeltas + idRangeOffsets + glyphsIds); + return stringToArray(cmap + format100 + format314); }; function createOS2Table(properties) { diff --git a/utils/fonts_utils.js b/utils/fonts_utils.js index 532853460..5bd5f2972 100644 --- a/utils/fonts_utils.js +++ b/utils/fonts_utils.js @@ -1,8 +1,6 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -"use strict"; - /** * The Type2 reader code below is only used for debugging purpose since Type2 * is only a CharString format and is never used directly as a Font file. @@ -12,8 +10,6 @@ * 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 * of the glyph data. @@ -388,7 +384,7 @@ function writeToFile(aBytes, aFilePath) { var stream = Cc["@mozilla.org/network/file-output-stream;1"] .createInstance(Ci.nsIFileOutputStream); - stream.init(file, 0x04 | 0x08 | 0x20, 600, 0); + stream.init(file, 0x04 | 0x08 | 0x20, 0600, 0); var bos = Cc["@mozilla.org/binaryoutputstream;1"] .createInstance(Ci.nsIBinaryOutputStream); From 3e78538c1c8a753bd56ab7f25362f936a1eee8f3 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Sat, 2 Jul 2011 05:46:50 +0200 Subject: [PATCH 11/11] Remove cmap format 100, fixes some nits to merge with upstream --- fonts.js | 27 ++++++++------------------- pdf.js | 11 +++++++---- utils/fonts_utils.js | 7 ++++++- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/fonts.js b/fonts.js index ff25a08ee..a871520ea 100644 --- a/fonts.js +++ b/fonts.js @@ -437,25 +437,14 @@ var Font = (function () { glyphs.push({ unicode: 0x0000 }); var ranges = getRanges(glyphs); - var numTables = 2; - var kFormat100ArraySize = 256; + var numTables = 1; var cmap = "\x00\x00" + // version string16(numTables) + // numTables - "\x00\x01" + // platformID - "\x00\x00" + // encodingID - string32(4 + numTables * 8) + // start of the table record "\x00\x03" + // platformID "\x00\x01" + // encodingID - string32(4 + numTables * 8 + 6 + kFormat100ArraySize); // start of the table record + string32(4 + numTables * 8); // start of the table record - var format100 = "\x00\x00" + // format - string16(6 + kFormat100ArraySize) + // length - "\x00\x00"; // language - - for (var i = 0; i < kFormat100ArraySize; i++) - format100 += String.fromCharCode(i); - - var headerSize = (12 * 2 + (ranges.length * 4 * 2)); + var headerSize = (12 * 2 + (ranges.length * 5 * 2)); var segCount = ranges.length + 1; var segCount2 = segCount * 2; var searchRange = FontsUtils.getMaxPower2(segCount) * 2; @@ -481,7 +470,7 @@ var Font = (function () { var range = ranges[i]; var start = range[0]; var end = range[1]; - var delta = (((start - 1) - bias) ^ 0xffff); + var delta = (bias - start) % 0xffff; bias += (end - start + 1); startCount += string16(start); @@ -489,7 +478,7 @@ var Font = (function () { idDeltas += string16(delta); idRangeOffsets += string16(0); - for (var j = start; j < end; j++) { + for (var j = start; j <= end; j++) { glyphsIds += string16(j); } } @@ -501,7 +490,7 @@ var Font = (function () { format314 += endCount + "\x00\x00" + startCount + idDeltas + idRangeOffsets + glyphsIds; - return stringToArray(cmap + format100 + format314); + return stringToArray(cmap + format314); }; function createOS2Table(properties) { @@ -541,7 +530,7 @@ var Font = (function () { "\x02\x24" + // xAvgCharWidth "\x01\xF4" + // usWeightClass "\x00\x05" + // usWidthClass - "\x00\x02" + // fstype + "\x00\x00" + // fstype (0 to let the font loads via font-face on IE) "\x02\x8A" + // ySubscriptXSize "\x02\xBB" + // ySubscriptYSize "\x00\x00" + // ySubscriptXOffset @@ -1075,7 +1064,7 @@ var Font = (function () { var url = "url(data:" + this.mimetype + ";base64," + window.btoa(data) + ");"; var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; - styleSheet.insertRule(rule, styleSheet.length); + styleSheet.insertRule(rule, styleSheet.cssRules.length); } }; diff --git a/pdf.js b/pdf.js index 12ab5acc3..493dd7ef0 100644 --- a/pdf.js +++ b/pdf.js @@ -3404,7 +3404,7 @@ var CanvasGraphics = (function() { BX: "beginCompat", EX: "endCompat", }, - + translateFont: function(fontDict, xref, resources) { var fd = fontDict.get("FontDescriptor"); if (!fd) @@ -3545,7 +3545,7 @@ var CanvasGraphics = (function() { flags: descriptor.get("Flags"), italicAngle: descriptor.get("ItalicAngle"), fixedPitch: false, - textMatrix: IDENTITY_MATRIX.slice() + textMatrix: IDENTITY_MATRIX }; return { @@ -3870,8 +3870,11 @@ var CanvasGraphics = (function() { } else { text = Fonts.charsToUnicode(text); this.ctx.translate(this.current.x, -1 * this.current.y); - var matrix = Fonts.lookup(this.current.fontName).properties.textMatrix; - this.ctx.transform.apply(this.ctx, matrix); + + var font = Fonts.lookup(this.current.fontName); + if (font) + this.ctx.transform.apply(this.ctx, font.properties.textMatrix); + this.ctx.fillText(text, 0, 0); this.current.x += Fonts.measureText(text); } diff --git a/utils/fonts_utils.js b/utils/fonts_utils.js index 5bd5f2972..bc0a8544c 100644 --- a/utils/fonts_utils.js +++ b/utils/fonts_utils.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"; + /** * The Type2 reader code below is only used for debugging purpose since Type2 * is only a CharString format and is never used directly as a Font file. @@ -376,6 +378,9 @@ var Type2Parser = function(aFilePath) { * writeToFile(fontData, "/tmp/pdf.js." + fontCount + ".cff"); */ function writeToFile(aBytes, aFilePath) { + if (!("netscape" in window)) + return; + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); var Cc = Components.classes, Ci = Components.interfaces; @@ -384,7 +389,7 @@ function writeToFile(aBytes, aFilePath) { var stream = Cc["@mozilla.org/network/file-output-stream;1"] .createInstance(Ci.nsIFileOutputStream); - stream.init(file, 0x04 | 0x08 | 0x20, 0600, 0); + stream.init(file, 0x04 | 0x08 | 0x20, 0x180, 0); var bos = Cc["@mozilla.org/binaryoutputstream;1"] .createInstance(Ci.nsIBinaryOutputStream);