From 59e178946a33a6797139cd3011beb045b6d5803c Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Thu, 23 Jun 2011 22:11:16 +0200 Subject: [PATCH 01/28] Add a createPostTable function and remove the useless join('') calls --- fonts.js | 142 +++++++++++++++++++++++-------------------------------- 1 file changed, 60 insertions(+), 82 deletions(-) diff --git a/fonts.js b/fonts.js index d5943b7a3..f783c459c 100644 --- a/fonts.js +++ b/fonts.js @@ -298,56 +298,59 @@ var Font = (function () { }; 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; + return "\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 + }; + + function createPostTable() { + TODO("Fill with real values from the font dict"); + + return "\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 }; - /** - * 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 - * where all the code relative to OpenType will probably have its own - * class and will take decision without the Fonts consent. - * But at the moment it allows to develop around the TrueType rewriting - * on the fly without messing up with the 'regular' Type1 to OTF conversion. - */ constructor.prototype = { name: null, font: null, @@ -405,7 +408,7 @@ var Font = (function () { var length = FontsUtils.bytesToInteger(font.getBytes(2)); var language = FontsUtils.bytesToInteger(font.getBytes(2)); - if ((format == 0 && numTables == 1) || + if ((format == 0 && numTables == 1) || (format == 6 && numTables == 1 && !properties.encoding.empty)) { // Format 0 alone is not allowed by the sanitizer so let's rewrite // that to a 3-1-4 Unicode BMP table @@ -512,10 +515,9 @@ var Font = (function () { createOpenTypeHeader("\x00\x01\x00\x00", ttf, offsets, numTables); // Insert the missing table - var OS2 = createOS2Table(); tables.push({ tag: "OS/2", - data: OS2 + data: stringToArray(createOS2Table) }); // Replace the old CMAP table with a shiny new one @@ -523,20 +525,9 @@ var Font = (function () { // 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({ + tables.push({ tag: "post", - data: stringToArray(post) + data: stringToArray(createPostTable()) }); } @@ -586,10 +577,10 @@ var Font = (function () { function createNameTable(name) { var names = [ "See original licence", // Copyright - name, // Font family + name, // Font family "undefined", // Font subfamily (font weight) "uniqueID", // Unique ID - name, // Full font name + name, // Full font name "0.1", // Version "undefined", // Postscript name "undefined", // Trademark @@ -625,10 +616,10 @@ var Font = (function () { // Required Tables var CFF = - font.data, // PostScript Font Program + font.data, // PostScript Font Program OS2, // OS/2 and Windows Specific metrics cmap, // Character to glyphs mapping - head, // Font eader + head, // Font header hhea, // Horizontal header hmtx, // Horizontal metrics maxp, // Maximum profile @@ -728,17 +719,7 @@ var Font = (function () { 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); + post = stringToArray(createPostTable()); createTableEntry(otf, offsets, "post", post); // Once all the table entries header are written, dump the data! @@ -1480,7 +1461,6 @@ CFF.prototype = { } var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true); - charstringsIndex = charstringsIndex.join(" ").split(" "); // XXX why? //Top Dict Index var topDictIndex = [ @@ -1514,7 +1494,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, @@ -1544,7 +1523,6 @@ CFF.prototype = { 139, 12, 14, 28, 0, 55, 19 ]); - privateData = privateData.join(" ").split(" "); cff.set(privateData, currentOffset); currentOffset += privateData.length; From 873d6df1f580e9a9c01004a53ee55d7ad965654e Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Thu, 23 Jun 2011 22:20:55 +0200 Subject: [PATCH 02/28] Fix bustage of the previous commit --- fonts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fonts.js b/fonts.js index f783c459c..2c63adfd4 100644 --- a/fonts.js +++ b/fonts.js @@ -517,7 +517,7 @@ var Font = (function () { // Insert the missing table tables.push({ tag: "OS/2", - data: stringToArray(createOS2Table) + data: stringToArray(createOS2Table()) }); // Replace the old CMAP table with a shiny new one @@ -643,7 +643,7 @@ var Font = (function () { createTableEntry(otf, offsets, "CFF ", CFF); /** OS/2 */ - OS2 = createOS2Table(); + OS2 = stringToArray(createOS2Table()); createTableEntry(otf, offsets, "OS/2", OS2); //XXX Getting charstrings here seems wrong since this is another CFF glue From f240a5b811a897d77e83653c9f0e59e7d89d8e34 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Thu, 23 Jun 2011 22:22:46 +0200 Subject: [PATCH 03/28] Remove the ignoreFont variable --- pdf.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pdf.js b/pdf.js index addcea6de..f0d3bae65 100644 --- a/pdf.js +++ b/pdf.js @@ -2359,16 +2359,9 @@ 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 = false; - 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 @@ -2433,7 +2426,6 @@ var CanvasGraphics = (function() { break; case "beginbfrange": - ignoreFont = false; case "begincodespacerange": token = ""; tokens = []; @@ -2488,8 +2480,7 @@ var CanvasGraphics = (function() { type: subType.name, encoding: encodingMap, charset: charset, - bbox: bbox, - ignore: ignoreFont + bbox: bbox }; return { From b9a53361f82108b1137fb43d11a06adf9723962d Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Thu, 23 Jun 2011 23:15:22 +0200 Subject: [PATCH 04/28] Remove some hardcoded glue --- fonts.js | 79 +++++++++++++++++++++++++++----------------------------- pdf.js | 15 ++++++----- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/fonts.js b/fonts.js index 2c63adfd4..2c113d652 100644 --- a/fonts.js +++ b/fonts.js @@ -297,7 +297,7 @@ var Font = (function () { idDeltas + idRangeOffsets + glyphsIds); }; - function createOS2Table() { + function createOS2Table(properties) { return "\x00\x03" + // version "\x02\x24" + // xAvgCharWidth "\x01\xF4" + // usWeightClass @@ -326,29 +326,29 @@ var Font = (function () { "\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 + string16(properties.ascent) + // usWinAscent + string16(properties.descent) + // usWinDescent + "\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31) + "\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63) + string16(properties.xHeight) + // sxHeight + string16(properties.capHeight) + // sCapHeight + "\x00\x01" + // usDefaultChar + "\x00\xCD" + // usBreakChar + "\x00\x02"; // usMaxContext }; - function createPostTable() { + function createPostTable(properties) { TODO("Fill with real values from the font dict"); - return "\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 + return "\x00\x03\x00\x00" + // Version number + string32(properties.italicAngle) + // 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 }; constructor.prototype = { @@ -412,6 +412,7 @@ var Font = (function () { (format == 6 && numTables == 1 && !properties.encoding.empty)) { // Format 0 alone is not allowed by the sanitizer so let's rewrite // that to a 3-1-4 Unicode BMP table + TODO("Use an other source of informations than charset here, it is not reliable"); var charset = properties.charset; var glyphs = []; for (var j = 0; j < charset.length; j++) { @@ -517,7 +518,7 @@ var Font = (function () { // Insert the missing table tables.push({ tag: "OS/2", - data: stringToArray(createOS2Table()) + data: stringToArray(createOS2Table(properties)) }); // Replace the old CMAP table with a shiny new one @@ -527,7 +528,7 @@ var Font = (function () { if (!post) { tables.push({ tag: "post", - data: stringToArray(createPostTable()) + data: stringToArray(createPostTable(properties)) }); } @@ -643,14 +644,12 @@ var Font = (function () { createTableEntry(otf, offsets, "CFF ", CFF); /** OS/2 */ - OS2 = stringToArray(createOS2Table()); + OS2 = stringToArray(createOS2Table(properties)); 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); + var charstrings = font.charstrings; + cmap = createCMapTable(font.charstrings); createTableEntry(otf, offsets, "cmap", cmap); /** HEAD */ @@ -719,7 +718,7 @@ var Font = (function () { createTableEntry(otf, offsets, "name", name); /** POST */ - post = stringToArray(createPostTable()); + post = stringToArray(createPostTable(properties)); createTableEntry(otf, offsets, "post", post); // Once all the table entries header are written, dump the data! @@ -1187,6 +1186,8 @@ var CFFStrings = [ "001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold" ]; +var type1Parser = new Type1Parser(); + var CFF = function(name, file, properties) { // Get the data block containing glyphs and subrs informations var length1 = file.dict.get("Length1"); @@ -1194,13 +1195,11 @@ var CFF = function(name, file, properties) { 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); + // Decrypt the data blocks and retrieve it's content + var data = type1Parser.extractFontProgram(eexecBlock); - properties.subrs = fontInfo.subrs; - properties.glyphs = fontInfo.charstrings; - this.data = this.wrap(name, properties); + this.charstrings = this.getOrderedCharStrings(data.charstrings); + this.data = this.wrap(name, this.charstrings, data.subrs, properties); }; CFF.prototype = { @@ -1265,7 +1264,7 @@ CFF.prototype = { charstrings.push({ glyph: glyph, unicode: unicode, - charstring: glyphs[i].data.slice() + charstring: glyphs[i].data }); } }; @@ -1308,7 +1307,7 @@ CFF.prototype = { if (obj.charAt) { switch (obj) { case "callsubr": - var subr = subrs[charstring[i - 1]].slice(); + var subr = subrs[charstring[i - 1]]; if (subr.length > 1) { subr = this.flattenCharstring(glyph, subr, subrs); subr.pop(); @@ -1402,18 +1401,16 @@ CFF.prototype = { error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")"); }, - wrap: function wrap(name, properties) { - var charstrings = this.getOrderedCharStrings(properties.glyphs); - + wrap: function wrap(name, charstrings, subrs, properties) { // Starts the conversion of the Type1 charstrings to Type2 var charstringsCount = 0; var charstringsDataLength = 0; var glyphs = []; for (var i = 0; i < charstrings.length; i++) { - var charstring = charstrings[i].charstring.slice(); + var charstring = charstrings[i].charstring; var glyph = charstrings[i].glyph; - var flattened = this.flattenCharstring(glyph, charstring, properties.subrs); + var flattened = this.flattenCharstring(glyph, charstring, subrs); glyphs.push(flattened); charstringsCount++; charstringsDataLength += flattened.length; diff --git a/pdf.js b/pdf.js index f0d3bae65..c314b6e55 100644 --- a/pdf.js +++ b/pdf.js @@ -2384,9 +2384,8 @@ var CanvasGraphics = (function() { error("Unknown font encoding"); var index = 0; - for (var j = 0; j < encoding.length; j++) { + 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")); @@ -2472,15 +2471,19 @@ var CanvasGraphics = (function() { } var subType = fontDict.get("Subtype"); - var bbox = descriptor.get("FontBBox"); - assertWellFormed(IsName(subType) && IsArray(bbox), - "invalid font Subtype or FontBBox"); + assertWellFormed(IsName(subType), "invalid font Subtype"); var properties = { type: subType.name, encoding: encodingMap, charset: charset, - bbox: bbox + bbox: descriptor.get("FontBBox"), + ascent: descriptor.get("Ascent"), + descent: descriptor.get("Descent"), + xHeight: descriptor.get("XHeight"), + capHeight: descriptor.get("CapHeight"), + flags: descriptor.get("Flags"), + italicAngle: descriptor.get("ItalicAngle") }; return { From 0a18434a2da3320737029cf19e7caac86b2826f0 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 01:37:54 +0200 Subject: [PATCH 05/28] Remove some useless var --- fonts.js | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/fonts.js b/fonts.js index 2c113d652..971d8d257 100644 --- a/fonts.js +++ b/fonts.js @@ -649,7 +649,7 @@ var Font = (function () { /** CMAP */ var charstrings = font.charstrings; - cmap = createCMapTable(font.charstrings); + cmap = createCMapTable(charstrings); createTableEntry(otf, offsets, "cmap", cmap); /** HEAD */ @@ -1403,17 +1403,11 @@ CFF.prototype = { wrap: function wrap(name, charstrings, subrs, properties) { // Starts the conversion of the Type1 charstrings to Type2 - var charstringsCount = 0; - var charstringsDataLength = 0; - var glyphs = []; - for (var i = 0; i < charstrings.length; i++) { - var charstring = charstrings[i].charstring; - var glyph = charstrings[i].glyph; - - var flattened = this.flattenCharstring(glyph, charstring, subrs); - glyphs.push(flattened); - charstringsCount++; - charstringsDataLength += flattened.length; + var glyphs = charstrings.slice(); + var glyphsCount = glyphs.length; + for (var i = 0; i < glyphs.length; i++) { + var charstring = glyphs[i]; + glyphs[i] = this.flattenCharstring(charstring.glyph, charstring.charstring, subrs); } // Create a CFF font data @@ -1448,10 +1442,10 @@ CFF.prototype = { // Fill the charset header (first byte is the encoding) var charset = [0x00]; - for (var i = 0; i < glyphs.length; i++) { + for (var i = 0; i < glyphsCount; i++) { var index = CFFStrings.indexOf(charstrings[i].glyph); if (index == -1) - index = CFFStrings.length + strings.indexOf(glyph); + index = CFFStrings.length + strings.indexOf(charstrings[i].glyph); var bytes = FontsUtils.integerToBytes(index, 2); charset.push(bytes[0]); charset.push(bytes[1]); @@ -1483,7 +1477,7 @@ CFF.prototype = { topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding - var charstringsOffset = charsetOffset + (charstringsCount * 2) + 1; + var charstringsOffset = charsetOffset + (glyphsCount * 2) + 1; topDictIndex = topDictIndex.concat(this.encodeNumber(charstringsOffset)); topDictIndex.push(17); // charstrings From 0f1b9949b637511dfea5048aa1079afb17b0c400 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 02:45:58 +0200 Subject: [PATCH 06/28] Remove code to handle FlateStream differently now that we have a sane interface --- pdf.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pdf.js b/pdf.js index a7de0bb35..f61250a0c 100644 --- a/pdf.js +++ b/pdf.js @@ -2458,13 +2458,7 @@ var CanvasGraphics = (function() { var tokens = []; var token = ""; - var length = cmapObj.length; - if (cmapObj instanceof FlateStream) { - cmapObj.readBlock(); - length = cmapObj.bufferLength; - } - - var cmap = cmapObj.getBytes(length); + 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) { From d704bfacf418db5a891bea054218dd1021e54fc0 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 02:58:17 +0200 Subject: [PATCH 07/28] Fix Windows 'hmtx' bust table --- fonts.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/fonts.js b/fonts.js index 1169e21c6..6f5adbeef 100644 --- a/fonts.js +++ b/fonts.js @@ -710,11 +710,15 @@ var Font = (function () { createTableEntry(otf, offsets, "hhea", hhea); /** HMTX */ - hmtx = "\x01\xF4\x00\x00"; + /* For some reasons, probably related to how the backend handle fonts, + * Linux seems to ignore this file and prefer the data from the CFF itself + * while Windows use this data. So be careful if you hack on Linux and + * have to touch the 'hmtx' table + */ + hmtx = "\x01\xF4\x00\x00"; // Fake .notdef + var width = 0, lsb = 0; for (var i = 0; i < charstrings.length; i++) { - var charstring = charstrings[i].charstring; - var width = charstring[1]; - var lsb = charstring[0]; + width = charstrings[i].charstring[0]; hmtx += string16(width) + string16(lsb); } hmtx = stringToArray(hmtx); @@ -1314,7 +1318,7 @@ CFF.prototype = { "hvcurveto": 31, }, - flattenCharstring: function flattenCharstring(glyph, charstring, subrs) { + flattenCharstring: function flattenCharstring(charstring, subrs) { var i = 0; while (true) { var obj = charstring[i]; @@ -1326,7 +1330,7 @@ CFF.prototype = { case "callsubr": var subr = subrs[charstring[i - 1]]; if (subr.length > 1) { - subr = this.flattenCharstring(glyph, subr, subrs); + subr = this.flattenCharstring(subr, subrs); subr.pop(); charstring.splice(i - 1, 2, subr); } else { @@ -1420,11 +1424,11 @@ CFF.prototype = { wrap: function wrap(name, charstrings, subrs, properties) { // Starts the conversion of the Type1 charstrings to Type2 - var glyphs = charstrings.slice(); - var glyphsCount = glyphs.length; - for (var i = 0; i < glyphs.length; i++) { - var charstring = glyphs[i]; - glyphs[i] = this.flattenCharstring(charstring.glyph, charstring.charstring, subrs); + var glyphs = []; + var glyphsCount = charstrings.length; + for (var i = 0; i < glyphsCount; i++) { + var charstring = charstrings[i].charstring; + glyphs.push(this.flattenCharstring(charstring.slice(), subrs)); } // Create a CFF font data From 40006194eaa6283c22ce87ef358f5b9600e7ce85 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 03:01:41 +0200 Subject: [PATCH 08/28] Don't read the lsb instead of the width --- fonts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fonts.js b/fonts.js index 6f5adbeef..a995c55eb 100644 --- a/fonts.js +++ b/fonts.js @@ -718,7 +718,7 @@ var Font = (function () { hmtx = "\x01\xF4\x00\x00"; // Fake .notdef var width = 0, lsb = 0; for (var i = 0; i < charstrings.length; i++) { - width = charstrings[i].charstring[0]; + width = charstrings[i].charstring[1]; hmtx += string16(width) + string16(lsb); } hmtx = stringToArray(hmtx); From 86f197dabac95eeeff51c60f3373d6ee5565a6ab Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 11:47:22 +0200 Subject: [PATCH 09/28] Start adding a FontLoader class to isolate the font-loaded hack --- fonts.js | 53 ++++++++++++++++++++++++++++++++++------------------- viewer.js | 35 +++++++---------------------------- 2 files changed, 41 insertions(+), 47 deletions(-) diff --git a/fonts.js b/fonts.js index a995c55eb..0a1974571 100644 --- a/fonts.js +++ b/fonts.js @@ -80,6 +80,35 @@ var Fonts = { } }; +var FontsLoader = { + bind: function(fonts) { + var worker = (typeof window == "undefined"); + var ready = true; + + for (var i = 0; i < fonts.length; i++) { + var font = fonts[i]; + if (Fonts[font.name]) { + ready = ready && !Fonts[font.name].loading; + continue; + } else { + ready = false; + } + + var obj = new Font(font.name, font.file, font.properties); + + var str = ""; + var data = Fonts[font.name].data; + var length = data.length; + for (var j = 0; j < length; j++) + str += String.fromCharCode(data[j]); + + worker ? obj.bindWorker(str) : obj.bindDOM(str); + } + return ready; + } +}; + + /** * 'Font' is the class the outside world should use, it encapsulate all the font * decoding logics whatever type it is (assuming the font type is supported). @@ -113,13 +142,14 @@ var Font = (function () { return; } + var data; switch (properties.type) { case "Type1": var cff = new CFF(name, file, properties); this.mimetype = "font/opentype"; // Wrap the CFF data inside an OTF font file - this.font = this.convert(name, cff, properties); + data = this.convert(name, cff, properties); break; case "TrueType": @@ -127,7 +157,7 @@ var Font = (function () { // 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); + data = this.checkAndRepair(name, file, properties); break; default: @@ -135,28 +165,12 @@ var Font = (function () { break; } - var data = this.font; Fonts[name] = { data: data, properties: properties, loading: true, cache: Object.create(null) - } - - // Convert data to a string. - var dataStr = ""; - var length = data.length; - for (var i = 0; i < length; ++i) - dataStr += String.fromCharCode(data[i]); - - // Attach the font to the document. If this script is runnig in a worker, - // call `bindWorker`, which sends stuff over to the main thread. - if (typeof window != "undefined") { - this.bindDOM(dataStr); - } else { - this.bindWorker(dataStr); - } - + }; }; function stringToArray(str) { @@ -1420,6 +1434,7 @@ CFF.prototype = { i++; } error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")"); + return []; }, wrap: function wrap(name, charstrings, subrs, properties) { diff --git a/viewer.js b/viewer.js index 41aaf354c..2bcff50a6 100644 --- a/viewer.js +++ b/viewer.js @@ -3,7 +3,7 @@ "use strict"; -var pdfDocument, canvas, pageDisplay, pageNum, numPages, pageInterval; +var pdfDocument, canvas, pageDisplay, pageNum, numPages, pageTimeout; function load(userInput) { canvas = document.getElementById("canvas"); canvas.mozOpaque = true; @@ -52,7 +52,7 @@ function gotoPage(num) { } function displayPage(num) { - window.clearInterval(pageInterval); + window.clearTimeout(pageTimeout); document.getElementById("pageNumber").value = num; @@ -75,28 +75,12 @@ function displayPage(num) { page.compile(gfx, fonts); var t2 = Date.now(); - var fontsReady = true; - - // Inspect fonts and translate the missing one - var count = fonts.length; - for (var i = 0; i < count; i++) { - var font = fonts[i]; - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; + function loadFont() { + if (!FontsLoader.bind(fonts)) { + pageTimeout = window.setTimeout(loadFont, 10); + return; } - new Font(font.name, font.file, font.properties); - fontsReady = false; - } - - function delayLoadFont() { - for (var i = 0; i < count; i++) { - if (Fonts[font.name].loading) - return; - } - window.clearInterval(pageInterval); - var t3 = Date.now(); page.display(gfx); @@ -106,12 +90,7 @@ function displayPage(num) { var infoDisplay = document.getElementById("info"); infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms"; }; - - if (fontsReady) { - delayLoadFont(); - } else { - pageInterval = setInterval(delayLoadFont, 10); - } + loadFont(); } function nextPage() { From 3955ed1b4c6a6ee0d0643c78ab6ee4f0c5d4b440 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 11:58:05 +0200 Subject: [PATCH 10/28] Fix a bunch of warnings from Firebug strict mode --- fonts.js | 27 +++++++++++++++------------ pdf.js | 56 ++++++++++++++++++++++++++++++-------------------------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/fonts.js b/fonts.js index 0a1974571..7e8aecd6d 100644 --- a/fonts.js +++ b/fonts.js @@ -599,16 +599,16 @@ var Font = (function () { return font.getBytes(); }, - convert: function font_convert(name, font, properties) { + convert: function font_convert(fontName, font, properties) { var otf = new Uint8Array(kMaxFontFileSize); function createNameTable(name) { var names = [ "See original licence", // Copyright - name, // Font family + fontName, // Font family "undefined", // Font subfamily (font weight) "uniqueID", // Unique ID - name, // Full font name + fontName, // Full font name "0.1", // Version "undefined", // Postscript name "undefined", // Trademark @@ -616,7 +616,7 @@ var Font = (function () { "undefined" // Designer ]; - var name = + var nameTable = "\x00\x00" + // format "\x00\x0A" + // Number of names Record "\x00\x7E"; // Storage @@ -633,13 +633,13 @@ var Font = (function () { "\x00\x00" + // name ID string16(str.length) + string16(strOffset); - name += nameRecord; + nameTable += nameRecord; strOffset += str.length; } - name += names.join(""); - return name; + nameTable += names.join(""); + return nameTable; } // Required Tables @@ -885,6 +885,9 @@ var FontsUtils = { bytes.set([value >> 24, value >> 16, value >> 8, value]); return [bytes[0], bytes[1], bytes[2], bytes[3]]; } + + error("This number of bytes " + bytesCount + " is not supported"); + return null; }, bytesToInteger: function fu_bytesToInteger(bytesArray) { @@ -1238,7 +1241,7 @@ var CFF = function(name, file, properties) { }; CFF.prototype = { - createCFFIndexHeader: function(objects, isByte) { + createCFFIndexHeader: function cff_createCFFIndexHeader(objects, isByte) { // First 2 bytes contains the number of objects contained into this index var count = objects.length; @@ -1275,18 +1278,18 @@ CFF.prototype = { return data; }, - encodeNumber: function(value) { + encodeNumber: function cff_encodeNumber(value) { var x = 0; 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: " + value + " is not allowed"); } + error("Value: " + value + " is not allowed"); + return null; }, - getOrderedCharStrings: function(glyphs) { + getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs) { var charstrings = []; for (var i = 0; i < glyphs.length; i++) { diff --git a/pdf.js b/pdf.js index f61250a0c..c4ebb5f24 100644 --- a/pdf.js +++ b/pdf.js @@ -71,14 +71,14 @@ var Stream = (function() { get length() { return this.end - this.start; }, - getByte: function() { + getByte: function stream_getByte() { if (this.pos >= this.end) - return; + return null; return this.bytes[this.pos++]; }, // returns subarray of original buffer // should only be read - getBytes: function(length) { + getBytes: function stream_getBytes(length) { var bytes = this.bytes; var pos = this.pos; var strEnd = this.end; @@ -93,28 +93,28 @@ var Stream = (function() { this.pos = end; return bytes.subarray(pos, end); }, - lookChar: function() { + lookChar: function stream_lookChar() { if (this.pos >= this.end) - return; + return null; return String.fromCharCode(this.bytes[this.pos]); }, - getChar: function() { + getChar: function stream_getChar() { if (this.pos >= this.end) - return; + return null; return String.fromCharCode(this.bytes[this.pos++]); }, - skip: function(n) { + skip: function stream_skip(n) { if (!n) n = 1; this.pos += n; }, - reset: function() { + reset: function stream_reset() { this.pos = this.start; }, - moveStart: function() { + moveStart: function stream_moveStart() { this.start = this.pos; }, - makeSubStream: function(start, length, dict) { + makeSubStream: function stream_makeSubstream(start, length, dict) { return new Stream(this.bytes.buffer, start, length, dict); } }; @@ -146,7 +146,7 @@ var DecodeStream = (function() { } constructor.prototype = { - ensureBuffer: function(requested) { + ensureBuffer: function decodestream_ensureBuffer(requested) { var buffer = this.buffer; var current = buffer ? buffer.byteLength : 0; if (requested < current) @@ -159,16 +159,16 @@ var DecodeStream = (function() { buffer2[i] = buffer[i]; return this.buffer = buffer2; }, - getByte: function() { + getByte: function decodestream_getByte() { var pos = this.pos; while (this.bufferLength <= pos) { if (this.eof) - return; + return null; this.readBlock(); } return this.buffer[this.pos++]; }, - getBytes: function(length) { + getBytes: function decodestream_getBytes(length) { var pos = this.pos; if (length) { @@ -191,25 +191,25 @@ var DecodeStream = (function() { this.pos = end; return this.buffer.subarray(pos, end) }, - lookChar: function() { + lookChar: function decodestream_lookChar() { var pos = this.pos; while (this.bufferLength <= pos) { if (this.eof) - return; + return null; this.readBlock(); } return String.fromCharCode(this.buffer[this.pos]); }, - getChar: function() { + getChar: function decodestream_getChar() { var pos = this.pos; while (this.bufferLength <= pos) { if (this.eof) - return; + return null; this.readBlock(); } return String.fromCharCode(this.buffer[this.pos++]); }, - skip: function(n) { + skip: function decodestream_skip(n) { if (!n) n = 1; this.pos += n; @@ -635,6 +635,7 @@ var PredictorStream = (function() { var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3; DecodeStream.call(this); + return this; } constructor.prototype = Object.create(DecodeStream.prototype); @@ -905,7 +906,9 @@ var Dict = (function() { constructor.prototype = { get: function(key) { - return this.map[key]; + if (key in this.map) + return this.map[key]; + return null; }, get2: function(key1, key2) { return this.get(key1) || this.get(key2); @@ -1590,7 +1593,7 @@ var XRef = (function() { } constructor.prototype = { - readXRefTable: function(parser) { + readXRefTable: function readXRefTable(parser) { var obj; while (true) { if (IsCmd(obj = parser.getObj(), "trailer")) @@ -1661,7 +1664,7 @@ var XRef = (function() { return dict; }, - readXRefStream: function(stream) { + readXRefStream: function readXRefStream(stream) { var streamParameters = stream.parameters; var length = streamParameters.get("Length"); var byteWidths = streamParameters.get("W"); @@ -1713,7 +1716,7 @@ var XRef = (function() { this.readXRef(prev); return streamParameters; }, - readXRef: function(startXRef) { + readXRef: function readXref(startXRef) { var stream = this.stream; stream.pos = startXRef; var parser = new Parser(new Lexer(stream), true); @@ -1731,6 +1734,7 @@ var XRef = (function() { return this.readXRefStream(obj); } error("Invalid XRef"); + return null; }, getEntry: function(i) { var e = this.entries[i]; @@ -2396,7 +2400,7 @@ var CanvasGraphics = (function() { if (!fd) // XXX deprecated "special treatment" for standard // fonts? What do we need to do here? - return; + return null; var descriptor = xref.fetch(fd); var fontName = descriptor.get("FontName"); @@ -3037,7 +3041,7 @@ var CanvasGraphics = (function() { this.restore(); TODO("Inverse pattern is painted"); - var pattern = this.ctx.createPattern(tmpCanvas, "repeat"); + pattern = this.ctx.createPattern(tmpCanvas, "repeat"); this.ctx.fillStyle = pattern; }, setStrokeGray: function(gray) { From 994b075470ddeb7feb02b199c29c62ed9382e2da Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Fri, 24 Jun 2011 07:51:31 -0500 Subject: [PATCH 11/28] Basic DecryptStream functionality; md5 and arcfour algorithms --- pdf.js | 90 +++++++++++------- security.js | 261 ++++++++++++++++++++++++++++++++++++++++++++++++++++ viewer.html | 1 + 3 files changed, 319 insertions(+), 33 deletions(-) create mode 100644 security.js diff --git a/pdf.js b/pdf.js index 52a65f1e3..320fc6913 100644 --- a/pdf.js +++ b/pdf.js @@ -56,6 +56,14 @@ function bytesToString(bytes) { return str; } +function stringToBytes(str) { + var length = str.length; + var bytes = new Uint8Array(length); + for (var n = 0; n < length; ++n) + bytes[n] = str.charCodeAt(n) & 0xFF; + return bytes; +} + var Stream = (function() { function constructor(arrayBuffer, start, length, dict) { this.bytes = Uint8Array(arrayBuffer); @@ -741,11 +749,34 @@ var PredictorStream = (function() { })(); var DecryptStream = (function() { - function constructor(str, fileKey, encAlgorithm, keyLength) { - TODO("decrypt stream is not implemented"); + function constructor(str, decrypt) { + this.str = str; + this.dict = str.dict; + this.decrypt = decrypt; + + DecodeStream.call(this); } - constructor.prototype = Stream.prototype; + const chunkSize = 512; + + constructor.prototype = Object.create(DecodeStream.prototype); + constructor.prototype.readBlock = function() { + var chunk = this.str.getBytes(chunkSize); + if (!chunk || chunk.length == 0) { + this.eof = true; + return; + } + var decrypt = this.decrypt; + chunk = decrypt(chunk); + + var bufferLength = this.bufferLength; + var i, n = chunk.length; + var buffer = this.ensureBuffer(bufferLength + n); + for (i = 0; i < n; i++) + buffer[bufferLength++] = chunk[i]; + this.bufferLength = n; + this.eof = n < chunkSize; + }; return constructor; })(); @@ -919,10 +950,10 @@ var Lexer = (function() { function ToHexDigit(ch) { if (ch >= "0" && ch <= "9") - return ch - "0"; - ch = ch.toLowerCase(); - if (ch >= "a" && ch <= "f") - return ch - "a"; + return ch.charCodeAt(0) - 48; + ch = ch.toUpperCase(); + if (ch >= "A" && ch <= "F") + return ch.charCodeAt(0) - 55; return -1; } @@ -1216,7 +1247,7 @@ var Parser = (function() { // don't buffer inline image data this.buf2 = (this.inlineImg > 0) ? null : this.lexer.getObj(); }, - getObj: function() { + getObj: function(cipherTransform) { // refill buffer after inline image data if (this.inlineImg == 2) this.refill(); @@ -1242,7 +1273,7 @@ var Parser = (function() { this.shift(); if (IsEOF(this.buf1)) break; - dict.set(key, this.getObj()); + dict.set(key, this.getObj(cipherTransform)); } } if (IsEOF(this.buf1)) @@ -1251,7 +1282,7 @@ var Parser = (function() { // stream objects are not allowed inside content streams or // object streams if (this.allowStreams && IsCmd(this.buf2, "stream")) { - return this.makeStream(dict); + return this.makeStream(dict, cipherTransform); } else { this.shift(); } @@ -1270,17 +1301,8 @@ var Parser = (function() { } else if (IsString(this.buf1)) { // string var str = this.buf1; this.shift(); - if (this.fileKey) { - var decrypt = new DecryptStream(new StringStream(str), - this.fileKey, - this.encAlgorithm, - this.keyLength); - var str = ""; - var pos = decrypt.pos; - var length = decrypt.length; - while (pos++ > length) - str += decrypt.getChar(); - } + if (cipherTransform) + str = cipherTransform.decryptString(str); return str; } @@ -1289,7 +1311,7 @@ var Parser = (function() { this.shift(); return obj; }, - makeStream: function(dict) { + makeStream: function(dict, cipherTransform) { var lexer = this.lexer; var stream = lexer.stream; @@ -1316,12 +1338,8 @@ var Parser = (function() { this.shift(); stream = stream.makeSubStream(pos, length, dict); - if (this.fileKey) { - stream = new DecryptStream(stream, - this.fileKey, - this.encAlgorithm, - this.keyLength); - } + if (cipherTransform) + stream = cipherTransform.createString(stream); stream = this.filter(stream, dict, length); stream.parameters = dict; return stream; @@ -1450,12 +1468,18 @@ var XRef = (function() { this.xrefstms = {}; var trailerDict = this.readXRef(startXRef); + // prepare the XRef cache + this.cache = []; + + var encrypt = trailerDict.get("Encrypt"); + if (encrypt) { + var fileId = trailerDict.get("ID"); + this.encrypt = new CipherTransformFactory(this.fetch(encrypt), fileId[0] /*, password */); + } + // get the root dictionary (catalog) object if (!IsRef(this.root = trailerDict.get("Root"))) error("Invalid root reference"); - - // prepare the XRef cache - this.cache = []; } constructor.prototype = { @@ -1643,7 +1667,7 @@ var XRef = (function() { } error("bad XRef entry"); } - e = parser.getObj(); + e = parser.getObj(this.encrypt); // Don't cache streams since they are mutable. if (!IsStream(e)) this.cache[num] = e; @@ -2462,7 +2486,7 @@ var CanvasGraphics = (function() { } } } else if (cmd == "Tf") { // eagerly collect all fonts - var fontRes = resources.get("Font"); + var fontRes; // = resources.get("Font"); if (fontRes) { fontRes = xref.fetchIfRef(fontRes); var font = xref.fetchIfRef(fontRes.get(args[0].name)); diff --git a/security.js b/security.js new file mode 100644 index 000000000..d6aa8d3b2 --- /dev/null +++ b/security.js @@ -0,0 +1,261 @@ +/* -*- Mode: Java; tab-width: s; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=s tabstop=2 autoindent cindent expandtab: */ + +"use strict"; + +var ARCFourCipher = (function() { + function constructor(key) { + var key = this.key; + this.a = 0; + this.b = 0; + var s = new Uint8Array(256); + var i, j = 0, tmp, keyLength = key.length; + for (i = 0; i < 256; ++i) + s[i] = i; + for (i = 0; i < 256; ++i) { + tmp = s[i]; + j = (j + tmp + key[i % keyLength]) & 0xFF; + s[i] = s[j]; + s[j] = tmp; + } + this.s = s; + } + + constructor.prototype = { + encryptBlock: function(data) { + var i, n = data.length, tmp, tmp2; + var a = this.a, b = this.b, s = this.s; + var output = new Uint8Array(n); + for (i = 0; i < n; ++i) { + var tmp; + a = (a + 1) & 0xFF; + tmp = s[a]; + b = (b + tmp) & 0xFF; + tmp2 = s[b] + s[a] = tmp2; + s[b] = tmp; + output[i] = data[i] ^ s[(tmp + tmp2) & 0xFF]; + } + this.a = a; + this.b = b; + return output; + } + }; + + return constructor; +})(); + +var md5 = (function() { + const r = new Uint8Array([ + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]); + const k = new Int32Array([ + -680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426, + -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162, + 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632, + 643717713, -373897302, -701558691, 38016083, -660478335, -405537848, 568446438, + -1019803690, -187363961, 1163531501, -1444681467, -51403784, 1735328473, + -1926607734, -378558, -2022574463, 1839030562, -35309556, -1530992060, + 1272893353, -155497632, -1094730640, 681279174, -358537222, -722521979, + 76029189, -640364487, -421815835, 530742520, -995338651, -198630844, 1126891415, + -1416354905, -57434055, 1700485571, -1894986606, -1051523, -2054922799, + 1873313359, -30611744, -1560198380, 1309151649, -145523070, -1120210379, + 718787259, -343485551]); + + function hash(data, offset, length) { + var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878; + // pre-processing + var paddedLength = (length + 72) & ~63; // data + 9 extra bytes + var padded = new Uint8Array(paddedLength); + var i, j, n; + for (i = 0; i < length; ++i) + padded[i] = data[offset++]; + padded[i++] = 0x80; + n = paddedLength - 8; + for (; i < n; ++i) + padded[i] = 0; + padded[i++] = (length << 3) & 0xFF; + padded[i++] = (length >> 5) & 0xFF; + padded[i++] = (length >> 13) & 0xFF; + padded[i++] = (length >> 21) & 0xFF; + padded[i++] = (length >>> 29) & 0xFF; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + // chunking + // TODO ArrayBuffer ? + var w = new Int32Array(16); + for (i = 0; i < paddedLength;) { + for (j = 0; j < 16; ++j, i += 4) + w[j] = padded[i] | (padded[i + 1] << 8) | (padded[i + 2] << 16) | (padded[i + 3] << 24); + var a = h0, b = h1, c = h2, d = h3, f, g; + for (j = 0; j < 64; ++j) { + if (j < 16) { + f = (b & c) | ((~b) & d); + g = j; + } else if (j < 32) { + f = (d & b) | ((~d) & c); + g = (5 * j + 1) & 15; + } else if (j < 48) { + f = b ^ c ^ d; + g = (3 * j + 5) & 15; + } else { + f = c ^ (b | (~d)); + g = (7 * j) & 15; + } + var tmp = d, rotateArg = (a + f + k[j] + w[g]) | 0, rotate = r[j]; + d = c; + c = b; + b = (b + ((rotateArg << rotate) | (rotateArg >>> (32 - rotate)))) | 0; + a = tmp; + } + h0 = (h0 + a) | 0; + h1 = (h1 + b) | 0; + h2 = (h2 + c) | 0; + h3 = (h3 + d) | 0; + } + return new Uint8Array([ + h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF, + h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF, + h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF, + h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF + ]); + } + return hash; +})(); + +var CipherTransform = (function() { + function constructor(stringCipherConstructor, streamCipherConstructor) { + this.stringCipherConstructor = stringCipherConstructor; + this.streamCipherConstructor = streamCipherConstructor; + } + constructor.prototype = { + createStream: function (stream) { + var cipher = new streamCipherConstructor(); + return new DecryptStream(stream, function(data) { + return cipher.encryptBlock(data); + }); + }, + decryptString: function(s) { + var cipher = new stringCipherConstructor(); + var data = string2bytes(s); + data = cipher.encryptBlock(data); + return bytes2string(data); + } + }; + return constructor; +})(); + +var CipherTransformFactory = (function() { + function prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength) { + const defaultPasswordBytes = new Uint8Array([ + 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, + 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]); + var hashData = new Uint8Array(88), i = 0, j, n; + if (password) { + n = Math.min(32, password.length); + for (; i < n; ++i) + hashData[i] = password[i]; + } + j = 0; + while (i < 32) { + hashData[i++] = defaultPasswordBytes[j++]; + } + // as now the padded password in the hashData[0..i] + for (j = 0, n = ownerPassword.length; j < n; ++j) + hashData[i++] = ownerPassword[j]; + hashData[i++] = flags & 0xFF; + hashData[i++] = (flags >> 8) & 0xFF; + hashData[i++] = (flags >> 16) & 0xFF; + hashData[i++] = (flags >>> 24) & 0xFF; + for (j = 0, n = fileId.length; j < n; ++j) + hashData[i++] = fileId[j]; + // TODO rev 4, if metadata is not encrypted pass 0xFFFFFF also + var hash = md5(hashData, 0, i); + var keyLengthInBytes = keyLength >> 3; + if (revision >= 3) { + for (j = 0; j < 50; ++j) { + hash = md5(hash, 0, keyLengthInBytes); + } + } + var encryptionKey = hash.subarray(0, keyLengthInBytes); + var cipher, checkData; + + if (revision >= 3) { + // padded password in hashData, we can use this array for user password check + i = 32; + for(j = 0, n = fileId.length; j < n; ++j) + hashData[i++] = fileId[j]; + cipher = new ARCFourCipher(encryptionKey); + var checkData = cipher.encryptBlock(md5(hashData, 0, i)); + n = encryptionKey.length; + var derrivedKey = new Uint8Array(n), k; + for (j = 1; j <= 19; ++j) { + for (k = 0; k < n; ++k) + derrivedKey[k] = encryptionKey[k] ^ j; + cipher = new ARCFourCipher(derrivedKey); + checkData = cipher.encryptBlock(checkData); + } + } else { + cipher = new ARCFourCipher(encryptionKey); + checkData = cipher.encryptBlock(hashData.subarray(0, 32)); + } + for (j = 0, n = checkData.length; j < n; ++j) { + if (userPassword[j] != checkData[j]) + error("incorrect password"); + } + return encryptionKey; + } + + function constructor(dict, fileId, password) { + var filter = dict.get("Filter"); + if (!IsName(filter) || filter.name != "Standard") + error("unknown encryption method"); + this.dict = dict; + var algorithm = dict.get("V"); + if (!IsInt(algorithm) || + (algorithm != 1 && algorithm != 2)) + error("unsupported encryption algorithm"); + // TODO support algorithm 4 + var keyLength = dict.get("Length") || 40; + if (!IsInt(keyLength) || + keyLength < 40 || (keyLength % 8) != 0) + error("invalid key length"); + // prepare keys + var ownerPassword = stringToBytes(dict.get("O")); + var userPassword = stringToBytes(dict.get("U")); + var flags = dict.get("P"); + var revision = dict.get("R"); + var fileIdBytes = stringToBytes(fileId); + var passwordBytes; + if (password) + passwordBytes = stringToBytes(password); + + this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes, + ownerPassword, userPassword, flags, revision, keyLength); + } + + constructor.prototype = { + createCipherTransform: function(num, gen) { + var encryptionKey = this.encryptionKey; + var key = new Uint8Array(encryptionKey.length + 5), i, j, n; + for (j = 0, n = encryptionKey.length; j < n; ++j) + key[j] = encryptionKey[j]; + key[i++] = num & 0xFF; + key[i++] = (num >> 8) & 0xFF; + key[i++] = (num >> 16) & 0xFF; + key[i++] = gen & 0xFF; + key[i++] = (gen >> 8) & 0xFF; + var hash = md5(key, 0, i); + key = hash.subarray(0, Math.min(key.length, 16)); + var cipherConstructor = function() { + return new ARCFourCipher(key); + }; + return new CipherTransform(cipherConstructor, cipherConstructor); + } + }; + + return constructor; +})(); diff --git a/viewer.html b/viewer.html index 6e733319e..4b9dd3cbd 100644 --- a/viewer.html +++ b/viewer.html @@ -6,6 +6,7 @@ + From 6446b6d03a6138e5693516d7df1e295e51a25971 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 08:43:26 -0700 Subject: [PATCH 12/28] Run browsers in parallel. No limit on the number of concurrent browsers right now. --- test/test.py | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/test/test.py b/test/test.py index 53f65f78b..7e678bd90 100644 --- a/test/test.py +++ b/test/test.py @@ -1,4 +1,4 @@ -import json, platform, os, shutil, sys, subprocess, tempfile, threading, urllib, urllib2 +import json, platform, os, shutil, sys, subprocess, tempfile, threading, time, urllib, urllib2 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import SocketServer from optparse import OptionParser @@ -138,6 +138,7 @@ class BrowserCommand(): def __init__(self, browserRecord): self.name = browserRecord["name"] self.path = browserRecord["path"] + self.tempDir = None if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")): self._fixupMacPath() @@ -151,19 +152,19 @@ class BrowserCommand(): def setup(self): self.tempDir = tempfile.mkdtemp() self.profileDir = os.path.join(self.tempDir, "profile") - print self.profileDir shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"), self.profileDir) def teardown(self): - shutil.rmtree(self.tempDir) + if self.tempDir is not None and os.path.exists(self.tempDir): + shutil.rmtree(self.tempDir) def start(self, url): cmds = [self.path] if platform.system() == "Darwin": cmds.append("-foreground") cmds.extend(["-no-remote", "-profile", self.profileDir, url]) - subprocess.call(cmds) + subprocess.Popen(cmds) def makeBrowserCommands(browserManifestFile): with open(browserManifestFile) as bmf: @@ -223,15 +224,22 @@ def setUp(options): State.remaining = len(testBrowsers) * len(manifestList) - for b in testBrowsers: - try: - b.setup() - print 'Launching', b.name - qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) - b.start('http://localhost:8080/test/test_slave.html?'+ qs) - finally: - b.teardown() + return testBrowsers +def startBrowsers(browsers, options): + for b in browsers: + b.setup() + print 'Launching', b.name + qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) + b.start('http://localhost:8080/test/test_slave.html?'+ qs) + +def teardownBrowsers(browsers): + for b in browsers: + try: + b.teardown() + except: + print "Error cleaning up after browser at ", b.path + def check(task, results, browser): failed = False for r in xrange(len(results)): @@ -385,8 +393,14 @@ def main(): httpd_thread.setDaemon(True) httpd_thread.start() - setUp(options) - processResults() + browsers = setUp(options) + try: + startBrowsers(browsers, options) + while not State.done: + time.sleep(1) + processResults() + finally: + teardownBrowsers(browsers) if __name__ == '__main__': main() From a67a4c0677abfd191de0330e18507f834315e7b3 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 09:00:13 -0700 Subject: [PATCH 13/28] Add a line to prevent TestPilot from installing. --- test/resources/firefox/user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js index d4b9d4130..7ca293923 100644 --- a/test/resources/firefox/user.js +++ b/test/resources/firefox/user.js @@ -32,3 +32,4 @@ user_pref("app.update.enabled", false); user_pref("browser.panorama.experienced_first_run", true); // Assume experienced user_pref("dom.w3c_touch_events.enabled", true); user_pref("extensions.checkCompatibility", false); +user_pref("extensions.installDistroAddons", false); // prevent testpilot etc From 2b6b6d5ab23de0a99a54d824b0ab4dc4c7fe5af9 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 09:45:41 -0700 Subject: [PATCH 14/28] Try harder to clean up after the browsers. --- test/test.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/test/test.py b/test/test.py index 7e678bd90..bc30d5f8a 100644 --- a/test/test.py +++ b/test/test.py @@ -139,6 +139,7 @@ class BrowserCommand(): self.name = browserRecord["name"] self.path = browserRecord["path"] self.tempDir = None + self.process = None if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")): self._fixupMacPath() @@ -156,6 +157,17 @@ class BrowserCommand(): self.profileDir) def teardown(self): + # If the browser is still running, wait up to ten seconds for it to quit + if self.process and self.process.poll() is None: + checks = 0 + while self.process.poll() is None and checks < 20: + checks += 1 + time.sleep(.5) + # If it's still not dead, try to kill it + if self.process.poll() is None: + print "Process %s is still running. Killing." % self.name + self.process.kill() + if self.tempDir is not None and os.path.exists(self.tempDir): shutil.rmtree(self.tempDir) @@ -164,7 +176,7 @@ class BrowserCommand(): if platform.system() == "Darwin": cmds.append("-foreground") cmds.extend(["-no-remote", "-profile", self.profileDir, url]) - subprocess.Popen(cmds) + self.process = subprocess.Popen(cmds) def makeBrowserCommands(browserManifestFile): with open(browserManifestFile) as bmf: @@ -239,7 +251,9 @@ def teardownBrowsers(browsers): b.teardown() except: print "Error cleaning up after browser at ", b.path - + print "Temp dir was ", b.tempDir + print "Error:", sys.exc_info()[0] + def check(task, results, browser): failed = False for r in xrange(len(results)): From 3d85caa212cf8b86379d94932abca2618ee5b007 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 21:25:08 +0200 Subject: [PATCH 15/28] Do not add the font-loader canvas to the page dom to save some load time --- fonts.js | 58 +++++++++----------------------------------------------- 1 file changed, 9 insertions(+), 49 deletions(-) diff --git a/fonts.js b/fonts.js index 7e8aecd6d..c817b12e5 100644 --- a/fonts.js +++ b/fonts.js @@ -104,6 +104,7 @@ var FontsLoader = { worker ? obj.bindWorker(str) : obj.bindDOM(str); } + return ready; } }; @@ -766,96 +767,55 @@ var Font = (function () { return fontData; }, - bindWorker: function font_bind_worker(dataStr) { + bindWorker: function font_bindWorker(data) { postMessage({ action: "font", data: { - raw: dataStr, + raw: data, fontName: this.name, mimetype: this.mimetype } }); }, - bindDOM: function font_bind_dom(dataStr) { + bindDOM: function font_bindDom(data) { var fontName = this.name; /** Hack begin */ // Actually there is not event when a font has finished downloading so // the following code are a dirty hack to 'guess' when a font is ready + // This code could go away when bug 471915 has landed 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"; - canvas.setAttribute("style", style); - canvas.setAttribute("width", 340); - canvas.setAttribute("heigth", 100); - document.body.appendChild(canvas); - - // Get the font size canvas think it will be for 'spaces' var ctx = canvas.getContext("2d"); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + ctx.font = "bold italic 20px " + fontName + ", Symbol"; var testString = " "; - // When debugging use the characters provided by the charsets to visually - // 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.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(); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + ctx.font = "bold italic 20px " + fontName + ", Symbol"; // For some reasons the font has not loaded, so mark it loaded for the // page to proceed but cry if ((Date.now() - this.start) >= kMaxWaitForFontFace) { window.clearInterval(interval); Fonts[fontName].loading = false; - warn("Is " + fontName + " for charset: " + charset + " loaded?"); + warn("Is " + fontName + " loaded?"); this.start = 0; } else if (textWidth != ctx.measureText(testString).width) { window.clearInterval(interval); Fonts[fontName].loading = false; this.start = 0; } - - if (debug) - ctx.fillText(testString, 20, 50); }, 30, this); /** Hack end */ - - // Convert the data string and add it to the page. - var base64 = window.btoa(dataStr); // Add the @font-face rule to the document - var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; + 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); From 83fabc49c2dac7f2dac16bdd26a759a8575f0183 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 21:46:48 +0200 Subject: [PATCH 16/28] Update the multi_page_viewer.js code to work with the new FontLoader API --- multi_page_viewer.js | 73 ++++++++++++-------------------------------- 1 file changed, 19 insertions(+), 54 deletions(-) diff --git a/multi_page_viewer.js b/multi_page_viewer.js index 3a02ea332..1631433ca 100644 --- a/multi_page_viewer.js +++ b/multi_page_viewer.js @@ -3,6 +3,8 @@ "use strict"; +var pageTimeout; + var PDFViewer = { queryParams: {}, @@ -75,81 +77,45 @@ var PDFViewer = { }, drawPage: function(num) { - if (!PDFViewer.pdf) { + if (!PDFViewer.pdf) return; - } - + var div = document.getElementById('pageContainer' + num); var canvas = document.createElement('canvas'); - + if (div && !div.hasChildNodes()) { - div.appendChild(canvas); - var page = PDFViewer.pdf.getPage(num); - + 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(); - + div.appendChild(canvas); + 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 + var fonts = []; 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 loadFont = function() { + if (!FontsLoader.bind(fonts)) { + pageTimeout = window.setTimeout(loadFont, 10); + return; } - - new Font(font.name, font.file, font.properties); - - areFontsReady = false; + page.display(gfx); } - - 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); + loadFont(); } }, @@ -258,7 +224,6 @@ var PDFViewer = { }; window.onload = function() { - // Parse the URL query parameters into a cached object. PDFViewer.queryParams = function() { var qs = window.location.search.substring(1); From f317eae084fa8e77e808105454432aa26966d597 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Fri, 24 Jun 2011 17:12:06 -0400 Subject: [PATCH 17/28] nits --- fonts.js | 6 +++--- multi_page_viewer.js | 2 +- viewer.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fonts.js b/fonts.js index c817b12e5..ac06b76ae 100644 --- a/fonts.js +++ b/fonts.js @@ -80,7 +80,7 @@ var Fonts = { } }; -var FontsLoader = { +var FontLoader = { bind: function(fonts) { var worker = (typeof window == "undefined"); var ready = true; @@ -90,10 +90,10 @@ var FontsLoader = { if (Fonts[font.name]) { ready = ready && !Fonts[font.name].loading; continue; - } else { - ready = false; } + ready = false; + var obj = new Font(font.name, font.file, font.properties); var str = ""; diff --git a/multi_page_viewer.js b/multi_page_viewer.js index 1631433ca..f262734d3 100644 --- a/multi_page_viewer.js +++ b/multi_page_viewer.js @@ -109,7 +109,7 @@ var PDFViewer = { page.compile(gfx, fonts); var loadFont = function() { - if (!FontsLoader.bind(fonts)) { + if (!FontLoader.bind(fonts)) { pageTimeout = window.setTimeout(loadFont, 10); return; } diff --git a/viewer.js b/viewer.js index 2bcff50a6..c7be739bc 100644 --- a/viewer.js +++ b/viewer.js @@ -76,7 +76,7 @@ function displayPage(num) { var t2 = Date.now(); function loadFont() { - if (!FontsLoader.bind(fonts)) { + if (!FontLoader.bind(fonts)) { pageTimeout = window.setTimeout(loadFont, 10); return; } From 9748d2ad4978c9fa69f24aea4c994415d51fb2c5 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 16:14:33 -0700 Subject: [PATCH 18/28] Concurrent browsers sending snapshots asynchronously. --- test/test.py | 19 +++++++++++++---- test/test_slave.html | 50 +++++++++++++++++++++++++++++--------------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/test/test.py b/test/test.py index bc30d5f8a..a89aa0a7b 100644 --- a/test/test.py +++ b/test/test.py @@ -69,9 +69,10 @@ class State: eqLog = None class Result: - def __init__(self, snapshot, failure): + def __init__(self, snapshot, failure, page): self.snapshot = snapshot self.failure = failure + self.page = page class TestServer(SocketServer.TCPServer): allow_reuse_address = True @@ -122,10 +123,20 @@ class PDFTestHandler(BaseHTTPRequestHandler): result = json.loads(self.rfile.read(numBytes)) browser, id, failure, round, page, snapshot = result['browser'], result['id'], result['failure'], result['round'], result['page'], result['snapshot'] taskResults = State.taskResults[browser][id] - taskResults[round].append(Result(snapshot, failure)) - assert len(taskResults[round]) == page + taskResults[round].append(Result(snapshot, failure, page)) - if result['taskDone']: + def isTaskDone(): + numPages = result["numPages"] + rounds = State.manifest[id]["rounds"] + for round in range(0,rounds): + if len(taskResults[round]) < numPages: + return False + return True + + if isTaskDone(): + # sort the results since they sometimes come in out of order + for results in taskResults: + results.sort(key=lambda result: result.page) check(State.manifest[id], taskResults, browser) # Please oh please GC this ... del State.taskResults[browser][id] diff --git a/test/test_slave.html b/test/test_slave.html index 1053025e1..03982bbb5 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -139,31 +139,41 @@ function snapshotCurrentPage(gfx) { } } - currentTask.taskDone = (currentTask.pageNum == pdfDoc.numPages - && (1 + currentTask.round) == currentTask.rounds); sendTaskResult(canvas.toDataURL("image/png")); log("done"+ (failure ? " (failed!)" : "") +"\n"); - ++currentTask.pageNum, nextPage(); -} - -function done() { - log("Done!\n"); + // Set up the next request + backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0; setTimeout(function() { - document.body.innerHTML = "Tests are finished.

CLOSE ME!

"; - if (window.SpecialPowers) - SpecialPowers.quitApplication(); - else - window.close(); + ++currentTask.pageNum, nextPage(); }, - 100 + backoff ); } +function quitApp() { + log("Done!"); + document.body.innerHTML = "Tests are finished.

CLOSE ME!

"; + if (window.SpecialPowers) + SpecialPowers.quitApplication(); + else + window.close(); +} + +function done() { + if (inFlightRequests > 0) { + document.getElementById("inFlightCount").innerHTML = inFlightRequests; + setTimeout(done, 100); + } else { + setTimeout(quitApp, 100); + } +} + +var inFlightRequests = 0; function sendTaskResult(snapshot) { var result = { browser: browser, id: currentTask.id, - taskDone: currentTask.taskDone, + numPages: pdfDoc.numPages, failure: failure, file: currentTask.file, round: currentTask.round, @@ -172,9 +182,14 @@ function sendTaskResult(snapshot) { var r = new XMLHttpRequest(); // (The POST URI is ignored atm.) - r.open("POST", "/submit_task_results", false); + r.open("POST", "/submit_task_results", true); r.setRequestHeader("Content-Type", "application/json"); - // XXX async + r.onreadystatechange = function(e) { + if (r.readyState == 4) { + inFlightRequests--; + } + } + document.getElementById("inFlightCount").innerHTML = inFlightRequests++; r.send(JSON.stringify(result)); } @@ -194,7 +209,8 @@ function log(str) { -

+  

+  

Inflight requests:

From b90869e97f644a7f9fb59ff177eddd87847ca4c2 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 24 Jun 2011 17:11:39 -0700 Subject: [PATCH 19/28] update test_slave for the new font-loading API --- test/test_slave.html | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/test/test_slave.html b/test/test_slave.html index 03982bbb5..08a3804f3 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -88,43 +88,29 @@ function nextPage() { clear(ctx); var fonts = []; - var fontsReady = true; var gfx = new CanvasGraphics(ctx); try { currentPage = pdfDoc.getPage(currentTask.pageNum); currentPage.compile(gfx, fonts); - - // Inspect fonts and translate the missing ones - var count = fonts.length; - for (var i = 0; i < count; ++i) { - var font = fonts[i]; - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; - } - new Font(font.name, font.file, font.properties); - fontsReady = false; - } } catch(e) { failure = 'compile: '+ e.toString(); } - var checkFontsLoadedIntervalTimer = null; + var fontLoaderTimer = null; function checkFontsLoaded() { - for (var i = 0; i < count; i++) { - if (Fonts[font.name].loading) { - return; - } + if (!FontLoader.bind(fonts)) { + fontLoaderTimer = window.setTimeout(checkFontsLoaded, 10); + return; } - window.clearInterval(checkFontsLoadedIntervalTimer); - snapshotCurrentPage(gfx); } - if (failure || fontsReady) { + if (failure) { + // Skip font loading if there was a failure, since the fonts might + // be in an inconsistent state. snapshotCurrentPage(gfx); } else { - checkFontsLoadedIntervalTimer = setInterval(checkFontsLoaded, 10); + checkFontsLoaded(); } } From 0b422b42773a4abe46c23a14525dd94e8480e77d Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 17:13:31 -0700 Subject: [PATCH 20/28] Turn off safe-browsing code in test harness. --- test/resources/firefox/user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js index 7ca293923..b01e2eb76 100644 --- a/test/resources/firefox/user.js +++ b/test/resources/firefox/user.js @@ -33,3 +33,4 @@ user_pref("browser.panorama.experienced_first_run", true); // Assume experienced user_pref("dom.w3c_touch_events.enabled", true); user_pref("extensions.checkCompatibility", false); user_pref("extensions.installDistroAddons", false); // prevent testpilot etc +user_pref("browser.safebrowsing.enable", false); // prevent traffic to google servers From 4278660216d1b4d5fbb6869d785b6c50c2c750f8 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 17:58:07 -0700 Subject: [PATCH 21/28] Clean up 404 noise from favicon.ico, send Content-Length with GET responses. --- test/test.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/test.py b/test/test.py index a89aa0a7b..5aece2c24 100644 --- a/test/test.py +++ b/test/test.py @@ -51,6 +51,7 @@ MIMEs = { '.json': 'application/json', '.pdf': 'application/pdf', '.xhtml': 'application/xhtml+xml', + '.ico': 'image/x-icon' } class State: @@ -84,6 +85,14 @@ class PDFTestHandler(BaseHTTPRequestHandler): if VERBOSE: BaseHTTPRequestHandler.log_request(code, size) + def sendFile(self, path, ext): + self.send_response(200) + self.send_header("Content-Type", MIMEs[ext]) + self.send_header("Content-Length", os.path.getsize(path)) + self.end_headers() + with open(path) as f: + self.wfile.write(f.read()) + def do_GET(self): url = urlparse(self.path) # Ignore query string @@ -92,9 +101,14 @@ class PDFTestHandler(BaseHTTPRequestHandler): prefix = os.path.commonprefix(( path, DOC_ROOT )) _, ext = os.path.splitext(path) + if url.path == "/favicon.ico": + self.sendFile(os.path.join(DOC_ROOT, "test", "resources", "favicon.ico"), ext) + return + if not (prefix == DOC_ROOT and os.path.isfile(path) and ext in MIMEs): + print path self.send_error(404) return @@ -103,14 +117,8 @@ class PDFTestHandler(BaseHTTPRequestHandler): self.send_error(501) return - self.send_response(200) - self.send_header("Content-Type", MIMEs[ext]) - self.end_headers() - - # Sigh, os.sendfile() plz - f = open(path) - self.wfile.write(f.read()) - f.close() + self.sendFile(path, ext) + def do_POST(self): From 6ca1c4cf8346d789d952388602d35c3bdcadbcc7 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 24 Jun 2011 19:23:29 -0700 Subject: [PATCH 22/28] add Intel ISA doc as load test, and make harness resilient to bad PDF loads --- test/pdfs/intelisa.pdf.link | 1 + test/test_manifest.json | 6 ++++++ test/test_slave.html | 9 ++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 test/pdfs/intelisa.pdf.link diff --git a/test/pdfs/intelisa.pdf.link b/test/pdfs/intelisa.pdf.link new file mode 100644 index 000000000..371cdf947 --- /dev/null +++ b/test/pdfs/intelisa.pdf.link @@ -0,0 +1 @@ +http://www.intel.com/Assets/PDF/manual/253665.pdf \ No newline at end of file diff --git a/test/test_manifest.json b/test/test_manifest.json index e4a7ada81..9b9d5e333 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -14,6 +14,12 @@ "rounds": 1, "type": "load" }, + { "id": "intelisa-load", + "file": "pdfs/intelisa.pdf", + "link": true, + "rounds": 1, + "type": "load" + }, { "id": "pdfspec-load", "file": "pdfs/pdf.pdf", "link": true, diff --git a/test/test_slave.html b/test/test_slave.html index 08a3804f3..d685eeaf2 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -63,7 +63,14 @@ function nextTask() { if (r.readyState == 4) { var data = r.mozResponseArrayBuffer || r.mozResponse || r.responseArrayBuffer || r.response; - pdfDoc = new PDFDoc(new Stream(data)); + + try { + pdfDoc = new PDFDoc(new Stream(data)); + } catch(e) { + pdfDoc.numPages = 1; + failure = 'load PDF doc: '+ e.toString(); + } + currentTask.pageNum = 1, nextPage(); } }; From d56e6614a383b45a8643ad61b903c44658341357 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 19:25:03 -0700 Subject: [PATCH 23/28] Add favicon.ico file --- test/resources/favicon.ico | Bin 0 -> 1406 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/resources/favicon.ico diff --git a/test/resources/favicon.ico b/test/resources/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..d44438903b751f4732f5365783eb0229b0501f9a GIT binary patch literal 1406 zcmZvcze^lJ6vy9y;f`fV)+?@Ze|W?Y>#3{^YAe`S+YM=~Z34NW`?g{0%{Gm#-zuW37pASug&9hEW%I5<8GkmQYMp$Ony#e@#BjuEiFmE-c}V zrd^0bcjyk?p*!@r%%gjB&%~p9bOs6N0X?7xgn+KhmAQuGZ0RljmPD3CR#cKborS@| zU}3N@BqSIt3>F3pgM}es!eC*rFjyEY3<(_u3xkEh!eC*riUx;;!$uC(Q~?ymczRl( zp~pvt#bNF+awK)Iq%v^Rap7Qbu%xoU;oxv^I5->}sZ?+{7#s`^hFCfWhl9bv5b#p^ zR6=~ZlX)s{tPWIod!kaQ@%AuzOg#o3CXcu0rYDKV+vAsduroh z@GyAl5Vwu0q^Cz=q>tN34FY}vwg5-KFKun)paubs5IK0TwB0NUumo5F5COyV3cwOr z7zhdo0TzBV@~|fXmJsWaSmN6dcL5Csg+XCZ7!(GDA-zs81Q-+sg(1K|8XEC1C=3dN z!a!;oIt&Vf!k_>s{()r5LM6zN!!dVoXv!U9oUF=cIXF0wqoX4^K0cO{lM^{VKbNz! zGr7FHl<9OTS65eZb8{oNx3_YCe=mzaDxXeI`5%v|Zu}3#fqZEe^Tw%4_Mf9?On&&F zi}beZwkZd!>yBC3w&vx-=SH%5uQV+Iv+k~DdEGL9UJhoPqdUoZxoMl$(3xtsdZBU89L3N8 literal 0 HcmV?d00001 From 4189bb610e90e35c2f21aa53acf27a672283101c Mon Sep 17 00:00:00 2001 From: sbarman Date: Fri, 24 Jun 2011 19:40:51 -0700 Subject: [PATCH 24/28] Fixed bug, used pixBytes instead of rowBytes --- pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index 48fc8f1ae..a34cd7c42 100644 --- a/pdf.js +++ b/pdf.js @@ -708,7 +708,7 @@ var PredictorStream = (function() { var rawBytes = this.stream.getBytes(rowBytes); var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + pixBytes); + var buffer = this.ensureBuffer(bufferLength + rowBytes); var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); From 4326a1d5bf146c75ec16512613cedde09b7b0cf3 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Fri, 24 Jun 2011 22:08:33 -0500 Subject: [PATCH 25/28] Fix arcfour initialization and encoding; png prediction fix --- pdf.js | 14 +++++++++----- security.js | 11 +++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/pdf.js b/pdf.js index 3ebedd9e1..a4de3d8b2 100644 --- a/pdf.js +++ b/pdf.js @@ -716,7 +716,7 @@ var PredictorStream = (function() { var rawBytes = this.stream.getBytes(rowBytes); var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + pixBytes); + var buffer = this.ensureBuffer(bufferLength + rowBytes); var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); @@ -833,7 +833,7 @@ var DecryptStream = (function() { var buffer = this.ensureBuffer(bufferLength + n); for (i = 0; i < n; i++) buffer[bufferLength++] = chunk[i]; - this.bufferLength = n; + this.bufferLength = bufferLength; this.eof = n < chunkSize; }; @@ -1468,7 +1468,7 @@ var Parser = (function() { stream = stream.makeSubStream(pos, length, dict); if (cipherTransform) - stream = cipherTransform.createString(stream); + stream = cipherTransform.createStream(stream); stream = this.filter(stream, dict, length); stream.parameters = dict; return stream; @@ -1802,7 +1802,11 @@ var XRef = (function() { } error("bad XRef entry"); } - e = parser.getObj(this.encrypt); + if (this.encrypt) { + e = parser.getObj(this.encrypt.createCipherTransform(num, gen)); + } else { + e = parser.getObj(); + } // Don't cache streams since they are mutable. if (!IsStream(e)) this.cache[num] = e; @@ -2629,7 +2633,7 @@ var CanvasGraphics = (function() { } } } else if (cmd == "Tf") { // eagerly collect all fonts - var fontRes; // = resources.get("Font"); + var fontRes = resources.get("Font"); if (fontRes) { fontRes = xref.fetchIfRef(fontRes); var font = xref.fetchIfRef(fontRes.get(args[0].name)); diff --git a/security.js b/security.js index d6aa8d3b2..14cc21902 100644 --- a/security.js +++ b/security.js @@ -5,7 +5,6 @@ var ARCFourCipher = (function() { function constructor(key) { - var key = this.key; this.a = 0; this.b = 0; var s = new Uint8Array(256); @@ -133,13 +132,13 @@ var CipherTransform = (function() { } constructor.prototype = { createStream: function (stream) { - var cipher = new streamCipherConstructor(); + var cipher = new this.streamCipherConstructor(); return new DecryptStream(stream, function(data) { return cipher.encryptBlock(data); }); }, decryptString: function(s) { - var cipher = new stringCipherConstructor(); + var cipher = new this.stringCipherConstructor(); var data = string2bytes(s); data = cipher.encryptBlock(data); return bytes2string(data); @@ -240,9 +239,9 @@ var CipherTransformFactory = (function() { constructor.prototype = { createCipherTransform: function(num, gen) { var encryptionKey = this.encryptionKey; - var key = new Uint8Array(encryptionKey.length + 5), i, j, n; - for (j = 0, n = encryptionKey.length; j < n; ++j) - key[j] = encryptionKey[j]; + var key = new Uint8Array(encryptionKey.length + 5), i, n; + for (i = 0, n = encryptionKey.length; i < n; ++i) + key[i] = encryptionKey[i]; key[i++] = num & 0xFF; key[i++] = (num >> 8) & 0xFF; key[i++] = (num >> 16) & 0xFF; From d68b136ac475efc8fd5588148b8eb7d33f74f3e8 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Fri, 24 Jun 2011 22:17:57 -0500 Subject: [PATCH 26/28] security.js -> crypto.js; add ref to the multi-page-viewer --- security.js => crypto.js | 0 multi_page_viewer.html | 1 + viewer.html | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) rename security.js => crypto.js (100%) diff --git a/security.js b/crypto.js similarity index 100% rename from security.js rename to crypto.js diff --git a/multi_page_viewer.html b/multi_page_viewer.html index 47234686d..649e3a7cc 100644 --- a/multi_page_viewer.html +++ b/multi_page_viewer.html @@ -6,6 +6,7 @@ + diff --git a/viewer.html b/viewer.html index 4b9dd3cbd..b8e3fffb8 100644 --- a/viewer.html +++ b/viewer.html @@ -6,7 +6,7 @@ - + From 2f246236cba86fe9c0a9ff182851b05ef51ecf4b Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Fri, 24 Jun 2011 23:27:22 -0400 Subject: [PATCH 27/28] fix issue #78, don't try to use 'in' operator on null --- pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index a34cd7c42..48fd742b2 100644 --- a/pdf.js +++ b/pdf.js @@ -979,7 +979,7 @@ function IsArray(v) { } function IsStream(v) { - return typeof v == "object" && "getChar" in v; + return typeof v == "object" && v != null && ("getChar" in v); } function IsRef(v) { From 9b7ff00f67d706ab63c6d8fbf08c851ab7935427 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 24 Jun 2011 20:41:47 -0700 Subject: [PATCH 28/28] errors thrown by |get numPages()| were not caught. GETTERS!!! --- test/test_slave.html | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/test_slave.html b/test/test_slave.html index d685eeaf2..32076d075 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -6,7 +6,7 @@