diff --git a/LICENSE b/LICENSE index 81658476c..d96b927a3 100644 --- a/LICENSE +++ b/LICENSE @@ -7,6 +7,7 @@ Vivien Nicolas <21@vingtetun.org> Justin D'Arcangelo Yury Delendik + Kalervo Kujala Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -24,4 +25,5 @@ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. \ No newline at end of file + DEALINGS IN THE SOFTWARE. + diff --git a/Makefile b/Makefile index a6f3ba3a4..34af3b24c 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,7 @@ font-test: # To install gjslint, see: # # -SRC_DIRS := . utils worker web +SRC_DIRS := . utils worker web test GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js)) lint: gjslint $(GJSLINT_FILES) diff --git a/fonts.js b/fonts.js index 792b552ba..3d47ef4a2 100755 --- a/fonts.js +++ b/fonts.js @@ -4,11 +4,6 @@ 'use strict'; var isWorker = (typeof window == 'undefined'); -/** - * Maximum file size of the font. - */ -var kMaxFontFileSize = 200000; - /** * Maximum time to wait for a font to be loaded by font-face rules. */ @@ -19,46 +14,46 @@ var kMaxWaitForFontFace = 1000; * fonts and their acronyms. */ var stdFontMap = { - "Arial": "Helvetica", - "Arial_Bold": "Helvetica-Bold", - "Arial_BoldItalic": "Helvetica-BoldOblique", - "Arial_Italic": "Helvetica-Oblique", - "Arial_BoldItalicMT": "Helvetica-BoldOblique", - "Arial_BoldMT": "Helvetica-Bold", - "Arial_ItalicMT": "Helvetica-Oblique", - "ArialMT": "Helvetica", - "Courier_Bold": "Courier-Bold", - "Courier_BoldItalic": "Courier-BoldOblique", - "Courier_Italic": "Courier-Oblique", - "CourierNew": "Courier", - "CourierNew_Bold": "Courier-Bold", - "CourierNew_BoldItalic": "Courier-BoldOblique", - "CourierNew_Italic": "Courier-Oblique", - "CourierNewPS_BoldItalicMT": "Courier-BoldOblique", - "CourierNewPS_BoldMT": "Courier-Bold", - "CourierNewPS_ItalicMT": "Courier-Oblique", - "CourierNewPSMT": "Courier", - "Helvetica_Bold": "Helvetica-Bold", - "Helvetica_BoldItalic": "Helvetica-BoldOblique", - "Helvetica_Italic": "Helvetica-Oblique", - "Symbol_Bold": "Symbol", - "Symbol_BoldItalic": "Symbol", - "Symbol_Italic": "Symbol", - "TimesNewRoman": "Times-Roman", - "TimesNewRoman_Bold": "Times-Bold", - "TimesNewRoman_BoldItalic": "Times-BoldItalic", - "TimesNewRoman_Italic": "Times-Italic", - "TimesNewRomanPS": "Times-Roman", - "TimesNewRomanPS_Bold": "Times-Bold", - "TimesNewRomanPS_BoldItalic": "Times-BoldItalic", - "TimesNewRomanPS_BoldItalicMT": "Times-BoldItalic", - "TimesNewRomanPS_BoldMT": "Times-Bold", - "TimesNewRomanPS_Italic": "Times-Italic", - "TimesNewRomanPS_ItalicMT": "Times-Italic", - "TimesNewRomanPSMT": "Times-Roman", - "TimesNewRomanPSMT_Bold": "Times-Bold", - "TimesNewRomanPSMT_BoldItalic": "Times-BoldItalic", - "TimesNewRomanPSMT_Italic": "Times-Italic" + 'Arial': 'Helvetica', + 'Arial_Bold': 'Helvetica-Bold', + 'Arial_BoldItalic': 'Helvetica-BoldOblique', + 'Arial_Italic': 'Helvetica-Oblique', + 'Arial_BoldItalicMT': 'Helvetica-BoldOblique', + 'Arial_BoldMT': 'Helvetica-Bold', + 'Arial_ItalicMT': 'Helvetica-Oblique', + 'ArialMT': 'Helvetica', + 'Courier_Bold': 'Courier-Bold', + 'Courier_BoldItalic': 'Courier-BoldOblique', + 'Courier_Italic': 'Courier-Oblique', + 'CourierNew': 'Courier', + 'CourierNew_Bold': 'Courier-Bold', + 'CourierNew_BoldItalic': 'Courier-BoldOblique', + 'CourierNew_Italic': 'Courier-Oblique', + 'CourierNewPS_BoldItalicMT': 'Courier-BoldOblique', + 'CourierNewPS_BoldMT': 'Courier-Bold', + 'CourierNewPS_ItalicMT': 'Courier-Oblique', + 'CourierNewPSMT': 'Courier', + 'Helvetica_Bold': 'Helvetica-Bold', + 'Helvetica_BoldItalic': 'Helvetica-BoldOblique', + 'Helvetica_Italic': 'Helvetica-Oblique', + 'Symbol_Bold': 'Symbol', + 'Symbol_BoldItalic': 'Symbol', + 'Symbol_Italic': 'Symbol', + 'TimesNewRoman': 'Times-Roman', + 'TimesNewRoman_Bold': 'Times-Bold', + 'TimesNewRoman_BoldItalic': 'Times-BoldItalic', + 'TimesNewRoman_Italic': 'Times-Italic', + 'TimesNewRomanPS': 'Times-Roman', + 'TimesNewRomanPS_Bold': 'Times-Bold', + 'TimesNewRomanPS_BoldItalic': 'Times-BoldItalic', + 'TimesNewRomanPS_BoldItalicMT': 'Times-BoldItalic', + 'TimesNewRomanPS_BoldMT': 'Times-Bold', + 'TimesNewRomanPS_Italic': 'Times-Italic', + 'TimesNewRomanPS_ItalicMT': 'Times-Italic', + 'TimesNewRomanPSMT': 'Times-Roman', + 'TimesNewRomanPSMT_Bold': 'Times-Bold', + 'TimesNewRomanPSMT_BoldItalic': 'Times-BoldItalic', + 'TimesNewRomanPSMT_Italic': 'Times-Italic' }; var FontMeasure = (function FontMeasure() { @@ -76,14 +71,14 @@ var FontMeasure = (function FontMeasure() { if (!(measureCache = sizes[size])) measureCache = sizes[size] = Object.create(null); } else { - measureCache = null + measureCache = null; } var name = font.loadedName; - var bold = font.bold ? "bold" : "normal"; - var italic = font.italic ? "italic" : "normal"; + var bold = font.bold ? 'bold' : 'normal'; + var italic = font.italic ? 'italic' : 'normal'; size *= kScalePrecision; - var rule = bold + " " + italic + " " + size + 'px "' + name + '"'; + var rule = italic + ' ' + bold + ' ' + size + 'px "' + name + '"'; ctx.font = rule; }, measureText: function fonts_measureText(text) { @@ -144,7 +139,7 @@ var FontLoader = { if (!isWorker && rules.length) { FontLoader.prepareFontLoadEvent(rules, names, objs); } - + if (!checkFontsLoaded()) { document.documentElement.addEventListener( 'pdfjsFontLoad', checkFontsLoaded, false); @@ -395,17 +390,23 @@ var Font = (function Font() { // If the font is to be ignored, register it like an already loaded font // to avoid the cost of waiting for it be be loaded by the platform. if (properties.ignore) { - this.loadedName = 'Arial'; + this.loadedName = 'sans-serif'; this.loading = false; return; } if (!file) { - var fontName = stdFontMap[name]; - this.bold = (fontName.indexOf("Bold") != -1); - this.italic = (fontName.indexOf("Oblique") != -1); - this.loadedName = fontName.split("-")[0]; + // The file data is not specified. Trying to fix the font name + // to be used with the canvas.font. + var fontName = stdFontMap[name] || name.replace('_', '-'); + this.bold = (fontName.indexOf('Bold') != -1); + this.italic = (fontName.indexOf('Oblique') != -1) || + (fontName.indexOf('Italic') != -1); + this.loadedName = fontName.split('-')[0]; this.loading = false; + this.charsToUnicode = function(s) { + return s; + }; return; } @@ -414,7 +415,7 @@ var Font = (function Font() { case 'Type1': case 'CIDFontType0': this.mimetype = 'font/opentype'; - + var subtype = properties.subtype; if (subtype === 'Type1C') { var cff = new Type2CFF(file, properties); @@ -461,6 +462,14 @@ var Font = (function Font() { return array; }; + function arrayToString(arr) { + var str = ''; + for (var i = 0; i < arr.length; ++i) + str += String.fromCharCode(arr[i]); + + return str; + }; + function int16(bytes) { return (bytes[0] << 8) + (bytes[1] & 0xff); }; @@ -496,7 +505,7 @@ var Font = (function Font() { String.fromCharCode(value & 0xff); }; - function createOpenTypeHeader(sfnt, file, offsets, numTables) { + function createOpenTypeHeader(sfnt, file, numTables) { // sfnt version (4 bytes) var header = sfnt; @@ -514,14 +523,13 @@ var Font = (function Font() { // rangeShift (2 bytes) header += string16(numTables * 16 - searchRange); - file.set(stringToArray(header), offsets.currentOffset); - offsets.currentOffset += header.length; - offsets.virtualOffset += header.length; + file.file += header; + file.virtualOffset += header.length; }; - function createTableEntry(file, offsets, tag, data) { + function createTableEntry(file, tag, data) { // offset - var offset = offsets.virtualOffset; + var offset = file.virtualOffset; // length var length = data.length; @@ -530,21 +538,19 @@ var Font = (function Font() { while (data.length & 3) data.push(0x00); - while (offsets.virtualOffset & 3) - offsets.virtualOffset++; + while (file.virtualOffset & 3) + file.virtualOffset++; // checksum var checksum = 0, n = data.length; - for (var i = 0; i < n; i+=4) - checksum = (checksum + int32([data[i], data[i+1], data[i+2], data[i+3]])) | 0; + for (var i = 0; i < n; i += 4) + checksum = (checksum + int32([data[i], data[i + 1], data[i + 2], + data[i + 3]])) | 0; var tableEntry = (tag + string32(checksum) + string32(offset) + string32(length)); - tableEntry = stringToArray(tableEntry); - file.set(tableEntry, offsets.currentOffset); - - offsets.currentOffset += tableEntry.length; - offsets.virtualOffset += data.length; + file.file += tableEntry; + file.virtualOffset += data.length; }; function getRanges(glyphs) { @@ -567,7 +573,7 @@ var Font = (function Font() { } ranges.push([start, end]); } - + return ranges; }; @@ -598,7 +604,7 @@ var Font = (function Font() { var range = ranges[i]; var start = range[0]; var end = range[1]; - var offset = (segCount - i) * 2 + bias * 2; + var offset = (segCount - i) * 2 + bias * 2; bias += (end - start + 1); startCount += string16(start); @@ -730,30 +736,30 @@ var Font = (function Font() { 'Unknown', // 8.Manufacturer 'Unknown' // 9.Designer ]; - + // Mac want 1-byte per character strings while Windows want // 2-bytes per character, so duplicate the names table var stringsUnicode = []; for (var i = 0; i < strings.length; i++) { var str = strings[i]; - + var strUnicode = ''; for (var j = 0; j < str.length; j++) strUnicode += string16(str.charCodeAt(j)); stringsUnicode.push(strUnicode); } - + var names = [strings, stringsUnicode]; var platforms = ['\x00\x01', '\x00\x03']; var encodings = ['\x00\x00', '\x00\x01']; var languages = ['\x00\x00', '\x04\x09']; - + var namesRecordCount = strings.length * platforms.length; var nameTable = '\x00\x00' + // format string16(namesRecordCount) + // Number of names Record string16(namesRecordCount * 12 + 6); // Storage - + // Build the name records field var strOffset = 0; for (var i = 0; i < platforms.length; i++) { @@ -771,7 +777,7 @@ var Font = (function Font() { strOffset += str.length; } } - + nameTable += strings.join('') + stringsUnicode.join(''); return nameTable; } @@ -783,8 +789,10 @@ var Font = (function Font() { encoding: null, checkAndRepair: function font_checkAndRepair(name, font, properties) { + // offset glyphs to the Unicode Private Use Area + var kCmapGlyphOffset = 0xE000; + function readTableEntry(file) { - // tag var tag = file.getBytes(4); tag = String.fromCharCode(tag[0]) + String.fromCharCode(tag[1]) + @@ -803,7 +811,8 @@ var Font = (function Font() { file.pos = previousPosition; if (tag == 'head') - data[8] = data[9] = data[10] = data[11] = 0; // clearing checksum adjustment + // clearing checksum adjustment + data[8] = data[9] = data[10] = data[11] = 0; return { tag: tag, @@ -838,14 +847,14 @@ var Font = (function Font() { encodingID: int16(font.getBytes(2)), offset: int32(font.getBytes(4)) }); - }; + } var encoding = properties.encoding; var charset = properties.charset; for (var i = 0; i < numRecords; i++) { var table = records[i]; font.pos = start + table.offset; - + var format = int16(font.getBytes(2)); var length = int16(font.getBytes(2)); var language = int16(font.getBytes(2)); @@ -855,19 +864,19 @@ var Font = (function Font() { // into the platform so if some characters in the font are assigned // under this limit they will not be displayed so let's rewrite the // CMap. - var glyphs = []; + var glyphs = []; var deltas = []; for (var j = 0; j < 256; j++) { var index = font.getByte(); if (index) { deltas.push(index); - glyphs.push({ unicode : j }); + glyphs.push({ unicode: j }); } } var rewrite = false; for (var code in encoding) { - if (code < 0x20 && encoding[code]) + if (code < 0x20 && encoding[code]) rewrite = true; if (rewrite) @@ -965,23 +974,19 @@ var Font = (function Font() { tables.push(table); } - // Create a new file to hold the new version of our truetype with a new - // header and new offsets - var ttf = new Uint8Array(kMaxFontFileSize); - - // The offsets object holds at the same time a representation of where - // to write the table entry information about a table and another offset - // representing the offset where to put the actual data of a particular - // table var numTables = header.numTables + requiredTables.length; - var offsets = { - currentOffset: 0, + + // header and new offsets. Table entry information is appended to the + // end of file. The virtualOffset represents where to put the actual + // data of a particular table; + var ttf = { + file: '', virtualOffset: numTables * (4 * 4) }; // The new numbers of tables will be the last one plus the num // of missing tables - createOpenTypeHeader('\x00\x01\x00\x00', ttf, offsets, numTables); + createOpenTypeHeader('\x00\x01\x00\x00', ttf, numTables); if (requiredTables.indexOf('OS/2') != -1) { tables.push({ @@ -1001,42 +1006,50 @@ var Font = (function Font() { var numOfHMetrics = int16(font.getBytes(2)); var numOfSidebearings = numGlyphs - numOfHMetrics; - var numMissing = numOfSidebearings - (hmtx.length - numOfHMetrics * 4); + var numMissing = numOfSidebearings - + ((hmtx.length - numOfHMetrics * 4) >> 1); if (numMissing > 0) { font.pos = (font.start ? font.start : 0) + hmtx.offset; - var metrics = ""; + var metrics = ''; for (var i = 0; i < hmtx.length; i++) metrics += String.fromCharCode(font.getByte()); for (var i = 0; i < numMissing; i++) - metrics += "\x00\x00"; + metrics += '\x00\x00'; hmtx.data = stringToArray(metrics); } + // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth + // Sometimes it's 0. That needs to be fixed + if (hhea.data[10] == 0 && hhea.data[11] == 0) { + hhea.data[10] = 0xFF; + hhea.data[11] = 0xFF; + } // Replace the old CMAP table with a shiny new one if (properties.type == 'CIDFontType2') { // Type2 composite fonts map characters directly to glyphs so the cmap // table must be replaced. - + // canvas fillText will reencode some characters even if the font has a + // glyph at that position - e.g. newline is converted to a space and + // U+00AD (soft hyphen) is not drawn. + // So, offset all the glyphs by 0xFF to avoid these cases and use + // the encoding to map incoming characters to the new glyph positions + var glyphs = []; - var charset = properties.charset; - if (!charset.length) { - // Type2 composite fonts map characters directly to glyphs so the cmap - for (var i = 1; i < numGlyphs; i++) { - glyphs.push({ - unicode: i - }); - } - } else { - for (var i = 1; i < charset.length; i++) { - var index = charset.indexOf(i); - if (index == -1) - break; + var encoding = properties.encoding; - glyphs.push({ - unicode: index - }); - } + for (var i = 1; i < numGlyphs; i++) { + glyphs.push({ unicode: i + kCmapGlyphOffset }); + } + + if ('undefined' == typeof(encoding[0])) { + // the font is directly characters to glyphs with no encoding + // so create an identity encoding + for (i = 0; i < numGlyphs; i++) + encoding[i] = i + kCmapGlyphOffset; + } else { + for (var i in encoding) + encoding[i] = encoding[i] + kCmapGlyphOffset; } if (!cmap) { @@ -1080,26 +1093,21 @@ var Font = (function Font() { var tableData = table.data; for (var j = 0; j < tableData.length; j++) data.push(tableData[j]); - createTableEntry(ttf, offsets, table.tag, data); + createTableEntry(ttf, table.tag, data); } // Add the table datas for (var i = 0; i < tables.length; i++) { var table = tables[i]; var tableData = table.data; - ttf.set(tableData, offsets.currentOffset); - offsets.currentOffset += tableData.length; + ttf.file += arrayToString(tableData); // 4-byte aligned data - while (offsets.currentOffset & 3) - offsets.currentOffset++; + while (ttf.file.length & 3) + ttf.file += String.fromCharCode(0); } - var fontData = []; - for (var i = 0; i < offsets.currentOffset; i++) - fontData.push(ttf[i]); - - return fontData; + return stringToArray(ttf.file); }, convert: function font_convert(fontName, font, properties) { @@ -1116,13 +1124,13 @@ var Font = (function Font() { // representing the offset where to draw the actual data of a particular // table var kRequiredTablesCount = 9; - var offsets = { - currentOffset: 0, + + var otf = { + file: '', virtualOffset: 9 * (4 * 4) }; - var otf = new Uint8Array(kMaxFontFileSize); - createOpenTypeHeader('\x4F\x54\x54\x4F', otf, offsets, 9); + createOpenTypeHeader('\x4F\x54\x54\x4F', otf, 9); var charstrings = font.charstrings; properties.fixedPitch = isFixedPitch(charstrings); @@ -1206,18 +1214,14 @@ var Font = (function Font() { }; for (var field in fields) - createTableEntry(otf, offsets, field, fields[field]); + createTableEntry(otf, field, fields[field]); for (var field in fields) { var table = fields[field]; - otf.set(table, offsets.currentOffset); - offsets.currentOffset += table.length; + otf.file += arrayToString(table); } - var fontData = []; - for (var i = 0; i < offsets.currentOffset; i++) - fontData.push(otf[i]); - return fontData; + return stringToArray(otf.file); }, bindWorker: function font_bindWorker(data) { @@ -1247,58 +1251,52 @@ var Font = (function Font() { charsToUnicode: function fonts_chars2Unicode(chars) { var charsCache = this.charsCache; var str; - + // if we translated this string before, just grab it from the cache if (charsCache) { str = charsCache[chars]; if (str) return str; } - + // lazily create the translation cache if (!charsCache) charsCache = this.charsCache = Object.create(null); - + + // translate the string using the font's encoding + var encoding = this.encoding; + if (!encoding) + return chars; + str = ''; + if (this.compositeFont) { - // composite fonts have multi-byte strings - // convert the string from single-byte to multi-byte - // XXX assuming CIDFonts are two-byte - later need to extract the correct byte encoding - // according to the PDF spec - str = ''; - var multiByteStr = ""; - var length = chars.length; + // composite fonts have multi-byte strings convert the string from + // single-byte to multi-byte + // XXX assuming CIDFonts are two-byte - later need to extract the + // correct byte encoding according to the PDF spec + var length = chars.length - 1; // looping over two bytes at a time so + // loop should never end on the last byte for (var i = 0; i < length; i++) { - var byte1 = chars.charCodeAt(i++) & 0xFF; - var byte2; - if (i == length) - byte2 = 0; - else - byte2 = chars.charCodeAt(i) & 0xFF; - multiByteStr += String.fromCharCode((byte1 << 8) | byte2); + var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]); + var unicode = encoding[charcode]; + str += String.fromCharCode(unicode); } - str = multiByteStr; } else { - // translate the string using the font's encoding - var encoding = this.encoding; - if (!encoding) - return chars; - - str = ''; for (var i = 0; i < chars.length; ++i) { var charcode = chars.charCodeAt(i); var unicode = encoding[charcode]; if ('undefined' == typeof(unicode)) { // FIXME/issue 233: we're hitting this in test/pdf/sizes.pdf // at the moment, for unknown reasons. - warn('Unencoded charcode '+ charcode); + warn('Unencoded charcode ' + charcode); unicode = charcode; } - + // Check if the glyph has already been converted if (!IsNum(unicode)) unicode = encoding[unicode] = GlyphsUnicode[unicode.name]; - + // Handle surrogate pairs if (unicode > 0xFFFF) { str += String.fromCharCode(unicode & 0xFFFF); @@ -1455,7 +1453,7 @@ var Type1Parser = function() { var value = ''; var count = array.length; for (var i = 0; i < count; i++) { - value = parseInt(array[i]); + value = array[i]; if (value < 32) { var command = null; @@ -1466,7 +1464,8 @@ var Type1Parser = function() { if (escape == 16) { var index = charstring.pop(); var argc = charstring.pop(); - var data = charstring.pop(); + for (var j = 0; j < argc; j++) + charstring.push('drop'); // If the flex mechanishm is not used in a font program, Adobe // state that that entries 0, 1 and 2 can simply be replace by @@ -1518,11 +1517,11 @@ var Type1Parser = function() { value = command; } else if (value <= 246) { - value = parseInt(value) - 139; + value = value - 139; } else if (value <= 250) { - value = ((value - 247) * 256) + parseInt(array[++i]) + 108; + value = ((value - 247) * 256) + array[++i] + 108; } else if (value <= 254) { - value = -((value - 251) * 256) - parseInt(array[++i]) - 108; + value = -((value - 251) * 256) - array[++i] - 108; } else { value = (array[++i] & 0xff) << 24 | (array[++i] & 0xff) << 16 | (array[++i] & 0xff) << 8 | (array[++i] & 0xff) << 0; @@ -1551,8 +1550,8 @@ var Type1Parser = function() { }; function readNumber(str, index) { - while (str[index++] == ' ') - ; + while (str[index++] == ' '); + var start = index; var count = 0; @@ -1584,6 +1583,17 @@ var Type1Parser = function() { var c = ''; var count = eexecStr.length; for (var i = 0; i < count; i++) { + var getToken = function() { + while (i < count && (eexecStr[i] == ' ' || eexecStr[i] == '\n')) + ++i; + + var t = ''; + while (i < count && !(eexecStr[i] == ' ' || eexecStr[i] == '\n')) + t += eexecStr[i++]; + + return t; + } + var c = eexecStr[i]; if ((glyphsSection || subrsSection) && c == 'R') { @@ -1613,7 +1623,25 @@ var Type1Parser = function() { glyphsSection = true; break; case '/Subrs': - subrsSection = true; + ++i; + var num = parseInt(getToken()); + getToken(); // read in 'array' + for (var j = 0; j < num; ++j) { + var t = getToken(); // read in 'dup' + if (t == 'ND') + break; + var index = parseInt(getToken()); + if (index > j) + j = index; + var length = parseInt(getToken()); + getToken(); // read in 'RD' + var data = eexec.slice(i + 1, i + 1 + length); + var encoded = decrypt(data, kCharStringsEncryptionKey, 4); + var str = decodeCharString(encoded); + i = i + 1 + length; + getToken(); //read in 'NP' + program.subrs[index] = str.charstring; + } break; case '/BlueValues': case '/OtherBlues': @@ -1621,18 +1649,21 @@ var Type1Parser = function() { case '/FamilyOtherBlues': case '/StemSnapH': case '/StemSnapV': - program.properties.private[token.substring(1)] = readNumberArray(eexecStr, i + 2); + program.properties.private[token.substring(1)] = + readNumberArray(eexecStr, i + 2); break; case '/StdHW': case '/StdVW': - program.properties.private[token.substring(1)] = readNumberArray(eexecStr, i + 2)[0]; + program.properties.private[token.substring(1)] = + readNumberArray(eexecStr, i + 2)[0]; break; case '/BlueShift': case '/BlueFuzz': case '/BlueScale': case '/LanguageGroup': case '/ExpansionFactor': - program.properties.private[token.substring(1)] = readNumber(eexecStr, i + 1); + program.properties.private[token.substring(1)] = + readNumber(eexecStr, i + 1); break; } } else if (c == '/') { @@ -1803,8 +1834,10 @@ CFF.prototype = { // Add another offset after this one because we need a new offset var relativeOffset = 1; for (var i = 0; i < count + 1; i++) { - data += String.fromCharCode((relativeOffset >>> 24) & 0xFF, (relativeOffset >> 16) & 0xFF, - (relativeOffset >> 8) & 0xFF, relativeOffset & 0xFF); + data += String.fromCharCode((relativeOffset >>> 24) & 0xFF, + (relativeOffset >> 16) & 0xFF, + (relativeOffset >> 8) & 0xFF, + relativeOffset & 0xFF); if (objects[i]) relativeOffset += objects[i].length; @@ -1812,7 +1845,8 @@ CFF.prototype = { for (var i = 0; i < count; i++) { for (var j = 0; j < objects[i].length; j++) - data += isByte ? String.fromCharCode(objects[i][j] & 0xFF) : objects[i][j]; + data += isByte ? String.fromCharCode(objects[i][j] & 0xFF) : + objects[i][j]; } return data; }, @@ -1889,8 +1923,13 @@ CFF.prototype = { 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)); + for (var i = 0; i < count; i++) { + var subr = type1Subrs[i]; + if (!subr) + subr = [0x0B]; + + type2Subrs.push(this.flattenCharstring(subr, this.commandsMap)); + } return type2Subrs; }, @@ -1912,6 +1951,7 @@ CFF.prototype = { 'sub': [12, 11], 'div': [12, 12], 'pop': [1, 12, 18], + 'drop' : [12, 18], 'endchar': 14, 'rmoveto': 21, 'hmoveto': 22, @@ -2036,7 +2076,7 @@ CFF.prototype = { BlueFuzz: '\x0c\x0b', BlueScale: '\x0c\x09', LanguageGroup: '\x0c\x11', - ExpansionFactor: '\x0c\x18' + ExpansionFactor: '\x0c\x18' }; for (var field in fieldMap) { if (!properties.private.hasOwnProperty(field)) continue; @@ -2080,10 +2120,10 @@ var Type2CFF = (function() { var bytes = file.getBytes(); this.bytes = bytes; this.properties = properties; - + // Other classes expect this.data to be a Javascript array - var data = [] - for (var i = 0, ii = bytes.length; i < ii; ++i) + var data = []; + for (var i = 0, ii = bytes.length; i < ii; ++i) data.push(bytes[i]); this.data = data; @@ -2094,11 +2134,11 @@ var Type2CFF = (function() { parse: function cff_parse() { var header = this.parseHeader(); var nameIndex = this.parseIndex(header.endPos); - + var dictIndex = this.parseIndex(nameIndex.endPos); if (dictIndex.length != 1) error('More than 1 font'); - + var stringIndex = this.parseIndex(dictIndex.endPos); var gsubrIndex = this.parseIndex(stringIndex.endPos); @@ -2114,7 +2154,7 @@ var Type2CFF = (function() { var privOffset = privInfo[1], privLength = privInfo[0]; var privBytes = bytes.subarray(privOffset, privOffset + privLength); baseDict = this.parseDict(privBytes); - var privDict = this.getPrivDict(baseDict, strings); + var privDict = this.getPrivDict(baseDict, strings); TODO('Parse encoding'); var charStrings = this.parseIndex(topDict['CharStrings']); @@ -2125,7 +2165,7 @@ var Type2CFF = (function() { // containing mappings for {unicode, width}) var charstrings = this.getCharStrings(charset, charStrings, privDict, this.properties); - + // create the mapping between charstring and glyph id var glyphIds = []; for (var i = 0, ii = charstrings.length; i < ii; ++i) { @@ -2218,7 +2258,7 @@ var Type2CFF = (function() { var pair = baseDict[i]; var key = pair[0]; var value = pair[1]; - switch(key) { + switch (key) { case 20: dict['defaultWidthX'] = value[0]; case 21: @@ -2240,7 +2280,7 @@ var Type2CFF = (function() { var pair = baseDict[i]; var key = pair[0]; var value = pair[1]; - switch(key) { + switch (key) { case 1: dict['Notice'] = strings[value[0]]; break; @@ -2276,7 +2316,7 @@ var Type2CFF = (function() { }, getStrings: function cff_getstrings(stringIndex) { function bytesToString(bytesArr) { - var s = ""; + var s = ''; for (var i = 0, ii = bytesArr.length; i < ii; ++i) s += String.fromCharCode(bytesArr[i]); return s; @@ -2295,11 +2335,11 @@ var Type2CFF = (function() { var bytes = this.bytes; var offset = 0; - while(bytes[offset] != 1) + while (bytes[offset] != 1) ++offset; if (offset != 0) { - warning("cff data is shifted"); + warning('cff data is shifted'); bytes = bytes.subarray(offset); this.bytes = bytes; } @@ -2307,7 +2347,7 @@ var Type2CFF = (function() { return { endPos: bytes[2], offsetSize: bytes[3] - } + }; }, parseDict: function cff_parseDict(dict) { var pos = 0; @@ -2338,7 +2378,7 @@ var Type2CFF = (function() { }; function parseFloatOperand() { - var str = ""; + var str = ''; var eof = 15; var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; @@ -2361,7 +2401,7 @@ var Type2CFF = (function() { var operands = []; var entries = []; - + var pos = 0; var end = dict.length; while (pos < end) { @@ -2414,8 +2454,8 @@ var Type2CFF = (function() { }, length: count, endPos: end - } - }, + }; + } }; return constructor; diff --git a/pdf.js b/pdf.js index 66d922df2..469eec578 100644 --- a/pdf.js +++ b/pdf.js @@ -48,14 +48,17 @@ function backtrace() { var stackStr; try { throw new Error(); - } catch(e) { + } catch (e) { stackStr = e.stack; - }; + } return stackStr.split('\n').slice(1).join('\n'); } function shadow(obj, prop, value) { - Object.defineProperty(obj, prop, { value: value, enumerable: true, configurable: true, writable: false }); + Object.defineProperty(obj, prop, { value: value, + enumerable: true, + configurable: true, + writable: false }); return value; } @@ -228,6 +231,12 @@ var DecodeStream = (function() { } return String.fromCharCode(this.buffer[this.pos++]); }, + makeSubStream: function decodestream_makeSubstream(start, length, dict) { + var end = start + length; + while (this.bufferLength <= end && !this.eof) + this.readBlock(); + return new Stream(this.buffer, start, length, dict); + }, skip: function decodestream_skip(n) { if (!n) n = 1; @@ -381,13 +390,13 @@ var FlateStream = (function() { var cmf = bytes[bytesPos++]; var flg = bytes[bytesPos++]; if (cmf == -1 || flg == -1) - error('Invalid header in flate stream'); + error('Invalid header in flate stream: ' + cmf + ', ' + flg); if ((cmf & 0x0f) != 0x08) - error('Unknown compression method in flate stream'); + error('Unknown compression method in flate stream: ' + cmf + ', ' + flg); if ((((cmf << 8) + flg) % 31) != 0) - error('Bad FCHECK in flate stream'); + error('Bad FCHECK in flate stream: ' + cmf + ', ' + flg); if (flg & 0x20) - error('FDICT bit set in flate stream'); + error('FDICT bit set in flate stream: ' + cmf + ', ' + flg); this.bytes = bytes; this.bytesPos = bytesPos; @@ -626,7 +635,7 @@ var PredictorStream = (function() { if (predictor <= 1) return stream; // no prediction if (predictor !== 2 && (predictor < 10 || predictor > 15)) - error('Unsupported predictor'); + error('Unsupported predictor: ' + predictor); if (predictor === 2) this.readBlock = this.readBlockTiff; @@ -635,9 +644,7 @@ var PredictorStream = (function() { this.stream = stream; this.dict = stream.dict; - if (params.has('EarlyChange')) { - error('EarlyChange predictor parameter is not supported'); - } + var colors = this.colors = params.get('Colors') || 1; var bits = this.bits = params.get('BitsPerComponent') || 8; var columns = this.columns = params.get('Columns') || 1; @@ -780,7 +787,7 @@ var PredictorStream = (function() { break; } default: - error('Unsupported predictor'); + error('Unsupported predictor: ' + predictor); break; } this.bufferLength += rowBytes; @@ -799,6 +806,11 @@ var JpegStream = (function() { // create DOM image var img = new Image(); + img.onload = (function() { + this.loaded = true; + if (this.onLoad) + this.onLoad(); + }).bind(this); img.src = 'data:image/jpeg;base64,' + window.btoa(bytesToString(bytes)); this.domImage = img; } @@ -815,6 +827,44 @@ var JpegStream = (function() { return constructor; })(); +// Simple object to track the loading images +// Initialy for every that is in loading call imageLoading() +// and, when images onload is fired, call imageLoaded() +// When all images are loaded, the onLoad event is fired. +var ImagesLoader = (function() { + function constructor() { + this.loading = 0; + } + + constructor.prototype = { + imageLoading: function() { + ++this.loading; + }, + + imageLoaded: function() { + if (--this.loading == 0 && this.onLoad) { + this.onLoad(); + delete this.onLoad; + } + }, + + bind: function(jpegStream) { + if (jpegStream.loaded) + return; + this.imageLoading(); + jpegStream.onLoad = this.imageLoaded.bind(this); + }, + + notifyOnLoad: function(callback) { + if (this.loading == 0) + callback(); + this.onLoad = callback; + } + }; + + return constructor; +})(); + var DecryptStream = (function() { function constructor(str, decrypt) { this.str = str; @@ -921,10 +971,10 @@ var AsciiHexStream = (function() { function constructor(str) { this.str = str; this.dict = str.dict; - + DecodeStream.call(this); } - + var hexvalueMap = { 9: -1, // \t 32: -1, // space @@ -953,35 +1003,37 @@ var AsciiHexStream = (function() { }; constructor.prototype = Object.create(DecodeStream.prototype); - + constructor.prototype.readBlock = function() { - var gtCode = '>'.charCodeAt(0), bytes = this.str.getBytes(), c, n, + var gtCode = '>'.charCodeAt(0), bytes = this.str.getBytes(), c, n, decodeLength, buffer, bufferLength, i, length; - + decodeLength = (bytes.length + 1) >> 1; buffer = this.ensureBuffer(this.bufferLength + decodeLength); bufferLength = this.bufferLength; - - for(i = 0, length = bytes.length; i < length; i++) { + + for (i = 0, length = bytes.length; i < length; i++) { c = hexvalueMap[bytes[i]]; - while (c == -1 && (i+1) < length) { + while (c == -1 && (i + 1) < length) { c = hexvalueMap[bytes[++i]]; } - - if((i+1) < length && (bytes[i+1] !== gtCode)) { + + if ((i + 1) < length && (bytes[i + 1] !== gtCode)) { n = hexvalueMap[bytes[++i]]; - buffer[bufferLength++] = c*16+n; + buffer[bufferLength++] = c * 16 + n; } else { - if(bytes[i] !== gtCode) { // EOD marker at an odd number, behave as if a 0 followed the last digit. - buffer[bufferLength++] = c*16; + // EOD marker at an odd number, behave as if a 0 followed the last + // digit. + if (bytes[i] !== gtCode) { + buffer[bufferLength++] = c * 16; } } } - + this.bufferLength = bufferLength; this.eof = true; }; - + return constructor; })(); @@ -1517,18 +1569,18 @@ var CCITTFaxStream = (function() { }; constructor.prototype.lookChar = function() { + if (this.buf != EOF) + return this.buf; + var refLine = this.refLine; var codingLine = this.codingLine; var columns = this.columns; var refPos, blackPixels, bits; - if (this.buf != EOF) - return buf; - if (this.outputBits == 0) { if (this.eof) - return; + return null; this.err = false; @@ -1747,7 +1799,7 @@ var CCITTFaxStream = (function() { code1 = this.lookBits(13); if (code1 == EOF) { this.eof = true; - return; + return null; } if ((code1 >> 1) == 1) { break; @@ -1974,6 +2026,136 @@ var CCITTFaxStream = (function() { return constructor; })(); +var LZWStream = (function() { + function constructor(str, earlyChange) { + this.str = str; + this.dict = str.dict; + this.cachedData = 0; + this.bitsCached = 0; + + var maxLzwDictionarySize = 4096; + var lzwState = { + earlyChange: earlyChange, + codeLength: 9, + nextCode: 258, + dictionaryValues: new Uint8Array(maxLzwDictionarySize), + dictionaryLengths: new Uint16Array(maxLzwDictionarySize), + dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize), + currentSequence: new Uint8Array(maxLzwDictionarySize), + currentSequenceLength: 0 + }; + for (var i = 0; i < 256; ++i) { + lzwState.dictionaryValues[i] = i; + lzwState.dictionaryLengths[i] = 1; + } + this.lzwState = lzwState; + + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.readBits = function(n) { + var bitsCached = this.bitsCached; + var cachedData = this.cachedData; + while (bitsCached < n) { + var c = this.str.getByte(); + if (c == null) { + this.eof = true; + return; + } + cachedData = (cachedData << 8) | c; + bitsCached += 8; + } + this.bitsCached = (bitsCached -= n); + this.cachedData = cachedData; + this.lastCode = null; + return (cachedData >>> bitsCached) & ((1 << n) - 1); + }; + + constructor.prototype.readBlock = function() { + var blockSize = 512; + var estimatedDecodedSize = blockSize * 2, decodedSizeDelta = blockSize; + var i, j, q; + + var lzwState = this.lzwState; + if (!lzwState) + return; // eof was found + + var earlyChange = lzwState.earlyChange; + var nextCode = lzwState.nextCode; + var dictionaryValues = lzwState.dictionaryValues; + var dictionaryLengths = lzwState.dictionaryLengths; + var dictionaryPrevCodes = lzwState.dictionaryPrevCodes; + var codeLength = lzwState.codeLength; + var prevCode = lzwState.prevCode; + var currentSequence = lzwState.currentSequence; + var currentSequenceLength = lzwState.currentSequenceLength; + + var decodedLength = 0; + var currentBufferLength = this.bufferLength; + var buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); + + for (i = 0; i < blockSize; i++) { + var code = this.readBits(codeLength); + var hasPrev = currentSequenceLength > 0; + if (code < 256) { + currentSequence[0] = code; + currentSequenceLength = 1; + } else if (code >= 258) { + if (code < nextCode) { + currentSequenceLength = dictionaryLengths[code]; + for (j = currentSequenceLength - 1, q = code; j >= 0; j--) { + currentSequence[j] = dictionaryValues[q]; + q = dictionaryPrevCodes[q]; + } + } else { + currentSequence[currentSequenceLength++] = currentSequence[0]; + } + } else if (code == 256) { + codeLength = 9; + nextCode = 258; + currentSequenceLength = 0; + continue; + } else { + this.eof = true; + delete this.lzwState; + break; + } + + if (hasPrev) { + dictionaryPrevCodes[nextCode] = prevCode; + dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1; + dictionaryValues[nextCode] = currentSequence[0]; + nextCode++; + codeLength = (nextCode + earlyChange) & (nextCode + earlyChange - 1) ? + codeLength : Math.min(Math.log(nextCode + earlyChange) / + 0.6931471805599453 + 1, 12) | 0; + } + prevCode = code; + + decodedLength += currentSequenceLength; + if (estimatedDecodedSize < decodedLength) { + do { + estimatedDecodedSize += decodedSizeDelta; + } while (estimatedDecodedSize < decodedLength); + buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize); + } + for (j = 0; j < currentSequenceLength; j++) + buffer[currentBufferLength++] = currentSequence[j]; + } + lzwState.nextCode = nextCode; + lzwState.codeLength = codeLength; + lzwState.prevCode = prevCode; + lzwState.currentSequenceLength = currentSequenceLength; + + this.bufferLength = currentBufferLength; + }; + + return constructor; +})(); + + var Name = (function() { function constructor(name) { this.name = name; @@ -2004,10 +2186,12 @@ var Dict = (function() { constructor.prototype = { get: function(key1, key2, key3) { var value; - if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map || typeof key2 == 'undefined') { + if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map || + typeof key2 == 'undefined') { return value; } - if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map || typeof key3 == 'undefined') { + if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map || + typeof key3 == 'undefined') { return value; } @@ -2044,6 +2228,26 @@ var Ref = (function() { return constructor; })(); +// The reference is identified by number and generation, +// this structure stores only one instance of the reference. +var RefSet = (function() { + function constructor() { + this.dict = {}; + } + + constructor.prototype = { + has: function(ref) { + return !!this.dict['R' + ref.num + '.' + ref.gen]; + }, + + put: function(ref) { + this.dict['R' + ref.num + '.' + ref.gen] = ref; + } + }; + + return constructor; +})(); + function IsBool(v) { return typeof v == 'boolean'; } @@ -2183,11 +2387,10 @@ var Lexer = (function() { } while (true); var value = parseFloat(str); if (isNaN(value)) - error('Invalid floating point number'); + error('Invalid floating point number: ' + value); return value; }, getString: function() { - var n = 0; var numParen = 1; var done = false; var str = ''; @@ -2269,8 +2472,6 @@ var Lexer = (function() { break; } } while (!done); - if (!str.length) - return EOF; return str; }, getName: function(ch) { @@ -2285,7 +2486,7 @@ var Lexer = (function() { stream.skip(); var x2 = ToHexDigit(stream.getChar()); if (x2 == -1) - error('Illegal digit in hex char in name'); + error('Illegal digit in hex char in name: ' + x2); str += String.fromCharCode((x << 4) | x2); } else { str += '#'; @@ -2296,7 +2497,8 @@ var Lexer = (function() { } } if (str.length > 128) - error('Warning: name token is longer than allowed by the spec.'); + error('Warning: name token is longer than allowed by the spec: ' + + str.length); return new Name(str); }, getHexString: function(ch) { @@ -2314,14 +2516,14 @@ var Lexer = (function() { if (specialChars[ch.charCodeAt(0)] != 1) { var x, x2; if ((x = ToHexDigit(ch)) == -1) - error('Illegal character in hex string'); + error('Illegal character in hex string: ' + ch); ch = stream.getChar(); while (specialChars[ch.charCodeAt(0)] == 1) ch = stream.getChar(); if ((x2 = ToHexDigit(ch)) == -1) - error('Illegal character in hex string'); + error('Illegal character in hex string: ' + ch); str += String.fromCharCode((x << 4) | x2); } @@ -2381,7 +2583,7 @@ var Lexer = (function() { return new Cmd(ch); // fall through case ')': - error('Illegal character'); + error('Illegal character: ' + ch); return Error; } @@ -2390,7 +2592,7 @@ var Lexer = (function() { while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { stream.skip(); if (str.length == 128) { - error('Command token too long'); + error('Command token too long: ' + str.length); break; } str += ch; @@ -2415,6 +2617,9 @@ var Lexer = (function() { return; } } + }, + skip: function() { + this.stream.skip(); } }; @@ -2436,28 +2641,21 @@ var Parser = (function() { this.buf2 = this.lexer.getObj(); }, shift: function() { - if (this.inlineImg > 0) { - if (this.inlineImg < 2) { - this.inlineImg++; - } else { - // in a damaged content stream, if 'ID' shows up in the middle - // of a dictionary, we need to reset - this.inlineImg = 0; - } - } else if (IsCmd(this.buf2, 'ID')) { - this.lexer.skip(); // skip char after 'ID' command - this.inlineImg = 1; - } - this.buf1 = this.buf2; - // don't buffer inline image data - this.buf2 = (this.inlineImg > 0) ? null : this.lexer.getObj(); + if (IsCmd(this.buf2, 'ID')) { + this.buf1 = this.buf2; + this.buf2 = null; + // skip byte after ID + this.lexer.skip(); + } else { + this.buf1 = this.buf2; + this.buf2 = this.lexer.getObj(); + } }, getObj: function(cipherTransform) { - // refill buffer after inline image data - if (this.inlineImg == 2) - this.refill(); - - if (IsCmd(this.buf1, '[')) { // array + if (IsCmd(this.buf1, 'BI')) { // inline image + this.shift(); + return this.makeInlineImage(cipherTransform); + } else if (IsCmd(this.buf1, '[')) { // array this.shift(); var array = []; while (!IsCmd(this.buf1, ']') && !IsEOF(this.buf1)) @@ -2472,7 +2670,6 @@ var Parser = (function() { while (!IsCmd(this.buf1, '>>') && !IsEOF(this.buf1)) { if (!IsName(this.buf1)) { error('Dictionary key must be a name object'); - shift(); } else { var key = this.buf1.name; this.shift(); @@ -2492,7 +2689,6 @@ var Parser = (function() { this.shift(); } return dict; - } else if (IsInt(this.buf1)) { // indirect reference or integer var num = this.buf1; this.shift(); @@ -2516,6 +2712,46 @@ var Parser = (function() { this.shift(); return obj; }, + makeInlineImage: function(cipherTransform) { + var lexer = this.lexer; + var stream = lexer.stream; + + // parse dictionary + var dict = new Dict(); + while (!IsCmd(this.buf1, 'ID') && !IsEOF(this.buf1)) { + if (!IsName(this.buf1)) { + error('Dictionary key must be a name object'); + } else { + var key = this.buf1.name; + this.shift(); + if (IsEOF(this.buf1)) + break; + dict.set(key, this.getObj(cipherTransform)); + } + } + + // parse image stream + var startPos = stream.pos; + + var c1 = stream.getChar(); + var c2 = stream.getChar(); + while (!(c1 == 'E' && c2 == 'I') && c2 != null) { + c1 = c2; + c2 = stream.getChar(); + } + + var length = (stream.pos - 2) - startPos; + var imageStream = stream.makeSubStream(startPos, length, dict); + if (cipherTransform) + imageStream = cipherTransform.createStream(imageStream); + imageStream = this.filter(imageStream, dict, length); + imageStream.parameters = dict; + + this.buf2 = new Cmd('EI'); + this.shift(); + + return imageStream; + }, makeStream: function(dict, cipherTransform) { var lexer = this.lexer; var stream = lexer.stream; @@ -2530,7 +2766,7 @@ var Parser = (function() { if (xref) length = xref.fetchIfRef(length); if (!IsInt(length)) { - error("Bad 'Length' attribute in stream"); + error('Bad ' + Length + ' attribute in stream'); length = 0; } @@ -2539,7 +2775,7 @@ var Parser = (function() { this.shift(); // '>>' this.shift(); // 'stream' if (!IsCmd(this.buf1, 'endstream')) - error("Missing 'endstream'"); + error('Missing endstream'); this.shift(); stream = stream.makeSubStream(pos, length, dict); @@ -2560,7 +2796,7 @@ var Parser = (function() { for (var i = 0, ii = filterArray.length; i < ii; ++i) { filter = filterArray[i]; if (!IsName(filter)) - error('Bad filter name'); + error('Bad filter name: ' + filter); else { params = null; if (IsArray(paramsArray) && (i in paramsArray)) @@ -2577,18 +2813,26 @@ var Parser = (function() { return new PredictorStream(new FlateStream(stream), params); } return new FlateStream(stream); - } else if (name == 'DCTDecode') { + } else if (name == 'LZWDecode' || name == 'LZW') { + var earlyChange = 1; + if (params) { + if (params.has('EarlyChange')) + earlyChange = params.get('EarlyChange'); + return new PredictorStream( + new LZWStream(stream, earlyChange), params); + } + return new LZWStream(stream, earlyChange); + } else if (name == 'DCTDecode' || name == 'DCT') { var bytes = stream.getBytes(length); return new JpegStream(bytes, stream.dict); - } else if (name == 'ASCII85Decode') { + } else if (name == 'ASCII85Decode' || name == 'A85') { return new Ascii85Stream(stream); - } else if (name == 'ASCIIHexDecode') { + } else if (name == 'ASCIIHexDecode' || name == 'AHx') { return new AsciiHexStream(stream); - } else if (name == 'CCITTFaxDecode') { - TODO('implement fax stream'); + } else if (name == 'CCITTFaxDecode' || name == 'CCF') { return new CCITTFaxStream(stream, params); } else { - error("filter '" + name + "' not supported yet"); + error('filter "' + name + '" not supported yet'); } return stream; } @@ -2621,7 +2865,7 @@ var Linearization = (function() { obj > 0) { return obj; } - error("'" + name + "' field in linearization table is invalid"); + error('"' + name + '" field in linearization table is invalid'); return 0; }, getHint: function(index) { @@ -2634,7 +2878,7 @@ var Linearization = (function() { obj2 > 0) { return obj2; } - error('Hints table in linearization table is invalid'); + error('Hints table in linearization table is invalid: ' + index); return 0; }, get length() { @@ -2709,14 +2953,14 @@ var XRef = (function() { error('Invalid XRef table'); var n = obj; if (first < 0 || n < 0 || (first + n) != ((first + n) | 0)) - error('Invalid XRef table'); + error('Invalid XRef table: ' + first + ', ' + n); for (var i = first; i < first + n; ++i) { var entry = {}; if (!IsInt(obj = parser.getObj())) - error('Invalid XRef table'); + error('Invalid XRef table: ' + first + ', ' + n); entry.offset = obj; if (!IsInt(obj = parser.getObj())) - error('Invalid XRef table'); + error('Invalid XRef table: ' + first + ', ' + n); entry.gen = obj; obj = parser.getObj(); if (IsCmd(obj, 'n')) { @@ -2724,7 +2968,7 @@ var XRef = (function() { } else if (IsCmd(obj, 'f')) { entry.free = true; } else { - error('Invalid XRef table'); + error('Invalid XRef table: ' + first + ', ' + n); } if (!this.entries[i]) { // In some buggy PDF files the xref table claims to start at 1 @@ -2779,13 +3023,13 @@ var XRef = (function() { while (range.length > 0) { var first = range[0], n = range[1]; if (!IsInt(first) || !IsInt(n)) - error('Invalid XRef range fields'); + error('Invalid XRef range fields: ' + first + ', ' + n); var typeFieldWidth = byteWidths[0]; var offsetFieldWidth = byteWidths[1]; var generationFieldWidth = byteWidths[2]; if (!IsInt(typeFieldWidth) || !IsInt(offsetFieldWidth) || !IsInt(generationFieldWidth)) { - error('Invalid XRef entry fields length'); + error('Invalid XRef entry fields length: ' + first + ', ' + n); } for (i = 0; i < n; ++i) { var type = 0, offset = 0, generation = 0; @@ -2811,7 +3055,7 @@ var XRef = (function() { case 2: break; default: - error('Invalid XRef entry type'); + error('Invalid XRef entry type: ' + type); break; } if (!this.entries[first + i]) @@ -2912,12 +3156,12 @@ var XRef = (function() { for (i = 0; i < n; ++i) { var num = parser.getObj(); if (!IsInt(num)) { - error('invalid object number in the ObjStm stream'); + error('invalid object number in the ObjStm stream: ' + num); } nums.push(num); var offset = parser.getObj(); if (!IsInt(offset)) { - error('invalid object offset in the ObjStm stream'); + error('invalid object offset in the ObjStm stream: ' + offset); } } // read stream objects for cache @@ -2940,16 +3184,18 @@ var XRef = (function() { })(); var Page = (function() { - function constructor(xref, pageNumber, pageDict) { + function constructor(xref, pageNumber, pageDict, ref) { this.pageNumber = pageNumber; this.pageDict = pageDict; this.stats = { create: Date.now(), compile: 0.0, fonts: 0.0, - render: 0.0, + images: 0.0, + render: 0.0 }; this.xref = xref; + this.ref = ref; } constructor.prototype = { @@ -2959,7 +3205,7 @@ var Page = (function() { inheritPageProp: function(key) { var dict = this.pageDict; var obj = dict.get(key); - while (!obj) { + while (obj === undefined) { dict = this.xref.fetchIfRef(dict.get('Parent')); if (!dict) break; @@ -2978,32 +3224,79 @@ var Page = (function() { return shadow(this, 'mediaBox', ((IsArray(obj) && obj.length == 4) ? obj : null)); }, + get annotations() { + return shadow(this, 'annotations', this.inheritPageProp('Annots')); + }, + get width() { + var mediaBox = this.mediaBox; + var rotate = this.rotate; + var width; + if (rotate == 0 || rotate == 180) { + width = (mediaBox[2] - mediaBox[0]); + } else { + width = (mediaBox[3] - mediaBox[1]); + } + return shadow(this, 'width', width); + }, + get height() { + var mediaBox = this.mediaBox; + var rotate = this.rotate; + var height; + if (rotate == 0 || rotate == 180) { + height = (mediaBox[3] - mediaBox[1]); + } else { + height = (mediaBox[2] - mediaBox[0]); + } + return shadow(this, 'height', height); + }, + get rotate() { + var rotate = this.inheritPageProp('Rotate') || 0; + // Normalize rotation so it's a multiple of 90 and between 0 and 270 + if (rotate % 90 != 0) { + rotate = 0; + } else if (rotate >= 360) { + rotate = rotate % 360; + } else if (rotate < 0) { + // The spec doesn't cover negatives, assume its counterclockwise + // rotation. The following is the other implementation of modulo. + rotate = ((rotate % 360) + 360) % 360; + } + return shadow(this, 'rotate', rotate); + }, startRendering: function(canvasCtx, continuation, onerror) { var self = this; var stats = self.stats; stats.compile = stats.fonts = stats.render = 0; var gfx = new CanvasGraphics(canvasCtx); - var fonts = [ ]; + var fonts = []; + var images = new ImagesLoader(); - this.compile(gfx, fonts); + this.compile(gfx, fonts, images); stats.compile = Date.now(); + var displayContinuation = function() { + // Always defer call to display() to work around bug in + // Firefox error reporting from XHR callbacks. + setTimeout(function() { + var exc = null; + try { + self.display(gfx); + stats.render = Date.now(); + } catch (e) { + exc = e.toString(); + } + continuation(exc); + }); + }; + var fontObjs = FontLoader.bind( fonts, function() { stats.fonts = Date.now(); - // Always defer call to display() to work around bug in - // Firefox error reporting from XHR callbacks. - setTimeout(function () { - var exc = null; - try { - self.display(gfx); - stats.render = Date.now(); - } catch (e) { - exc = e.toString(); - } - continuation(exc); + images.notifyOnLoad(function() { + stats.images = Date.now(); + displayContinuation(); }); }); @@ -3012,7 +3305,7 @@ var Page = (function() { }, - compile: function(gfx, fonts) { + compile: function(gfx, fonts, images) { if (this.code) { // content was compiled return; @@ -3024,14 +3317,15 @@ var Page = (function() { if (!IsArray(this.content)) { // content is not an array, shortcut content = xref.fetchIfRef(this.content); - this.code = gfx.compile(content, xref, resources, fonts); + this.code = gfx.compile(content, xref, resources, fonts, images); return; } // the content is an array, compiling all items var i, n = this.content.length, compiledItems = []; for (i = 0; i < n; ++i) { content = xref.fetchIfRef(this.content[i]); - compiledItems.push(gfx.compile(content, xref, resources, fonts)); + compiledItems.push(gfx.compile(content, xref, resources, fonts, + images)); } // creating the function that executes all compiled items this.code = function(gfx) { @@ -3049,10 +3343,64 @@ var Page = (function() { var mediaBox = xref.fetchIfRef(this.mediaBox); assertWellFormed(IsDict(resources), 'invalid page resources'); gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1], - width: mediaBox[2] - mediaBox[0], - height: mediaBox[3] - mediaBox[1] }); + width: this.width, + height: this.height, + rotate: this.rotate }); gfx.execute(this.code, xref, resources); gfx.endDrawing(); + }, + rotatePoint: function(x, y) { + var rotate = this.rotate; + switch (rotate) { + default: + case 0: + return {x: x, y: this.height - y}; + case 180: + return {x: this.width - x, y: y}; + case 90: + return {x: this.width - y, y: this.height - x}; + case 270: + return {x: y, y: x}; + } + }, + getLinks: function() { + var xref = this.xref; + var annotations = xref.fetchIfRef(this.annotations) || []; + var i, n = annotations.length; + var links = []; + for (i = 0; i < n; ++i) { + var annotation = xref.fetch(annotations[i]); + if (!IsDict(annotation, 'Annot')) + continue; + var subtype = annotation.get('Subtype'); + if (!IsName(subtype) || subtype.name != 'Link') + continue; + var rect = annotation.get('Rect'); + var topLeftCorner = this.rotatePoint(rect[0], rect[1]); + var bottomRightCorner = this.rotatePoint(rect[2], rect[3]); + + var link = {}; + link.x = Math.min(topLeftCorner.x, bottomRightCorner.x); + link.y = Math.min(topLeftCorner.y, bottomRightCorner.y); + link.width = Math.abs(topLeftCorner.x - bottomRightCorner.x); + link.height = Math.abs(topLeftCorner.y - bottomRightCorner.y); + var a = this.xref.fetchIfRef(annotation.get('A')); + if (a) { + switch (a.get('S').name) { + case 'URI': + link.url = a.get('URI'); + break; + case 'GoTo': + link.dest = a.get('D'); + break; + default: + TODO('other link types'); + break; + } + } + links.push(link); + } + return links; } }; @@ -3076,6 +3424,63 @@ var Catalog = (function() { // shadow the prototype getter return shadow(this, 'toplevelPagesDict', obj); }, + get documentOutline() { + function convertIfUnicode(str) { + if (str[0] === '\xFE' && str[1] === '\xFF') { + // UTF16BE BOM + var i, n = str.length, str2 = ''; + for (i = 2; i < n; i += 2) + str2 += String.fromCharCode( + (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)); + str = str2; + } + return str; + } + var obj = this.catDict.get('Outlines'); + var root = { items: [] }; + if (IsRef(obj)) { + obj = this.xref.fetch(obj).get('First'); + var processed = new RefSet(); + if (IsRef(obj)) { + var queue = [{obj: obj, parent: root}]; + // to avoid recursion keeping track of the items + // in the processed dictionary + processed.put(obj); + while (queue.length > 0) { + var i = queue.shift(); + var outlineDict = this.xref.fetch(i.obj); + if (!outlineDict.has('Title')) + error('Invalid outline item'); + var dest = outlineDict.get('Dest'); + if (!dest && outlineDict.get('A')) { + var a = this.xref.fetchIfRef(outlineDict.get('A')); + dest = a.get('D'); + } + var outlineItem = { + dest: dest, + title: convertIfUnicode(outlineDict.get('Title')), + color: outlineDict.get('C') || [0, 0, 0], + count: outlineDict.get('Count'), + bold: !!(outlineDict.get('F') & 2), + italic: !!(outlineDict.get('F') & 1), + items: [] + }; + i.parent.items.push(outlineItem); + obj = outlineDict.get('First'); + if (IsRef(obj) && !processed.has(obj)) { + queue.push({obj: obj, parent: outlineItem}); + processed.put(obj); + } + obj = outlineDict.get('Next'); + if (IsRef(obj) && !processed.has(obj)) { + queue.push({obj: obj, parent: i.parent}); + processed.put(obj); + } + } + } + } + return shadow(this, 'documentOutline', root); + }, get numPages() { var obj = this.toplevelPagesDict.get('Count'); assertWellFormed( @@ -3096,7 +3501,7 @@ var Catalog = (function() { 'page dictionary kid is not a reference'); var obj = this.xref.fetch(kid); if (IsDict(obj, 'Page') || (IsDict(obj) && !obj.has('Kids'))) { - pageCache.push(new Page(this.xref, pageCache.length, obj)); + pageCache.push(new Page(this.xref, pageCache.length, obj, kid)); } else { // must be a child page dictionary assertWellFormed( IsDict(obj), @@ -3106,6 +3511,39 @@ var Catalog = (function() { } } }, + get destinations() { + var xref = this.xref; + var obj = this.catDict.get('Names'); + obj = obj ? xref.fetch(obj) : this.catDict; + obj = obj.get('Dests'); + var dests = {}; + if (obj) { + // reading name tree + var processed = new RefSet(); + processed.put(obj); + var queue = [obj]; + while (queue.length > 0) { + var i, n; + obj = xref.fetch(queue.shift()); + if (obj.has('Kids')) { + var kids = obj.get('Kids'); + for (i = 0, n = kids.length; i < n; i++) { + var kid = kids[i]; + if (processed.has(kid)) + error('invalid destinations'); + queue.push(kid); + processed.put(kid); + } + continue; + } + var names = obj.get('Names'); + for (i = 0, n = names.length; i < n; i += 2) { + dests[names[i]] = xref.fetch(names[i + 1]).get('D'); + } + } + } + return shadow(this, 'destinations', dests); + }, getPage: function(n) { var pageCache = this.pageCache; if (!pageCache) { @@ -3469,7 +3907,7 @@ var EvalState = (function() { var PartialEvaluator = (function() { function constructor() { this.state = new EvalState(); - this.stateStack = [ ]; + this.stateStack = []; } var OP_MAP = { @@ -3551,6 +3989,8 @@ var PartialEvaluator = (function() { // Images BI: 'beginInlineImage', + ID: 'beginImageData', + EI: 'endInlineImage', // XObjects Do: 'paintXObject', @@ -3568,12 +4008,13 @@ var PartialEvaluator = (function() { }; constructor.prototype = { - eval: function(stream, xref, resources, fonts) { + eval: function(stream, xref, resources, fonts, images) { resources = xref.fetchIfRef(resources) || new Dict(); var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); + var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict(); var parser = new Parser(new Lexer(stream), false); var args = [], argsArray = [], fnArray = [], obj; - + while (!IsEOF(obj = parser.getObj())) { if (IsCmd(obj)) { var cmd = obj.cmd; @@ -3581,7 +4022,23 @@ var PartialEvaluator = (function() { assertWellFormed(fn, "Unknown command '" + cmd + "'"); // TODO figure out how to type-check vararg functions - if (cmd == 'Do' && !args[0].code) { // eagerly compile XForm objects + if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) { + // compile tiling patterns + var patternName = args[args.length - 1]; + // SCN/scn applies patterns along with normal colors + if (IsName(patternName)) { + var pattern = xref.fetchIfRef(patterns.get(patternName.name)); + if (pattern) { + var dict = IsStream(pattern) ? pattern.dict : pattern; + var typeNum = dict.get('PatternType'); + if (typeNum == 1) { + patternName.code = this.eval(pattern, xref, + dict.get('Resources'), fonts); + } + } + } + } else if (cmd == 'Do' && !args[0].code) { + // eagerly compile XForm objects var name = args[0].name; var xobj = xobjs.get(name); if (xobj) { @@ -3595,8 +4052,11 @@ var PartialEvaluator = (function() { ); if ('Form' == type.name) { - args[0].code = this.eval(xobj, xref, xobj.dict.get('Resources'), fonts); + args[0].code = this.eval(xobj, xref, xobj.dict.get('Resources'), + fonts, images); } + if (xobj instanceof JpegStream) + images.bind(xobj); // monitoring image load } } else if (cmd == 'Tf') { // eagerly collect all fonts var fontRes = resources.get('Font'); @@ -3625,7 +4085,7 @@ var PartialEvaluator = (function() { } return function(gfx) { - for(var i = 0, length = argsArray.length; i < length; i++) + for (var i = 0, length = argsArray.length; i < length; i++) gfx[fnArray[i]].apply(gfx, argsArray[i]); } }, @@ -3636,8 +4096,8 @@ var PartialEvaluator = (function() { var subType = fontDict.get('Subtype'); var compositeFont = false; assertWellFormed(IsName(subType), 'invalid font Subtype'); - - // If font is a composite + + // If font is a composite // - get the descendant font // - set the type according to the descendant font // - get the FontDescriptor from the descendant font @@ -3657,13 +4117,22 @@ var PartialEvaluator = (function() { } else { fd = fontDict.get('FontDescriptor'); } - - if (!fd) - return null; - + + if (!fd) { + var baseFontName = fontDict.get('BaseFont'); + if (!IsName(baseFontName)) + return null; + // Using base font name as a font name. + return { + name: baseFontName.name.replace(/[\+,\-]/g, '_'), + fontDict: fontDict, + properties: {} + }; + } + var descriptor = xref.fetch(fd); - var fontName = descriptor.get('FontName'); + var fontName = xref.fetchIfRef(descriptor.get('FontName')); assertWellFormed(IsName(fontName), 'invalid font name'); fontName = fontName.name.replace(/[\+,\-]/g, '_'); @@ -3675,29 +4144,34 @@ var PartialEvaluator = (function() { if (subType.name == 'CIDFontType2') { var cidToGidMap = descendant.get('CIDToGIDMap'); if (cidToGidMap && IsRef(cidToGidMap)) { - // Extract the charset from the CIDToGIDMap + // Extract the encoding from the CIDToGIDMap var glyphsStream = xref.fetchIfRef(cidToGidMap); var glyphsData = glyphsStream.getBytes(0); - var i = 0; // Glyph ids are big-endian 2-byte values - for (var j=0; j> 1] = glyphID; } } } else { - // XXX This is a placeholder for handling of the encoding of CIDFontType0 fonts + // XXX This is a placeholder for handling of the encoding of + // CIDFontType0 fonts var encoding = xref.fetchIfRef(fontDict.get('Encoding')); if (IsName(encoding)) { // Encoding is a predefined CMap if (encoding.name == 'Identity-H') { - TODO ('Need to create an identity cmap') + TODO('Need to create an identity cmap'); } else { - TODO ('Need to support predefined CMaps see PDF 32000-1:2008 9.7.5.2 Predefined CMaps') + TODO('Need to support predefined CMaps see PDF 32000-1:2008 ' + + '9.7.5.2 Predefined CMaps'); } } else { - TODO ('Need to support encoding streams see PDF 32000-1:2008 9.7.5.3'); + TODO('Need to support encoding streams see PDF 32000-1:2008 ' + + '9.7.5.3'); } } } else if (fontDict.has('Encoding')) { @@ -3719,10 +4193,11 @@ var PartialEvaluator = (function() { var index = 0; for (var j = 0; j < differences.length; j++) { var data = differences[j]; - if (subType.name == 'TrueType') { - IsNum(data) ? index = data : encodingMap[index++] = j; + if (IsNum(data)) { + index = data; } else { - IsNum(data) ? index = data : encodingMap[index++] = GlyphsUnicode[data.name]; + encodingMap[index++] = (subType.name == 'TrueType') ? j : + GlyphsUnicode[data.name]; } } @@ -3862,11 +4337,11 @@ var PartialEvaluator = (function() { return { name: fontName, - fontDict: fontDict, + fontDict: fontDict, file: fontFile, properties: properties }; - }, + } }; return constructor; @@ -3903,6 +4378,10 @@ var CanvasExtraState = (function() { constructor.prototype = { clone: function canvasextra_clone() { return Object.create(this); + }, + setCurrentPoint: function canvasextra_setCurrentPoint(x, y) { + this.x = x; + this.y = y; } }; return constructor; @@ -3938,13 +4417,26 @@ var CanvasGraphics = (function() { beginDrawing: function(mediaBox) { var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; this.ctx.save(); - this.ctx.scale(cw / mediaBox.width, -ch / mediaBox.height); - this.ctx.translate(0, -mediaBox.height); + switch (mediaBox.rotate) { + case 0: + this.ctx.transform(1, 0, 0, -1, 0, ch); + break; + case 90: + this.ctx.transform(0, 1, 1, 0, 0, 0); + break; + case 180: + this.ctx.transform(-1, 0, 0, 1, cw, 0); + break; + case 270: + this.ctx.transform(0, -1, -1, 0, cw, ch); + break; + } + this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height); }, - compile: function(stream, xref, resources, fonts) { + compile: function(stream, xref, resources, fonts, images) { var pe = new PartialEvaluator(); - return pe.eval(stream, xref, resources, fonts); + return pe.eval(stream, xref, resources, fonts, images); }, execute: function(code, xref, resources) { @@ -4017,18 +4509,24 @@ var CanvasGraphics = (function() { // Path moveTo: function(x, y) { this.ctx.moveTo(x, y); + this.current.setCurrentPoint(x, y); }, lineTo: function(x, y) { this.ctx.lineTo(x, y); + this.current.setCurrentPoint(x, y); }, curveTo: function(x1, y1, x2, y2, x3, y3) { this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); + this.current.setCurrentPoint(x3, y3); }, curveTo2: function(x2, y2, x3, y3) { - TODO("'v' operator: need current point in gfx context"); + var current = this.current; + this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3); + current.setCurrentPoint(x3, y3); }, curveTo3: function(x1, y1, x3, y3) { this.curveTo(x1, y1, x3, y3, x3, y3); + this.current.setCurrentPoint(x3, y3); }, closePath: function() { this.ctx.closePath(); @@ -4039,7 +4537,7 @@ var CanvasGraphics = (function() { stroke: function() { var ctx = this.ctx; var strokeColor = this.current.strokeColor; - if (strokeColor && strokeColor.type === "Pattern") { + if (strokeColor && strokeColor.type === 'Pattern') { // for patterns, we transform to pattern space, calculate // the pattern, call stroke, and restore to user space ctx.save(); @@ -4060,7 +4558,7 @@ var CanvasGraphics = (function() { var ctx = this.ctx; var fillColor = this.current.fillColor; - if (fillColor && fillColor.type === "Pattern") { + if (fillColor && fillColor.type === 'Pattern') { ctx.save(); ctx.fillStyle = fillColor.getPattern(ctx); ctx.fill(); @@ -4080,7 +4578,7 @@ var CanvasGraphics = (function() { var ctx = this.ctx; var fillColor = this.current.fillColor; - if (fillColor && fillColor.type === "Pattern") { + if (fillColor && fillColor.type === 'Pattern') { ctx.save(); ctx.fillStyle = fillColor.getPattern(ctx); ctx.fill(); @@ -4088,9 +4586,9 @@ var CanvasGraphics = (function() { } else { ctx.fill(); } - + var strokeColor = this.current.strokeColor; - if (strokeColor && strokeColor.type === "Pattern") { + if (strokeColor && strokeColor.type === 'Pattern') { ctx.save(); ctx.strokeStyle = strokeColor.getPattern(ctx); ctx.stroke(); @@ -4098,7 +4596,7 @@ var CanvasGraphics = (function() { } else { ctx.stroke(); } - + this.consumePath(); }, eoFillStroke: function() { @@ -4179,9 +4677,9 @@ var CanvasGraphics = (function() { size = (size <= kRasterizerMin) ? size * kScalePrecision : size; - var bold = fontObj.bold ? "bold" : "normal"; - var italic = fontObj.italic ? "italic" : "normal"; - var rule = bold + " " + italic + " " + size + 'px "' + name + '"'; + var bold = fontObj.bold ? 'bold' : 'normal'; + var italic = fontObj.italic ? 'italic' : 'normal'; + var rule = italic + ' ' + bold + ' ' + size + 'px "' + name + '"'; this.ctx.font = rule; } }, @@ -4223,7 +4721,7 @@ var CanvasGraphics = (function() { ctx.save(); ctx.transform.apply(ctx, current.textMatrix); ctx.scale(1, -1); - + ctx.translate(current.x, -1 * current.y); var scaleFactorX = 1, scaleFactorY = 1; @@ -4270,7 +4768,8 @@ var CanvasGraphics = (function() { if (this.ctx.$addCurrentX) { this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize); } else { - this.current.x -= e * 0.001 * this.current.fontSize * this.current.textHScale; + this.current.x -= e * 0.001 * this.current.fontSize * + this.current.textHScale; } } else if (IsString(e)) { this.showText(e); @@ -4299,11 +4798,11 @@ var CanvasGraphics = (function() { // Color setStrokeColorSpace: function(space) { - this.current.strokeColorSpace = + this.current.strokeColorSpace = ColorSpace.parse(space, this.xref, this.res); }, setFillColorSpace: function(space) { - this.current.fillColorSpace = + this.current.fillColorSpace = ColorSpace.parse(space, this.xref, this.res); }, setStrokeColor: function(/*...*/) { @@ -4403,7 +4902,7 @@ var CanvasGraphics = (function() { var y0 = Math.min(bl[1], br[1], ul[1], ur[1]); var x1 = Math.max(bl[0], br[0], ul[0], ur[0]); var y1 = Math.max(bl[1], br[1], ul[1], ur[1]); - + this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); } else { // HACK to draw the gradient onto an infinite rectangle. @@ -4411,7 +4910,7 @@ var CanvasGraphics = (function() { // Canvas only allows gradients to be drawn in a rectangle // The following bug should allow us to remove this. // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 - + this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); } @@ -4420,11 +4919,13 @@ var CanvasGraphics = (function() { // Images beginInlineImage: function() { - TODO('inline images'); - error('(Stream will not be parsed properly, bailing now)'); - // Like an inline stream: - // - key/value pairs up to Cmd(ID) - // - then image data up to Cmd(EI) + error('Should not call beginInlineImage'); + }, + beginImageData: function() { + error('Should not call beginImageData'); + }, + endInlineImage: function(image) { + this.paintImageXObject(null, image, true); }, // XObjects @@ -4505,7 +5006,14 @@ var CanvasGraphics = (function() { var imgData = tmpCtx.getImageData(0, 0, w, h); var pixels = imgData.data; - imageObj.fillRgbaBuffer(pixels); + if (imageObj.imageMask) { + var inverseDecode = imageObj.decode && imageObj.decode[0] > 0; + // TODO fillColor pattern support + var fillColor = this.current.fillColor; + imageObj.fillUsingStencilMask(pixels, fillColor, + inverseDecode); + } else + imageObj.fillRgbaBuffer(pixels); tmpCtx.putImageData(imgData, 0, 0); ctx.drawImage(tmpCanvas, 0, -h); @@ -4565,7 +5073,7 @@ var CanvasGraphics = (function() { }, restoreFillRule: function(rule) { this.ctx.mozFillRule = rule; - }, + } }; return constructor; @@ -4587,7 +5095,7 @@ var Util = (function() { var yt = p[0] * m[1] + p[1] * m[3] + m[5]; return [xt, yt]; }; - + return constructor; })(); @@ -4601,12 +5109,12 @@ var ColorSpace = (function() { // Input: array of size numComps representing color component values // Output: array of rgb values, each value ranging from [0.1] getRgb: function cs_getRgb(color) { - error('Should not call ColorSpace.getRgb'); + error('Should not call ColorSpace.getRgb: ' + color); }, // Input: Uint8Array of component values, each value scaled to [0,255] // Output: Uint8Array of rgb values, each value scaled to [0,255] getRgbBuffer: function cs_getRgbBuffer(input) { - error('Should not call ColorSpace.getRgbBuffer'); + error('Should not call ColorSpace.getRgbBuffer: ' + input); } }; @@ -4700,10 +5208,10 @@ var ColorSpace = (function() { case 'Lab': case 'DeviceN': default: - error("unimplemented color space object '" + mode + "'"); + error('unimplemented color space object "' + mode + '"'); } } else { - error('unrecognized color space object: "'+ cs +"'"); + error('unrecognized color space object: "' + cs + '"'); } }; @@ -4712,7 +5220,7 @@ var ColorSpace = (function() { var SeparationCS = (function() { function constructor(base, tintFn) { - this.name = "Separation"; + this.name = 'Separation'; this.numComps = 1; this.defaultColor = [1]; @@ -4777,7 +5285,7 @@ var IndexedCS = (function() { for (var i = 0; i < length; ++i) lookupArray[i] = lookup.charCodeAt(i); } else { - error('Unrecognized lookup table'); + error('Unrecognized lookup table: ' + lookup); } this.lookup = lookupArray; } @@ -4879,41 +5387,41 @@ var DeviceCmykCS = (function() { r += 0.1373 * x; g += 0.1216 * x; b += 0.1255 * x; - x = c1 * m1 * y * k1; // 0 0 1 0 + x = c1 * m1 * y * k1; // 0 0 1 0 r += x; g += 0.9490 * x; - x = c1 * m1 * y * k; // 0 0 1 1 + x = c1 * m1 * y * k; // 0 0 1 1 r += 0.1098 * x; g += 0.1020 * x; - x = c1 * m * y1 * k1; // 0 1 0 0 + x = c1 * m * y1 * k1; // 0 1 0 0 r += 0.9255 * x; b += 0.5490 * x; - x = c1 * m * y1 * k; // 0 1 0 1 + x = c1 * m * y1 * k; // 0 1 0 1 r += 0.1412 * x; - x = c1 * m * y * k1; // 0 1 1 0 + x = c1 * m * y * k1; // 0 1 1 0 r += 0.9294 * x; g += 0.1098 * x; b += 0.1412 * x; - x = c1 * m * y * k; // 0 1 1 1 + x = c1 * m * y * k; // 0 1 1 1 r += 0.1333 * x; - x = c * m1 * y1 * k1; // 1 0 0 0 + x = c * m1 * y1 * k1; // 1 0 0 0 g += 0.6784 * x; b += 0.9373 * x; - x = c * m1 * y1 * k; // 1 0 0 1 + x = c * m1 * y1 * k; // 1 0 0 1 g += 0.0588 * x; b += 0.1412 * x; - x = c * m1 * y * k1; // 1 0 1 0 + x = c * m1 * y * k1; // 1 0 1 0 g += 0.6510 * x; b += 0.3137 * x; - x = c * m1 * y * k; // 1 0 1 1 + x = c * m1 * y * k; // 1 0 1 1 g += 0.0745 * x; - x = c * m * y1 * k1; // 1 1 0 0 + x = c * m * y1 * k1; // 1 1 0 0 r += 0.1804 * x; g += 0.1922 * x; b += 0.5725 * x; - x = c * m * y1 * k; // 1 1 0 1 + x = c * m * y1 * k; // 1 1 0 1 b += 0.0078 * x; - x = c * m * y * k1; // 1 1 1 0 + x = c * m * y * k1; // 1 1 1 0 r += 0.2118 * x; g += 0.2119 * x; b += 0.2235 * x; @@ -4929,7 +5437,7 @@ var DeviceCmykCS = (function() { for (var i = 0; i < length; i++) { var cmyk = []; for (var j = 0; j < 4; ++j) - cmyk.push(colorBuf[colorBufPos++]/255); + cmyk.push(colorBuf[colorBufPos++] / 255); var rgb = this.getRgb(cmyk); for (var j = 0; j < 3; ++j) @@ -4952,8 +5460,8 @@ var Pattern = (function() { // Input: current Canvas context // Output: the appropriate fillStyle or strokeStyle getPattern: function pattern_getStyle(ctx) { - error('Should not call Pattern.getStyle'); - }, + error('Should not call Pattern.getStyle: ' + ctx); + } }; constructor.parse = function pattern_parse(args, cs, xref, res, ctx) { @@ -4961,17 +5469,17 @@ var Pattern = (function() { var patternName = args[length - 1]; if (!IsName(patternName)) - error("Bad args to getPattern"); + error('Bad args to getPattern: ' + patternName); - var patternRes = xref.fetchIfRef(res.get("Pattern")); + var patternRes = xref.fetchIfRef(res.get('Pattern')); if (!patternRes) - error("Unable to find pattern resource"); + error('Unable to find pattern resource'); var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); var dict = IsStream(pattern) ? pattern.dict : pattern; - var typeNum = dict.get("PatternType"); + var typeNum = dict.get('PatternType'); - switch(typeNum) { + switch (typeNum) { case 1: var base = cs.base; var color; @@ -4984,13 +5492,14 @@ var Pattern = (function() { color = base.getRgb(color); } - return new TilingPattern(pattern, dict, color, xref, ctx); + var code = patternName.code; + return new TilingPattern(pattern, code, dict, color, xref, ctx); case 2: var shading = xref.fetchIfRef(dict.get('Shading')); var matrix = dict.get('Matrix'); return Pattern.parseShading(shading, matrix, xref, res, ctx); default: - error('Unknown type of pattern'); + error('Unknown type of pattern: ' + typeNum); } }; @@ -5092,9 +5601,9 @@ var RadialAxialShading = (function() { } else if (type == 3) { var p0 = [coordsArr[0], coordsArr[1]]; var p1 = [coordsArr[3], coordsArr[4]]; - var r0 = coordsArr[2], r1 = coordsArr[5] + var r0 = coordsArr[2], r1 = coordsArr[5]; } else { - error() + error(); } var matrix = this.matrix; @@ -5135,8 +5644,8 @@ var RadialAxialShading = (function() { var TilingPattern = (function() { var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; - - function constructor(pattern, dict, color, xref, ctx) { + + function constructor(pattern, code, dict, color, xref, ctx) { function multiply(m, tm) { var a = m[0] * tm[0] + m[1] * tm[2]; var b = m[0] * tm[1] + m[1] * tm[3]; @@ -5149,7 +5658,7 @@ var TilingPattern = (function() { TODO('TilingType'); - this.matrix = dict.get("Matrix"); + this.matrix = dict.get('Matrix'); this.curMatrix = ctx.mozCurrentTransform; this.invMatrix = ctx.mozCurrentTransformInverse; this.ctx = ctx; @@ -5163,14 +5672,14 @@ var TilingPattern = (function() { var topLeft = [x0, y0]; // we want the canvas to be as large as the step size - var botRight = [x0 + xstep, y0 + ystep] + var botRight = [x0 + xstep, y0 + ystep]; var width = botRight[0] - topLeft[0]; var height = botRight[1] - topLeft[1]; // TODO: hack to avoid OOM, we would idealy compute the tiling // pattern to be only as large as the acual size in device space - // This could be computed with .mozCurrentTransform, but still + // This could be computed with .mozCurrentTransform, but still // needs to be implemented while (Math.abs(width) > 512 || Math.abs(height) > 512) { width = 512; @@ -5195,7 +5704,7 @@ var TilingPattern = (function() { tmpCtx.strokeStyle = color; break; default: - error('Unsupported paint type'); + error('Unsupported paint type: ' + paintType); } var scale = [width / xstep, height / ystep]; @@ -5214,9 +5723,7 @@ var TilingPattern = (function() { } var res = xref.fetchIfRef(dict.get('Resources')); - if (!pattern.code) - pattern.code = graphics.compile(pattern, xref, res, []); - graphics.execute(pattern.code, xref, res); + graphics.execute(code, xref, res); this.canvas = tmpCanvas; }; @@ -5260,7 +5767,8 @@ var PDFImage = (function() { this.height = dict.get('Height', 'H'); if (this.width < 1 || this.height < 1) - error('Invalid image width or height'); + error('Invalid image width: ' + this.width + ' or height: ' + + this.height); this.interpolate = dict.get('Interpolate', 'I') || false; this.imageMask = dict.get('ImageMask', 'IM') || false; @@ -5272,19 +5780,21 @@ var PDFImage = (function() { if (this.imageMask) bitsPerComponent = 1; else - error('Bits per component missing in image'); + error('Bits per component missing in image: ' + this.imageMask); } } this.bpc = bitsPerComponent; - var colorSpace = dict.get('ColorSpace', 'CS'); - if (!colorSpace) { - TODO('JPX images (which don"t require color spaces'); - colorSpace = new Name('DeviceRGB'); + if (!this.imageMask) { + var colorSpace = dict.get('ColorSpace', 'CS'); + if (!colorSpace) { + TODO('JPX images (which don"t require color spaces'); + colorSpace = new Name('DeviceRGB'); + } + this.colorSpace = ColorSpace.parse(colorSpace, xref, res); + this.numComps = this.colorSpace.numComps; } - this.colorSpace = ColorSpace.parse(colorSpace, xref, res); - this.numComps = this.colorSpace.numComps; this.decode = dict.get('Decode', 'D'); var mask = xref.fetchIfRef(image.dict.get('Mask')); @@ -5370,7 +5880,8 @@ var PDFImage = (function() { var sw = smask.width; var sh = smask.height; if (sw != this.width || sh != this.height) - error('smask dimensions do not match image dimensions'); + error('smask dimensions do not match image dimensions: ' + sw + + ' != ' + this.width + ', ' + sh + ' != ' + this.height); smask.fillGrayBuffer(buf); return buf; @@ -5380,6 +5891,29 @@ var PDFImage = (function() { } return buf; }, + fillUsingStencilMask: function fillUsingStencilMask(buffer, + cssRgb, inverseDecode) { + var m = /rgb\((\d+),(\d+),(\d+)\)/.exec(cssRgb); // parse CSS color + var r = m[1] | 0, g = m[2] | 0, b = m[3] | 0; + var bufferLength = this.width * this.height; + var imgArray = this.image.getBytes((bufferLength + 7) >> 3); + var i, mask; + var bufferPos = 0, imgArrayPos = 0; + for (i = 0; i < bufferLength; i++) { + var buf = imgArray[imgArrayPos++]; + for (mask = 128; mask > 0; mask >>= 1) { + if (!(buf & mask) != inverseDecode) { + buffer[bufferPos++] = r; + buffer[bufferPos++] = g; + buffer[bufferPos++] = b; + buffer[bufferPos++] = 255; + } else { + buffer[bufferPos + 3] = 0; + bufferPos += 4; + } + } + } + }, fillRgbaBuffer: function fillRgbaBuffer(buffer) { var numComps = this.numComps; var width = this.width; @@ -5406,7 +5940,7 @@ var PDFImage = (function() { fillGrayBuffer: function fillGrayBuffer(buffer) { var numComps = this.numComps; if (numComps != 1) - error('Reading gray scale from a color image'); + error('Reading gray scale from a color image: ' + numComps); var width = this.width; var height = this.height; @@ -5443,7 +5977,7 @@ var PDFFunction = (function() { if (!typeFn) error('Unknown type of function'); - typeFn.call(this, fn, dict); + typeFn.call(this, fn, dict, xref); }; constructor.prototype = { @@ -5458,7 +5992,8 @@ var PDFFunction = (function() { var outputSize = range.length / 2; if (inputSize != 1) - error('No support for multi-variable inputs to functions'); + error('No support for multi-variable inputs to functions: ' + + inputSize); var size = dict.get('Size'); var bps = dict.get('BitsPerSample'); @@ -5466,7 +6001,7 @@ var PDFFunction = (function() { if (!order) order = 1; if (order !== 1) - error('No support for cubic spline interpolation'); + error('No support for cubic spline interpolation: ' + order); var encode = dict.get('Encode'); if (!encode) { @@ -5492,7 +6027,8 @@ var PDFFunction = (function() { } if (inputSize != args.length) - error('Incorrect number of arguments'); + error('Incorrect number of arguments: ' + inputSize + ' != ' + + args.length); for (var i = 0; i < inputSize; i++) { var i2 = i * 2; @@ -5569,7 +6105,7 @@ var PDFFunction = (function() { var c0 = dict.get('C0') || [0]; var c1 = dict.get('C1') || [1]; var n = dict.get('N'); - + if (!IsArray(c0) || !IsArray(c1)) error('Illegal dictionary for interpolated function'); @@ -5578,7 +6114,7 @@ var PDFFunction = (function() { for (var i = 0; i < length; ++i) diff.push(c1[i] - c0[i]); - this.func = function (args) { + this.func = function(args) { var x = args[0]; var out = []; @@ -5588,13 +6124,62 @@ var PDFFunction = (function() { return out; } }, - constructStiched: function() { - TODO('unhandled type of function'); - this.func = function () { return [ 255, 105, 180 ]; } + constructStiched: function(fn, dict, xref) { + var domain = dict.get('Domain'); + var range = dict.get('Range'); + + if (!domain) + error('No domain'); + + var inputSize = domain.length / 2; + if (inputSize != 1) + error('Bad domain for stiched function'); + + var fnRefs = dict.get('Functions'); + var fns = []; + for (var i = 0, ii = fnRefs.length; i < ii; ++i) + fns.push(new PDFFunction(xref, xref.fetchIfRef(fnRefs[i]))); + + var bounds = dict.get('Bounds'); + var encode = dict.get('Encode'); + + this.func = function(args) { + var clip = function(v, min, max) { + if (v > max) + v = max; + else if (v < min) + v = min; + return v; + } + + // clip to domain + var v = clip(args[0], domain[0], domain[1]); + // calulate which bound the value is in + for (var i = 0, ii = bounds.length; i < ii; ++i) { + if (v < bounds[i]) + break; + } + + // encode value into domain of function + var dmin = domain[0]; + if (i > 0) + dmin = bounds[i - 1]; + var dmax = domain[1]; + if (i < bounds.length) + dmax = bounds[i]; + + var rmin = encode[2 * i]; + var rmax = encode[2 * i + 1]; + + var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); + + // call the appropropriate function + return fns[i].func([v2]); + } }, constructPostScript: function() { TODO('unhandled type of function'); - this.func = function () { return [ 255, 105, 180 ]; } + this.func = function() { return [255, 105, 180]; } } }; diff --git a/test/driver.js b/test/driver.js index a6b0b1dc2..716046c4b 100644 --- a/test/driver.js +++ b/test/driver.js @@ -5,138 +5,151 @@ * A Test Driver for PDF.js */ +'use strict'; + var appPath, browser, canvas, currentTaskIdx, manifest, stdout; function queryParams() { - var qs = window.location.search.substring(1); - var kvs = qs.split("&"); - var params = { }; - for (var i = 0; i < kvs.length; ++i) { - var kv = kvs[i].split("="); - params[unescape(kv[0])] = unescape(kv[1]); - } - return params; + var qs = window.location.search.substring(1); + var kvs = qs.split('&'); + var params = { }; + for (var i = 0; i < kvs.length; ++i) { + var kv = kvs[i].split('='); + params[unescape(kv[0])] = unescape(kv[1]); + } + return params; } function load() { - var params = queryParams(); - browser = params.browser; - manifestFile = params.manifestFile; - appPath = params.path; - - canvas = document.createElement("canvas"); - canvas.mozOpaque = true; - stdout = document.getElementById("stdout"); - - log("load...\n"); - - log("Harness thinks this browser is '"+ browser + "' with path " + appPath + "\n"); - log("Fetching manifest "+ manifestFile +"..."); - - var r = new XMLHttpRequest(); - r.open("GET", manifestFile, false); - r.onreadystatechange = function(e) { - if (r.readyState == 4) { - log("done\n"); - manifest = JSON.parse(r.responseText); - currentTaskIdx = 0, nextTask(); - } - }; - r.send(null); + var params = queryParams(); + browser = params.browser; + var manifestFile = params.manifestFile; + appPath = params.path; + + canvas = document.createElement('canvas'); + canvas.mozOpaque = true; + stdout = document.getElementById('stdout'); + + log('load...\n'); + + log('Harness thinks this browser is "' + browser + '" with path "' + + appPath + '"\n'); + log('Fetching manifest "' + manifestFile + '"... '); + + var r = new XMLHttpRequest(); + r.open('GET', manifestFile, false); + r.onreadystatechange = function(e) { + if (r.readyState == 4) { + log('done\n'); + manifest = JSON.parse(r.responseText); + currentTaskIdx = 0, nextTask(); + } + }; + r.send(null); } window.onload = load; function nextTask() { - if (currentTaskIdx == manifest.length) { - return done(); + if (currentTaskIdx == manifest.length) { + return done(); + } + var task = manifest[currentTaskIdx]; + task.round = 0; + + log('Loading file "' + task.file + '"\n'); + + var r = new XMLHttpRequest(); + r.open('GET', task.file); + r.mozResponseType = r.responseType = 'arraybuffer'; + r.onreadystatechange = function() { + var failure; + if (r.readyState == 4) { + var data = r.mozResponseArrayBuffer || r.mozResponse || + r.responseArrayBuffer || r.response; + + try { + task.pdfDoc = new PDFDoc(new Stream(data)); + } catch (e) { + failure = 'load PDF doc : ' + e.toString(); + } + + task.pageNum = 1, nextPage(task, failure); } - var task = manifest[currentTaskIdx]; - task.round = 0; - - log("Loading file "+ task.file +"\n"); - - var r = new XMLHttpRequest(); - r.open("GET", task.file); - r.mozResponseType = r.responseType = "arraybuffer"; - r.onreadystatechange = function() { - var failure; - if (r.readyState == 4) { - var data = r.mozResponseArrayBuffer || r.mozResponse || - r.responseArrayBuffer || r.response; - - try { - task.pdfDoc = new PDFDoc(new Stream(data)); - } catch(e) { - failure = 'load PDF doc: '+ e.toString(); - } - - task.pageNum = 1, nextPage(task, failure); - } - }; - r.send(null); + }; + r.send(null); } function isLastPage(task) { - return (task.pdfDoc && (task.pageNum > task.pdfDoc.numPages)); + return (task.pageNum > task.pdfDoc.numPages); } function nextPage(task, loadError) { - if (isLastPage(task)) { - if (++task.round < task.rounds) { - log(" Round "+ (1 + task.round) +"\n"); - task.pageNum = 1; - } else { - ++currentTaskIdx, nextTask(); - return; - } + var failure = loadError || ''; + + if (!task.pdfDoc) { + sendTaskResult(canvas.toDataURL('image/png'), task, failure); + log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n'); + ++currentTaskIdx, nextTask(); + return; + } + + if (isLastPage(task)) { + if (++task.round < task.rounds) { + log(' Round ' + (1 + task.round) + '\n'); + task.pageNum = 1; + } else { + ++currentTaskIdx, nextTask(); + return; } + } - var failure = loadError || ''; - - var ctx = null; - var page = null; - if (!failure) { - try { - log(" loading page "+ task.pageNum +"... "); - ctx = canvas.getContext("2d"); - page = task.pdfDoc.getPage(task.pageNum); - - var pdfToCssUnitsCoef = 96.0 / 72.0; - // using mediaBox for the canvas size - var pageWidth = (page.mediaBox[2] - page.mediaBox[0]); - var pageHeight = (page.mediaBox[3] - page.mediaBox[1]); - canvas.width = pageWidth * pdfToCssUnitsCoef; - canvas.height = pageHeight * pdfToCssUnitsCoef; - clear(ctx); - - page.startRendering( - ctx, - function(e) { - snapshotCurrentPage(page, task, - (!failure && e) ? ('render: '+ e) : failure); - }); - } catch(e) { - failure = 'page setup: '+ e.toString(); + var page = null; + + if (!failure) { + try { + log(' loading page ' + task.pageNum + '/' + task.pdfDoc.numPages + + '... '); + var ctx = canvas.getContext('2d'); + page = task.pdfDoc.getPage(task.pageNum); + + var pdfToCssUnitsCoef = 96.0 / 72.0; + // using mediaBox for the canvas size + var pageWidth = page.width; + var pageHeight = page.height; + canvas.width = pageWidth * pdfToCssUnitsCoef; + canvas.height = pageHeight * pdfToCssUnitsCoef; + clear(ctx); + + page.startRendering( + ctx, + function(e) { + snapshotCurrentPage(page, task, (!failure && e) ? + ('render : ' + e) : failure); } + ); + } catch (e) { + failure = 'page setup : ' + e.toString(); } + } - if (failure) { - // Skip right to snapshotting if there was a failure, since the - // fonts might be in an inconsistent state. - snapshotCurrentPage(page, task, failure); - } + if (failure) { + // Skip right to snapshotting if there was a failure, since the + // fonts might be in an inconsistent state. + snapshotCurrentPage(page, task, failure); + } } function snapshotCurrentPage(page, task, failure) { - log("done, snapshotting... "); + log('done, snapshotting... '); - sendTaskResult(canvas.toDataURL("image/png"), task, failure); - log("done"+ (failure ? " (failed!: "+ failure +")" : "") +"\n"); + sendTaskResult(canvas.toDataURL('image/png'), task, failure); + log('done' + (failure ? ' (failed !: ' + failure + ')' : '') + '\n'); - // Set up the next request - backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0; - setTimeout(function() { - ++task.pageNum, nextPage(task); + // Set up the next request + var backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0; + setTimeout( + function() { + ++task.pageNum, nextPage(task); }, backoff ); @@ -144,13 +157,14 @@ function snapshotCurrentPage(page, task, failure) { function sendQuitRequest() { var r = new XMLHttpRequest(); - r.open("POST", "/tellMeToQuit?path=" + escape(appPath), false); - r.send(""); + r.open('POST', '/tellMeToQuit?path = ' + escape(appPath), false); + r.send(''); } function quitApp() { - log("Done!"); - document.body.innerHTML = "Tests are finished.

CLOSE ME!

"; + log('Done !'); + document.body.innerHTML = 'Tests are finished.

CLOSE ME!

' + + document.body.innerHTML; if (window.SpecialPowers) { SpecialPowers.quitApplication(); } else { @@ -161,7 +175,7 @@ function quitApp() { function done() { if (inFlightRequests > 0) { - document.getElementById("inFlightCount").innerHTML = inFlightRequests; + document.getElementById('inFlightCount').innerHTML = inFlightRequests; setTimeout(done, 100); } else { setTimeout(quitApp, 100); @@ -172,7 +186,7 @@ var inFlightRequests = 0; function sendTaskResult(snapshot, task, failure) { var result = { browser: browser, id: task.id, - numPages: task.pdfDoc.numPages, + numPages: task.pdfDoc ? task.pdfDoc.numPages : 0, failure: failure, file: task.file, round: task.round, @@ -181,20 +195,20 @@ function sendTaskResult(snapshot, task, failure) { var r = new XMLHttpRequest(); // (The POST URI is ignored atm.) - r.open("POST", "/submit_task_results", true); - r.setRequestHeader("Content-Type", "application/json"); + r.open('POST', '/submit_task_results', true); + r.setRequestHeader('Content-Type', 'application/json'); r.onreadystatechange = function(e) { if (r.readyState == 4) { inFlightRequests--; } } - document.getElementById("inFlightCount").innerHTML = inFlightRequests++; + document.getElementById('inFlightCount').innerHTML = inFlightRequests++; r.send(JSON.stringify(result)); } function clear(ctx) { ctx.save(); - ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); } @@ -209,4 +223,4 @@ function checkScrolling() { function log(str) { stdout.innerHTML += str; checkScrolling(); -} \ No newline at end of file +} diff --git a/test/pdfs/complex_ttf_font.pdf b/test/pdfs/complex_ttf_font.pdf new file mode 100644 index 000000000..284caabe4 --- /dev/null +++ b/test/pdfs/complex_ttf_font.pdf @@ -0,0 +1,575 @@ +%PDF-1.3 +% +1 0 obj +<< +/Parent 2 0 R +/MediaBox [0 0 595 842] +/Resources 3 0 R +/pdftk_PageNum 1 +/Contents 4 0 R +/Type /Page +>> +endobj +2 0 obj +<< +/MediaBox [0 0 612 792] +/Kids [1 0 R] +/Count 1 +/Type /Pages +>> +endobj +4 0 obj +<< +/Length 8531 +>> +stream +q Q q 0 0 595 842 re W n 0 0 0 1 k BT 36 0 0 36 165.615 619.79 Tm /F1.1 1 +Tf [ <033d> -0.2 <0b0b> 0.4 <04a8000301e5> -0.2 <000301b9> 0.2 <033d> -0.2 +<0b61> 0.1 <02250003100e> ] TJ ET BT 36 0 0 36 253.725 619.955 Tm /F1.1 1 +Tf <0d010b61> Tj ET BT 36 0 0 36 268.38 619.79 Tm /F1.1 1 Tf [ <08e7> -0.2 +<04a80003> 0.4 <100c> -0.3 <02be> 0.2 <0003100e> 0.2 <0cb1> -0.2 <033d> -0.2 +<0ccc> 0.4 <00a2000307060225> -0.3 <00a209de> 0.2 <00a200030a2e039d> -0.1 +<0a670003> 0.4 <0a45> -0.1 <0371> -0.1 <02b8> 0.1 <00a200030362> ] TJ ET BT +36 0 0 36 474.69 619.955 Tm /F1.1 1 Tf <0e0f02a4> Tj ET BT 36 0 0 36 494.37 619.79 +Tm /F1.1 1 Tf <04a8> Tj ET BT 36 0 0 36 167.58 568.415 Tm /F1.1 1 Tf [ <033f> +-0.2 <0b0b04a8000301e5> 0.2 <000301ba> 1.2 <033f> -0.2 <0b61> 0.1 <02250003100e> +] TJ ET BT -0.0002 Tc 36 0 0 36 254.25 568.58 Tm /F1.1 1 Tf <0d140b61> Tj +0 Tc ET BT 36 0 0 36 268.02 568.415 Tm /F1.1 1 Tf [ <08ed> -0.4 <04a80003> +0.4 <100c> -0.3 <02be> 0.2 <0003100e> -0.3 <0cba> 0.3 <033f> -0.2 <0ccc00a200030706022500a209df> +0.3 <00a200030a2e039d> -0.1 <0a5e> 1 <00030a45> 0.3 <0371> -0.1 <02ba> 0.1 +<00a200030362> ] TJ ET BT 0.0004 Tc 36 0 0 36 474 568.58 Tm /F1.1 1 Tf <0e0f02a6> +Tj 0 Tc ET BT 36 0 0 36 494.37 568.415 Tm /F1.1 1 Tf <04a8> Tj ET BT 36 0 0 36 161.55 517.055 +Tm /F1.1 1 Tf [ <033d> -0.2 <0b0b04a8000301e5> 0.2 <000301b9> 0.2 <033d> -0.2 +<0b61> 0.1 <0225> -0.3 <0003> 0.4 <100e> ] TJ ET BT 36 0 0 36 249.66 517.22 +Tm /F1.1 1 Tf <0d020b61> Tj ET BT 36 0 0 36 264.315 517.055 Tm /F1.1 1 Tf +[ <08e7> -0.2 <04a80003100c02be> 0.2 <0003100e> -0.3 <0cb1> 0.3 <033d> -0.2 +<0ccc00a200030706022500a209e000a200030a2e039d> -0.1 <0a670003> 0.4 <0a45> +-0.1 <0371> -0.1 <02b8> 0.1 <00a200030362> ] TJ ET BT 36 0 0 36 474.69 517.22 +Tm /F1.1 1 Tf <0e0f02a4> Tj ET BT 36 0 0 36 494.37 517.055 Tm /F1.1 1 Tf <04a8> +Tj ET BT 36 0 0 36 98.88 463.685 Tm /F1.1 1 Tf [ <033d> 0.2 <0b17> -0.2 <04a80003> +0.4 <01e5> -0.2 <000301ba> 1.2 <033f> -0.2 <0b61> 0.1 <02250003100e> ] TJ +ET BT -0.0002 Tc 36 0 0 36 191.955 463.85 Tm /F1.1 1 Tf <0d140b61> Tj 0 Tc +ET BT 36 0 0 36 205.725 463.685 Tm /F1.1 1 Tf [ <08ed> -0.4 <04a8> 0.4 <000303cc02be> +-0.2 <0003100e> 0.2 <0cba033f> 0.2 <0ccc00a20003> ] TJ ET BT 36 0 0 36 309.6 465.695 +Tm /F1.1 1 Tf [ <071e> -0.2 <022500a209df> 0.3 <00a20003> ] TJ ET BT 36 0 0 36 361.68 463.685 +Tm /F1.1 1 Tf [ <020f> -0.2 <039d> 0.3 <0a5e> 1 <0003000300030a4c> 0.2 <0371> +-0.1 <02ba> 0.1 <00a200030362> ] TJ ET BT 0.0004 Tc 36 0 0 36 474 463.85 Tm +/F1.1 1 Tf <0e0f02a6> Tj 0 Tc ET BT 36 0 0 36 494.37 463.685 Tm /F1.1 1 Tf +<04a8> Tj ET BT 36 0 0 36 113.625 409.25 Tm /F1.1 1 Tf <100f> Tj ET BT 0.0001 +Tc 36 0 0 36 125.325 409.775 Tm /F1.1 1 Tf <0d1d0b73> Tj 0 Tc ET BT 36 0 0 36 153.405 409.25 +Tm /F1.1 1 Tf [ <08f3> 0.2 <04a8000303ce02bf0003100e> ] TJ ET BT 36 0 0 36 222.57 409.52 +Tm /F1.1 1 Tf <0d2602a7> Tj ET BT 36 0 0 36 237.69 409.25 Tm /F1.1 1 Tf <0340> +Tj ET BT 36 0 0 36 246.93 410.225 Tm /F1.1 1 Tf <0330> Tj ET BT 36 0 0 36 258.705 410.495 +Tm /F1.1 1 Tf <0dde02a7> Tj ET BT 36 0 0 36 281.07 409.235 Tm /F1.1 1 Tf <00a20003> +Tj ET BT 36 0 0 36 287.295 411.23 Tm /F1.1 1 Tf [ <071e> 0.3 <0225> -0.3 <00a209de> +0.2 <00a20003> ] TJ ET BT 36 0 0 36 339.06 409.235 Tm /F1.1 1 Tf <0a2e03d6> +Tj ET BT -0.0003 Tc 36 0 0 36 357.555 410.21 Tm /F1.1 1 Tf <030e0a71> Tj 0 +Tc ET BT 36 0 0 36 380.415 409.235 Tm /F1.1 1 Tf [ <0003000300030a45> -0.1 +<0363> ] TJ ET BT -0.0006 Tc 36 0 0 36 411.51 409.58 Tm /F1.1 1 Tf <02f7033102a7> +Tj 0 Tc ET BT 36 0 0 36 447.885 409.235 Tm /F1.1 1 Tf <00a200030363> Tj ET +BT 36 0 0 36 464.31 409.82 Tm /F1.1 1 Tf <02fe> Tj ET BT 36 0 0 36 478.74 409.55 +Tm /F1.1 1 Tf <0e0702a7> Tj ET BT 36 0 0 36 494.37 409.235 Tm /F1.1 1 Tf <04a8> +Tj ET BT 36 0 0 36 162.15 348.08 Tm /F1.1 1 Tf [ <000303e6033d> -366 <043e> +365.8 <0b0b> 0.4 <00a4> -0.3 <0003043c> -117.1 <03dc> 117.1 <01e5> 0.2 <0003043c01b9> +-0.2 <033d> 0.2 <03dc> -117.1 <03df> 117.1 <0b61> -58.2 <0439> 58.3 <0225> +-0.3 <0003> -58 <03dc> 58.3 <100e> ] TJ ET BT 36 0 0 36 252.63 348.245 Tm +/F1.1 1 Tf <0d01043d0b61> Tj ET BT 36 0 0 36 265.185 348.08 Tm /F1.1 1 Tf +[ <0410> -175.4 <03e1> 117.1 <08e7> -0.2 <00a4> 0.1 <0003> -58.4 <043e> 58.3 +<100c046c02be> -0.2 <0003> -58.4 <03dc> 58.3 <100e> 0.2 <044e0cb1> -0.2 <033d> +-116.9 <03f2> 117.1 <0ccc04aa000303dc> -117.1 <03dc> 117.1 <07060410022500a203f209de> +-0.2 <04aa0003> -58.4 <043c> 58.3 <0a2e0450> -175.4 <03dc> 175.4 <039d> 58.7 +<043c> -58.3 <0a670003> -117.1 <044f> 117.1 <0a45> -0.1 <043d> -234.2 <043e> +234.2 <0371> 58.2 <046c> -58.3 <03e102b8> 0.1 <04aa0003> -58.4 <043c> 58.3 +<0362> ] TJ ET BT 0.3513 Tc 36 0 0 36 472.05 348.245 Tm /F1.1 1 Tf [ <03e103df> +644 <0e0f> 410 <03e1> 293 <02a4> ] TJ 0 Tc ET BT 36 0 0 36 493.815 348.08 +Tm /F1.1 1 Tf <00a4> Tj ET BT 36 0 0 36 162.15 278.72 Tm /F1.1 1 Tf [ <000303e6033d> +-366 <0411> 365.8 <0b0b> 0.4 <00a4> -0.3 <0003040f> -117.1 <0495> 117.1 <01e5> +0.2 <0003040f01b9> -0.2 <033d> 0.2 <0495> -117.1 <0496> 117.1 <0b61> -58.2 +<040c> 58.3 <0225> -0.3 <0003> -58 <0495> 58.3 <100e> ] TJ ET BT 36 0 0 36 252.63 278.885 +Tm /F1.1 1 Tf <0d01043d0b61> Tj ET BT 36 0 0 36 265.185 278.72 Tm /F1.1 1 +Tf [ <03e0> -175.4 <0497> 117.1 <08e7> -0.2 <00a4> 0.1 <0003> -58.4 <0411> +58.3 <100c043d02be> -0.2 <0003> -58.4 <0495> 58.3 <100e> 0.2 <041f0cb1> -0.2 +<033d> -116.9 <03f2> 117.1 <0ccc04aa00030495> -117.1 <0495> 117.1 <07060410022500a203f209de> +-0.2 <04aa0003> -58.4 <040f> 58.3 <0a2e0421> -175.4 <0495> 175.4 <039d> 58.7 +<043c> -58.3 <0a670003> -117.1 <0420> 117.1 <0a45> -0.1 <0410> -234.2 <0411> +234.2 <0371> 58.2 <043d> -58.3 <049702b8> 0.1 <04aa0003> -58.4 <040f> 58.3 +<0362> ] TJ ET BT 0.3513 Tc 36 0 0 36 472.05 278.885 Tm /F1.1 1 Tf [ <04970496> +644 <0e0f> 410 <0497> 293 <02a4> ] TJ 0 Tc ET BT 36 0 0 36 493.815 278.72 +Tm /F1.1 1 Tf <00a4> Tj ET BT 36 0 0 36 140.535 209.345 Tm /F1.1 1 Tf [ <000303e9033d> +-365.6 <046e> 365.8 <0b0b00a4> 0.1 <0003046b> -117.1 <03dd> 117.1 <01e5> -0.2 +<0003046b01b9> 0.2 <033d> -0.2 <040c> -117.1 <040f> 117.1 <0b61> -58.2 <0468> +58.3 <02250003> -58.4 <040d> 58.3 <100e> ] TJ ET BT 36 0 0 36 231 209.51 Tm +/F1.1 1 Tf [ <0d01> -0.4 <043d0b61> ] TJ ET BT 36 0 0 36 243.57 209.345 Tm +/F1.1 1 Tf [ <0410> -175.4 <0412> 117.1 <08e7> 0.3 <00a4> -0.3 <0003> -58.4 +<046e> 58.3 <100c046c02be> 0.2 <0003> -58.4 <040d> 58.3 <100e> -0.3 <047b0cb1> +0.3 <033d> -117.3 <041f> 117.1 <0ccc04aa000303dc> -117.1 <040c> 117.1 <07060410022500a2041f09de> +0.2 <04aa0003> -58.4 <040f> 58.3 <0a2e043d041e03d6043a0316> 58.4 <046b> -58.3 +<0a5d0003> -117.1 <047c> 117.1 <0a45> -0.1 <043d> -234.2 <043f> 234.2 <0371> +58.2 <046c> -58.3 <03e202b8> 0.1 <04aa0003> -58.4 <046b> 58.3 <0362> ] TJ +ET BT 0.3513 Tc 36 0 0 36 472.05 209.51 Tm /F1.1 1 Tf [ <03e2040f> 644 <0e0f> +410 <0497> 293 <02a4> ] TJ 0 Tc ET BT 36 0 0 36 493.815 209.345 Tm /F1.1 1 +Tf <00a4> Tj ET BT 9 0 0 9 609.45 625.205 Tm /F2.1 1 Tf [ <0031> 0.5 <005200030026> +0.5 <004b> -0.5 <00440051> -0.5 <004a0048> ] TJ ET BT 9 0 0 9 609.45 573.2 +Tm /F2.1 1 Tf [ <0026> 0.5 <004b> -0.5 <00440055> 0.5 <00440046> 0.5 <0057> +-0.3 <0048> 1.5 <0055> 0.5 <00030044004f> -0.5 <0057> -0.3 <0048> 1.5 <0055> +0.5 <0051> -0.5 <00440057> -0.3 <0048> 1.5 <0056> 0.8 <00030014> ] TJ ET BT +9 0 0 9 609.45 521.195 Tm /F2.1 1 Tf [ <0026> 0.5 <004b> -0.5 <00440055> 0.5 +<00440046> 0.5 <0057> -0.3 <0048> 1.5 <0055> 0.5 <00030044004f> -0.5 <0057> +-0.3 <0048> 1.5 <0055> 0.5 <0051> -0.5 <00440057> -0.3 <0048> 1.5 <0056> 0.8 +<00030015> ] TJ ET BT 9 0 0 9 609.45 469.205 Tm /F2.1 1 Tf [ <0029> -0.8 <004c> +0.5 <0051> -0.5 <0044004f> -0.5 <0003003a004c> 0.5 <0047> -0.5 <0048> 1.5 +<00030029> -0.8 <00520055> 0.5 <0050> -0.3 <0056> ] TJ ET BT 9 0 0 9 609.45 406.805 +Tm /F2.1 1 Tf [ <0032> 0.5 <0057> -0.3 <004b> -0.5 <0048> 1.5 <0055> 0.5 <0003003a004c> +0.5 <0047> -0.5 <0048> 1.5 <00030029> -0.8 <00520055> 0.5 <0050> -0.3 <0056> +] TJ ET BT 9 0 0 9 609.45 344.405 Tm /F2.1 1 Tf [ <0031> 0.5 <00520055> 0.5 +<0050> -0.3 <0044004f> -0.5 <00030027> 0.5 <004c> 0.5 <00440046> 0.5 <0055> +0.5 <004c> 0.5 <0057> -0.3 <004c> 0.5 <0046> 0.5 <00030033> 0.2 <00520056> +0.8 <004c> 0.5 <0057> -0.3 <004c> 0.5 <00520051> -0.5 <0056> ] TJ ET BT 9 0 0 9 609.45 282.005 +Tm /F2.1 1 Tf [ <0029> -0.8 <00440055> 0.5 <0057> -0.3 <004b> -0.5 <0048> +1.5 <0055> 0.5 <00030027> 0.5 <004c> 0.5 <00440046> 0.5 <0055> 0.5 <004c> +0.5 <0057> -0.3 <004c> 0.5 <0046> 0.5 <0056> ] TJ ET BT 9 0 0 9 609.45 209.195 +Tm /F2.1 1 Tf [ <0027> 0.5 <004c> 0.5 <00440046> 0.5 <0055> 0.5 <004c> 0.5 +<0057> -0.3 <004c> 0.5 <0046> 0.5 <00030024> 0.5 <004f> -0.5 <0057> -0.3 <0048> +1.5 <0055> 0.5 <0051> -0.5 <00440057> -0.3 <0048> 1.5 <0056> ] TJ ET Q +endstream +endobj +3 0 obj +<< +/Font +<< +/F2.1 5 0 R +/F1.1 6 0 R +>> +/ProcSet [/PDF /Text] +>> +endobj +6 0 obj +<< +/DescendantFonts [7 0 R] +/BaseFont /LSUISA+font0000000013f5eeab +/Subtype /Type0 +/Encoding /Identity-H +/Type /Font +>> +endobj +5 0 obj +<< +/DescendantFonts [8 0 R] +/BaseFont /ZCCVRA+font0000000013f5eeab +/Subtype /Type0 +/Encoding /Identity-H +/Type /Font +>> +endobj +9 0 obj +<< +/Pages 2 0 R +/Type /Catalog +>> +endobj +7 0 obj +<< +/BaseFont /LSUISA+font0000000013f5eeab +/DW 1000 +/CIDSystemInfo +<< +/Supplement 0 +/Ordering (Identity) +/Registry (Adobe) +>> +/Subtype /CIDFontType2 +/FontDescriptor 10 0 R +/W 11 0 R +/CIDToGIDMap 12 0 R +/Type /Font +>> +endobj +10 0 obj +<< +/FontName /LSUISA+font0000000013f5eeab +/StemV 0 +/FontFile2 13 0 R +/Ascent 785 +/Flags 32 +/XHeight 523 +/Descent -662 +/ItalicAngle 0 +/MaxWidth 4959 +/FontBBox [-979 -662 4935 785] +/Type /FontDescriptor +/CapHeight 291 +>> +endobj +13 0 obj +<< +/Length 15672 +/Length1 15672 +>> +stream +true cvt XfpgmLglyf7Lhead6hhea4$hmtx`  %=#,:!!>%(*ys7&'67&'&'6767'"*; )*AH (7 +{8(%C9',/ 3C##)s'&'67&'&'6767h#*: (*@H )8 +{8(%C9',/ 3C##). 2547&'&676'&5&76 M<'+C& / 0/ +1 U +(;#"&#"#"5476326324#"6;'-C+% +!2K"#%%<2)/.#'nGE `7#&5&'&'&7632 +$ + 1; !j'1BP.A#"767676547&'&7676&'&7676"'&'&&632I &ed +>h1,"& / #  ," /L.O +@'1E$'-1  b@ :1dJ)1%#"'&'#"54767654'&63232654'&632&'76J!%?@%C`? f %7/%   *B,0E183\* -49G + V{? +1r1=(-%.2d+3%# '&'#"5474'&632!27654'&6326'76!T쭌O *<] +  +)U>áG&.:2 41MA$I5 -I0<%kI2g,%'GoAL- E?" +)1:%'#"'&'&563267654'&'&632327654'#"&546324#"32 +^YG%_Y +(/NK?*$470>-3I#&',`) +''}sl @WI!m2))L>*8&#f‟F T%6;Oa;d7q1%#"'&54763232654''#"'&>32q xK_m9-- 60Bx ' % *)B8hK/A3GsQ:I>&#Q4M27tzv!(PJ%#3l ,L# b7HPT=?5%#"54767676767654'&54632-3>6EpUT<0+6CmY>12 ! $,)]29(/)$_uLqZ +X4:G,Y.,B'D'(%b) +9d+19%#"'#"547676'&463232545&54632&'76+\S. *@\:1Z +W779#* \,0`Ϸ..+A#DV3)NR +e FA-%Nd +y!%#"547632767654'632tfgV[MX])V #)jD$oI@QIAg(Ea)+? p~gRae7&'67# %&'&'&'&76!27676767654'&632}"H ) +&!q<بbL80/q +:y=H 78( 3D3KV + L(87+6P-j59}72#"546327676u*R4 5.N7{%#"54632765&'&4763+8čk! Z>",7# )&59^+DR%#"''"''&547&'&'&#"#"5463276763263267676&4632&'&#"327676+);t~5)$UV'   5G94 + .##r.k3$rrbI !+'.AHA~$6=D%5   %B7(11q$$sg;Lh ow?K%!"%''&547&'&12#"54632767632367632327654&'674'&#"3276o]}V( ! +)8I( +  %)G=4<-(KGwО2[-%+'Hg_Pk*: F%!&!7"  +/'3` +N;4Zw +yr'4%#&'#"546327&54632'.46324'&#"326 5slOc)v?1DE`@ % F)I54A197P_;=4Za/?2!- >+Hr)6%# '#"546327&546327654'674'&#"326H>2үe\JU)v?1V殲B $ F)Ia,$?37P_;=%"/B75*+#7#"546327654'&74632$dO'( + +c-A7,E!+ Z" + O + 7#"5463276'&'&#"547632+%D0) 2  +@-7 %#M!d7IOT#%#"5463276'&54763NkvK'* + #$u,>7&DY *." %) + 1%'"&#"#"546327676323254'&54632:'u#6TO0(f%_ #  '(MnL(/'7 ('!)f  =%1%#"'#"546327654'&5&6323276&74632%LV)j0   +?$W 0b7, ?OZ|P%,0 c! +\>W#"'#"'#"&#"&'&#"#"546327&7632326#2576763254'&7&7462 7@ ?23EGN !)C(( ##+/u33m0-; >BD B  +):"  +&-s+-<'V-3y$*22@=7ZT- T, $(% +*& R +e+%#2&#"#"546327&'&'&54632 M< =8) 51`76>H7#"&'#"54632767637654'&'&746322'&'&'&>3^,H. 9$z ! +  +X FVe;$5("F(7/W0 > Z! + &)  @uE%'#"546327676254&#"u+8x4iC,"#-/* B(!5N976<61:( T!%2#"'&'&#"#"54632767632FA$ -56#   *K8$#8 +#%2#"'#"5463276'&'&632 htW K70+AQ ]r&H%2#"'!"54632767632R!a^ &K9b7; * %2#"'&'&#"#"5463276763" E + /MK. +(MK977+ ,d6%2#"'#"'#"7#"546327676327676326V> !;8,Ug Dzt> +D= 3+AK9`V)=-37' +8 ( (j 8L1;%2#"'''&547&'#"546327676766763274'&#"676 kP-?'ˆ\B"(,+8>qK77 <  aFBy,T-# 03[ C23+#'%2#"'#"546327&546'4'&#"326TIV)v?1AH6 F)IK8B27O_Bq!v". <,BOY%2#"'%#"54627676'7&54632676327&546'4'&326!4&#"676 w>O+7&& %l$-4@*$<|Su?1A' + @'DuL',_oz{xK3:7'1a2 +R !JƲ/+J\ NSP`Csg* 7'&:]"!/0%2#"'&'#"546327654'&'&5476%67! qc +7C\E:h@V3:p8}K=6<.k6@7HK~\?F`6?<# $P8%2#"'&547!"5463 %$%&#"# 5476732$32B&!L_{{297 YLw1FBcB<=K9,&766:7921[YNx,*4%V.iV1,5>M>&%2#"'#"'&7#"54632767632&'&#"  'Zbˆ`F06 Z K9 ++ 8?M7 DL.%# +{(%2#"'#"'&7#"546327>72'&'&#m Q &M`7fN/5%+5Z K) ! 8[J(s3-( T 3&67676!u%!'+e@49D  (#&'&''&76767&'&54632'4#"6  >!6#>0(># #7#6]  0% #!!#B9!@ + 21&>" ;`M*(4 .*0"WG  #"547>32&#"32C6 +79!& 0PD@+=`J3-'&767&#"#"'&632K799F )L P4D[ + a + 2'&767>2'&7># ;_M 0# ;^N*4 /*VA5.+!W9r &67676&67676b*0%)0% &5oB5BM, '4pA4AN 321&767>#"''&767676767676'&76) .UQ "2 !/  +- 9C1- +3 2'&7> ! 8[J(3-( T 3&67676!u%!'+e@49D2 Z(#"'&''&76767&547632'4#"6 @/2">0A!# #7 5   /&#!! #" 9"> +u 2'&67>" :`L*u5/*3 XGw? #"54767632'&#"3216 +9!& 0#>+ _( I3-l'&767&#"#"'&632K799F V P4DY  a \%#"''&763676767676'&722 !/ + C1/ +\w 22&767>#"''&763676767676'&72( .2 !/ + u.= 8C1/ +\!GO#"&'"'&76767&'&54632#"''&763676767676'&72'4#"6 =!5 5+%8 2 !/ + >0a )%<2C1/ +D +[$1#"''&763676767>'&722'&767>2 !/ +  :* . !1D 20 -> 9G 2'&7> ! 8[J(G3-( Td| 3&67676!u%|!'+e@49D +"*#0&'&''&76767&'&547632'4#"6 + ?'9"90)!# #7#5/ /%!!#"8 "<? 21&767># ;`M ?5/( YG #"547>32'&#"3206 +79!& 0%@+=_( +H1-'&767&#"#"'&632K899F Y O5Dh[   _ + 12'&767>#"''&763676767>'&76) .3 !/ +  /? 8D00 ODL#.'#"5&76767&'&54632#"''&763676767>'&76'4#"6  =!1 5+%8 3 !/ +  >0# + '$ +;4! D00   $1#"''&763676767>'&762&767>2 !/  :* .C1. -=  8 2'&7> ! 8[J(3-( T +- (&'&''&76767&'&54632'4#"6 + >%7$90(># #8#6 + 0%!""B8!!=T 2'&>" :`L)(5/*0"W@'&767&#"#&'&632K899F Y O5D\  _ +0J 12'&767>#"''&763676767>'&76) .2 !/ +  I0? 8C00 0CK#&'&'#"5&767&'&54632#"''&763676767>'&76'4#"6  ;&,  i!%8 2 !/ +  =04# )";/ +;4"C00  Q'&767676A RbP*H 5'&0'&''6767&'&54632'4#"6 7*)j!#8 2 0: - ;. <4!9  #"547>32&#"32D5 6<#&1%D@+=bJ3-" +> +stream + + #$%)*+,236789:;<=>?@BCFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ +endstream +endobj +8 0 obj +<< +/BaseFont /ZCCVRA+font0000000013f5eeab +/DW 1000 +/CIDSystemInfo +<< +/Supplement 0 +/Ordering (Identity) +/Registry (Adobe) +>> +/Subtype /CIDFontType2 +/FontDescriptor 14 0 R +/W 15 0 R +/CIDToGIDMap 16 0 R +/Type /Font +>> +endobj +14 0 obj +<< +/FontName /ZCCVRA+font0000000013f5eeab +/StemV 0 +/FontFile2 17 0 R +/Leading 42 +/Ascent 891 +/Flags 32 +/XHeight 242 +/Descent -216 +/ItalicAngle 0 +/MaxWidth 2000 +/FontBBox [-558 -307 2000 1026] +/Type /FontDescriptor +/CapHeight 346 +>> +endobj +17 0 obj +<< +/Length 20588 +/Length1 20588 +>> +stream +true + cvt &fpgmglyf#X,head6hhea4$hmtx"hloca# 6maxp nameP<0prepXA4444455545L5d5|5</VHI +_<5FON5 !EWT  2  2 ( A?__@x3@r3@n3@j3@T3@J32@ + 2 2@Z3@A V3S3P3@M3@G3DE2@B3@>3:3A$ @p//@3A/`  ?O +lEE2@+.2@()22'2BB2682*-2A +((.22@2@APo-12 +2A  @2@2߲042߳2PA# 2Ay)z2~ٲ2ٲ(*2A +|2sq4lk2 @ 2@2kkn/n 2A @Ҳ 2A +@ 2@3@ 2A &82A&0 00Po0/py2y$(2}|2~~ 2@|2| 2?O@س2o@2@Բ 2{z2z2@ 2AToTTTTT/T_T@P2Z@I3HHH"2G"22-2/QKONd@Q2N8:20NN#2N2F 2C +2D+2+2*2A:37 707322PAd2@2`222202P22666p661?11111`10`00P//@/`/////--0----o,, @:@5%,2@j%,2@.%,2@(%,27` p 0 @ P  :3@:3MMM7A @>>>MMM---@M:27_o/?O_o/?O` p 0 @ P   P`p/?@AC`p@P#0#@#0#p@"0$@$$ $$$$$$$A:#0#`p@P#0ײ#7A_/?o@',2&3@"3@3@3@2_A?D3@=3@:3A7_O/??pO_/?z/z@ i(((A# # # # # W# @ # # # # A +J$ $ $ j@$ $ $ $ $ " !@" " " " " " \" Q" L" A +(  ^ @ Y : A +4 4 4 54 4 4 [ [  +*> *>!M%MAMRMrMA  * **^*9*#fff4f4ffA (&M?MA >%@*r*(*$*$*f.ff(lA (&sMqM$pM/oM5k>i>A gf*af*_fA ](\5GM9F>A E>%DCB@*0>*&=f%>*)*  f +&Mr>V@(MbM@Mr>@ *f"*@*.&y(Mf)fbf&(@ fL*+&f)@@*y&uMtMs*p(^j(@i*Rh* gf)efga*`5^*S5KfVI>HfRF&?fV@ ;f42*.5-CU7/r@.~bTB/'%!  +@+JKKSB7+KR8+KP[XYKcKb S# +QZ#BKKTB8+TXCX,FYYv??>9FD>9FD>9FD>9FD>9F`D>9F`DED+++++++++++++++++++++++++KSXY2KSXYK)S \XljEDkjEDYXhlERXlhDYYK;S \X>kEDMkEDYX\>ERX>\DYYK;S \X>ED>>EDYXOERXODYYKZS \XC>ED>>EDYXCERXCDYYKS \X*lED@lEDYXl*ERX*lDYYKS \X&&EDf&EDYX2&ERX&2DYYKQS \X&&ED(&EDYX&ERX&DYYKS \X&&ED5&EDYX(&ERX&(DYY++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++eB+++++++++++++++++++++++++++++++++++++++++++++++++++=vnXEe#E`#Ee`#E`vhb nvEe#E &`bch &aev#eDn#D =XEe#E &`bch &aeX#eD=#DXETXX@eD=@=E#aDYN8qEe#E`#Ee`#E`vhb 8Ee#E &`bch &ae#eD8#D NqEe#E &`bch &aeq#eDN#DqETXq@eDN@NE#aDYKRBKPXBYC\XBY +CX`!YBp>CX;!~ +Y #B #BCX-A-A +Y#B#BCX~;! +Y#B#BtEiDEiDssssttuuuu+++ss++++++EiDssst+EDssssstttEDsttttttuuuEDsuEDssttEDsssssttttuuuuuuuuuuu++tttttts+CXMMEiDM:2+Ys++uuu+++++ss+C\X3@ 3MMMA @>>>MMM---7+tttttts++Ysststststststu+sus+++s++++u+++s+++++t+s+sst+ss++++++s+s+++t+++sssss+ss+++s++++sts+s++++u++++++++u+++++s++++stu++sss+++u++s++++++++++stust+stu+++++++++++++++++++stu++++LLJk#j$$%$]E.X9K +(#KPJ 1F%O"NAGi@'LHQP.;L +#"sZPRn\Z[In~`5br(8 n+F@h33n5C4\]|(;H{7cx!z'4RFTm?N+En!I{nc(i~?2&5@EIWl&@[{w~(8/@FM~{/h   37x{#Xby!$]ehj /7?PW\@!$0MM]fz|/=JP_f|*D!a +3%?CZk"#',?WXw ""*6J\ +#Axy22%i54BHJWdqqrvwzz"#07C[`exxxx{-AGJP[]]_c'Eg&AHk2HI_c +6PT' M)[/td_|D= +a6;Pyd_UVCFJ]e7td %/B?v:7Q)?VN/4$4Z#/DtAPOQRIn{{j"% L}4mi jj88bZDmHm@@\Y+L@ HHd%2v1x + cB,4A8HXlYCp(7BPZdsx\,cAKU_s Ad*8t,@ + ,;DVcWd6P9ND$B"`9,N8iT=qAPO5G9P+WUs\rXD@BUT@?>=<;:987543210/.-,+*)('&%$#"!  + ,E#F` &`&#HH-,E#F#a &a&#HH-,E#F` a F`&#HH-,E#F#a ` &a a&#HH-,E#F`@a f`&#HH-,E#F#a@` &a@a&#HH-, <<-, E# D# ZQX# D#Y QX# MD#Y QX# D#Y!!-, EhD ` EFvhE`D-, +C#Ce +-, + C#C -,#p>#pE: -,E#DE#D-, E%EadPQXED!!Y-,Cc#b#B+-, EC`D-,CCe +-, i@a ,b`+ d#da\XaY-,E+#Dz-,E+#D-,CXE+#DzEi #D QX+#Dz!zYY-,-,%F`F@aH-,KS \XYXY-, %E#DE#DEe#E %`j #B#hj`a Ry!@E TX#!?#YaDRy@ E TX#!?#YaD-,C#C -,C#C -, C#C -, C#Ce -,C#Ce -,C#Ce -,KRXED!!Y-, %#I@` c RX#%8#%e8c8!!!!!Y-,KdQXEi C`:!!!Y-,%# `#-,%# a#-,%-, ` <<-, a <<-,++**-,CC -,>**-,5-,v#p E PXaY:/-,!! d#d@b-,!QX d#d b@/+Y`-,!QX d#dUb/+Y`-, d#d@b`#!-,&&&&Eh:-,&&&&Ehe:-,KS#KQZX E`D!!Y-,KTX E`D!!Y-,KS#KQZX8!!Y-,KTX8!!Y-,CXY-,CXY-,KTC\ZX8!!Y-,C\X %% d#dadQX%% F`H F`HY +!!!!Y-,C\X %% d#dadQX%% F`H F`HY +!!!!Y-,KS#KQZX:+!!Y-,KS#KQZX;+!!Y-,KS#KQZC\ZX8!!Y-, KT&KTZ +C\ZX8!!Y-,F#F`F# F`ab# #''pE` PXaFY`h:-,B#Q@SZX TXC`BY$QX @TXC`BY$TX C`BKKRXC`BY@TXC`BY@cTXC`BY@cTXC`BYYYY-,CTXKS#KQZX8!!Y!!!!Y-92Q.9P4\NsOOMsF9*9+LsFJKQ&i tF5E; +f i 8 Yd[qch5"_oA 5#5@  @Po+N]]pO jB+9%% c:)%% B-QWk$ +4 +4 +4 @Y +4  6Mkkyy  +&CA +/&hwv v7$Z@Z!jj _/p&&%n2+NCX@  + + +MM] +MMYN]]M??9910KSy2Cy@$ &%  &  +% +% % %+++++*++]q]q++++#&&#"3267#"$54$323267W',1?\ĜvrY\ /k*ĒvՙpuwkF\>$/3qL+ @\ +4# +4 +4+ +4ghg%)(gihc "" "#."5->0P},=+N]q>-,($(( $9,(%(%$-.!=++??<<<9/<<N"! ( _  }P##(Po}"=v+Nq]rLGz+:%% #O`iuR%%Zl$6"%Pk @3hh +  '''( + +4 +4 +4 +4 +4@d +4 +4 +4      +   ffxw vv V55  nX+NMCX@M@/ ?  M]N]qM]@M@/ ?  MN]qMY??10KSyQCy@<%&& %& %%%% % % +%%++<+<+++++*+++++]q]C\X9@ 99 99 9 9 9++++++++Y++++++++q]$! '&"32765&DȞ8aOzUgEXbd_ n .Lt2@  n4L$Ϲ 9 @\ 9O& "* "" "# "#..!!`/&&-  P `  }%&!=++N]r@aK59~-<--!-5- 5EJJI5Xf5 3>-1>P>c`>r:> +P>t;H;@9;23 +,(+,%+,)<3333=3M3}33333333 p33;!eu;Lz p &++,))f/;H2]//@)+4_o@4 ,,/,?,,D)C%%%/ ?       @@>>@>`>>@(P(?+7772@#%4o0@P`=8U1+N]q+Mqq+]]q]]r<]]??9+/]q+<9]qr9]qr9999<<<]+rCTX ]Y10Cy@28:# +%&!% &9& "#:7  +" 8;++++<+++++++rq]rq]q+q+++%#"&546%54&&#"#"&546632327#"&'32IPj5@(A*&I;?Urn + 2kDPZ +y;'!-2iOk^j7)+(1EL3HFqB*@!%JCK9GQ67.#N_#@m$ Y # +,*Wf gv v +r#%!& !H +2?@ 4%%%22?(+4@ 4$8+N+]++M]N]]q++Mr??q<999]q9q910]q]rq%#"547632#"&'&'&#"326@Bof{ؑE8;M 0"4TI7K1X|yƿZ9DNd?3M;+.OEL-z@# 7- #[+  _@y!&**@2?O/K5/;=4/@ 4P//p///&&@@2?0@P.8+N]q+M]N]r+++]q+M]]<?z4Ak*&O] 5@XK]ohey% +  +"@4Vfo"w"  * + ] F(& @ 2?@"4i2? 4@5/"?"_""" )22??&04@ 4!8+N]++++M]N]++q+M++<??<<]q10Cy@%++++*+]+q]q!3267#"'&54324&'&#"] ZEa) / >@?>@9>@&'4>@4>@40>>4>@@47 /@/14@ +1/E&H ?H@ + ? HHH:@2? ::2rr/@),B@ ?B@ ?/BBBBH!@ ?@ ?/@&402?4444@!&+,@2?,@()4,@#%4,o,,P,,,wM8U+]q+++<]q+]]++]]++]q<q+]++??<+<9/9+/CTX@!4+Y+]+++++CTX>%'4>4++Y99999999910]q]+!##"'"372#"&&5467&5467&&54632"32654'&"32654'&&;1oJr-=7>OqhU~ZE^jsx]J:PO6uonA053SJCF8L,߹ 9.@/K54FP..& ", ",$@"'$"#" $ ##$!@c#($,##",f"!! +  *@!A?@EG4@=5@C5@9:4@014@'(4@+-4@"$4@4@4 @.../.p..@>5.9:4.014.'(4.4.,.4.@4.*('@!A?'/''''7:4'/14'"$4'@ !4'-N+N++++]q+LQCdk5$$!et;!7E d"$$ kj4%*#l @?$" $#$#@464@  + @464   @)    *@?:5 #44@/-04{@P`/?N+]qr]]q+++++<]<]+?.Bl[@@ZZ@@[;c7$$"c"c7%+$L@?` $"$# $@# +* @? :5 -44 #4 @#4     {/?144-.4N+++]q]]q+++++<]<?.BLvc7$$"cb7%LuFN@G@ +7<F<F67$״"-$(@"A$<#"$##@'"$'ط#@;7$;@*#B$F##"-6!<2ff + +F<;(' +H@2?H@U"4HHHHH*@=5@+-4@!"*.-@=5-@+-4---@---777*BA@"4`AAAAA/AAAAGHS!N++N]qr+54&&#"!567654&'5MNZz!PPas-'>9++X-A'-/(!/5'>;'>{TCTVZPYl4$$'a}D!?? +e%$$#1R@$K +c?$$ lj4%F7+ 9 -@8K5P-%5F!+!+$@"&$!#" +$##$ @#'$+##!  +f_@V5y+ +  *@!A?@=5 @EG4@C5@9:4@564@014@+-4@'(4@"$4@4@4@--/-p--@>5-9:4-564-014-,.4-'(4-4-@4-*''&@!A?&/&&&&7:4&/14&@ $4&,N+N+++]q+'5+VJ'5';'>yNGeK9l5$$%cs> +stream + +  +endstream +endobj +18 0 obj (Mac OS X 10.6.8 Quartz PDFContext) +endobj +19 0 obj (Adil) +endobj +20 0 obj (al-Nashir al-Sahafi Version 4.5.0) +endobj +21 0 obj (D:20110812232909Z00'00') +endobj +22 0 obj +<< +/Creator (al-Nashir al-Sahafi Version 4.5.0) +/Author (Adil) +/Producer (Mac OS X 10.6.8 Quartz PDFContext) +/ModDate (D:20110812232909Z00'00') +/CreationDate (D:20110812232909Z00'00') +>> +endobj xref +0 23 +0000000000 65535 f +0000000015 00000 n +0000000138 00000 n +0000008807 00000 n +0000000221 00000 n +0000009026 00000 n +0000008889 00000 n +0000009214 00000 n +0000034643 00000 n +0000009163 00000 n +0000009448 00000 n +0000025429 00000 n +0000026363 00000 n +0000009685 00000 n +0000034877 00000 n +0000055787 00000 n +0000055953 00000 n +0000035127 00000 n +0000056184 00000 n +0000056237 00000 n +0000056261 00000 n +0000056314 00000 n +0000056357 00000 n +trailer + +<< +/Info 22 0 R +/Root 9 0 R +/Size 23 +/ID [<0c87b1b9a219e7aeea550c93d2d72c96> <0c87b1b9a219e7aeea550c93d2d72c96>] +>> +startxref +56561 +%%EOF diff --git a/test/pdfs/ecma262.pdf.link b/test/pdfs/ecma262.pdf.link new file mode 100644 index 000000000..9aae67530 --- /dev/null +++ b/test/pdfs/ecma262.pdf.link @@ -0,0 +1 @@ +http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf diff --git a/test/pdfs/hmm.pdf.link b/test/pdfs/hmm.pdf.link new file mode 100644 index 000000000..5ba2534d0 --- /dev/null +++ b/test/pdfs/hmm.pdf.link @@ -0,0 +1 @@ +http://speech.tifr.res.in/tutorials/hmmTutExamplesAlgo.pdf diff --git a/test/pdfs/intelisa.pdf.link b/test/pdfs/intelisa.pdf.link index 371cdf947..3ccbd1759 100644 --- a/test/pdfs/intelisa.pdf.link +++ b/test/pdfs/intelisa.pdf.link @@ -1 +1 @@ -http://www.intel.com/Assets/PDF/manual/253665.pdf \ No newline at end of file +http://www.intel.com/content/dam/doc/manual/64-ia-32-architectures-software-developer-vol-1-manual.pdf diff --git a/test/pdfs/jai.pdf.link b/test/pdfs/jai.pdf.link new file mode 100644 index 000000000..54e17dfc2 --- /dev/null +++ b/test/pdfs/jai.pdf.link @@ -0,0 +1 @@ +http://download.oracle.com/docs/cd/E19957-01/806-5413-10/806-5413-10.pdf diff --git a/test/pdfs/rotation.pdf b/test/pdfs/rotation.pdf new file mode 100644 index 000000000..807dafe5f Binary files /dev/null and b/test/pdfs/rotation.pdf differ diff --git a/test/resources/browser_manifests/browser_manifest.json.windows b/test/resources/browser_manifests/browser_manifest.json.windows new file mode 100644 index 000000000..a19727b05 --- /dev/null +++ b/test/resources/browser_manifests/browser_manifest.json.windows @@ -0,0 +1,10 @@ +[ + { + "name":"firefox", + "path":"C:/Program Files (x86)/Firefox/firefox.exe" + }, + { + "name":"aurora", + "path":"C:/Program Files (x86)/Aurora/firefox.exe" + } +] diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js index c92af9167..b7f9845f7 100644 --- a/test/resources/firefox/user.js +++ b/test/resources/firefox/user.js @@ -1,38 +1,40 @@ -user_pref("browser.console.showInPanel", true); -user_pref("browser.dom.window.dump.enabled", true); -user_pref("browser.firstrun.show.localepicker", false); -user_pref("browser.firstrun.show.uidiscovery", false); -user_pref("dom.allow_scripts_to_close_windows", true); -user_pref("dom.disable_open_during_load", false); -user_pref("dom.max_script_run_time", 0); // no slow script dialogs -user_pref("dom.max_chrome_script_run_time", 0); -user_pref("dom.popup_maximum", -1); -user_pref("dom.send_after_paint_to_content", true); -user_pref("dom.successive_dialog_time_limit", 0); -user_pref("security.warn_submit_insecure", false); -user_pref("browser.shell.checkDefaultBrowser", false); -user_pref("shell.checkDefaultClient", false); -user_pref("browser.warnOnQuit", false); -user_pref("accessibility.typeaheadfind.autostart", false); -user_pref("javascript.options.showInConsole", true); -user_pref("devtools.errorconsole.enabled", true); -user_pref("layout.debug.enable_data_xbl", true); -user_pref("browser.EULA.override", true); -user_pref("javascript.options.tracejit.content", true); -user_pref("javascript.options.methodjit.content", true); -user_pref("javascript.options.jitprofiling.content", true); -user_pref("javascript.options.methodjit_always", false); -user_pref("gfx.color_management.force_srgb", true); -user_pref("network.manage-offline-status", false); -user_pref("test.mousescroll", true); -user_pref("network.http.prompt-temp-redirect", false); -user_pref("media.cache_size", 100); -user_pref("security.warn_viewing_mixed", false); -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 -user_pref("browser.safebrowsing.enable", false); // prevent traffic to google servers -user_pref("toolkit.telemetry.prompted", true); // prevent telemetry banner -user_pref("toolkit.telemetry.enabled", false); +'use strict'; + +user_pref('browser.console.showInPanel', true); +user_pref('browser.dom.window.dump.enabled', true); +user_pref('browser.firstrun.show.localepicker', false); +user_pref('browser.firstrun.show.uidiscovery', false); +user_pref('dom.allow_scripts_to_close_windows', true); +user_pref('dom.disable_open_during_load', false); +user_pref('dom.max_script_run_time', 0); // no slow script dialogs +user_pref('dom.max_chrome_script_run_time', 0); +user_pref('dom.popup_maximum', -1); +user_pref('dom.send_after_paint_to_content', true); +user_pref('dom.successive_dialog_time_limit', 0); +user_pref('security.warn_submit_insecure', false); +user_pref('browser.shell.checkDefaultBrowser', false); +user_pref('shell.checkDefaultClient', false); +user_pref('browser.warnOnQuit', false); +user_pref('accessibility.typeaheadfind.autostart', false); +user_pref('javascript.options.showInConsole', true); +user_pref('devtools.errorconsole.enabled', true); +user_pref('layout.debug.enable_data_xbl', true); +user_pref('browser.EULA.override', true); +user_pref('javascript.options.tracejit.content', true); +user_pref('javascript.options.methodjit.content', true); +user_pref('javascript.options.jitprofiling.content', true); +user_pref('javascript.options.methodjit_always', false); +user_pref('gfx.color_management.force_srgb', true); +user_pref('network.manage-offline-status', false); +user_pref('test.mousescroll', true); +user_pref('network.http.prompt-temp-redirect', false); +user_pref('media.cache_size', 100); +user_pref('security.warn_viewing_mixed', false); +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 +user_pref('browser.safebrowsing.enable', false); // prevent traffic to google servers +user_pref('toolkit.telemetry.prompted', true); // prevent telemetry banner +user_pref('toolkit.telemetry.enabled', false); diff --git a/test/test.py b/test/test.py index b61ba816b..c76df3fd4 100644 --- a/test/test.py +++ b/test/test.py @@ -469,7 +469,7 @@ def maybeUpdateRefImages(options, browser): else: print ' Yes! The references in tmp/ can be synced with ref/.' if options.reftest: - startReftest(browser) + startReftest(browser, options) if not prompt('Would you like to update the master copy in ref/?'): print ' OK, not updating.' else: diff --git a/test/test_manifest.json b/test/test_manifest.json index c5c196397..90a9f8a77 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -64,10 +64,38 @@ "rounds": 1, "type": "load" }, + { "id": "complexttffont-pdf", + "file": "pdfs/complex_ttf_font.pdf", + "rounds": 1, + "type": "load" + }, { "id": "i9-pdf", "file": "pdfs/i9.pdf", "link": true, "rounds": 1, "type": "eq" + }, + { "id": "hmm-pdf", + "file": "pdfs/hmm.pdf", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "rotation", + "file": "pdfs/rotation.pdf", + "rounds": 1, + "type": "load" + }, + { "id": "ecma262-pdf", + "file": "pdfs/ecma262.pdf", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "jai-pdf", + "file": "pdfs/jai.pdf", + "link": true, + "rounds": 1, + "type": "load" } ] diff --git a/web/compatibility.js b/web/compatibility.js index 5687d30ce..d286692b1 100644 --- a/web/compatibility.js +++ b/web/compatibility.js @@ -40,6 +40,7 @@ // so we can use the TypedArray as well window.Uint32Array = TypedArray; window.Int32Array = TypedArray; + window.Uint16Array = TypedArray; })(); // Object.create() ? diff --git a/web/index.html.template b/web/index.html.template index c3086f078..12e606371 100644 --- a/web/index.html.template +++ b/web/index.html.template @@ -50,7 +50,7 @@

Try it out!

-

Live demo lives here.

+

Live demo lives here.

Authors

Vivien Nicolas (21@vingtetun.org) diff --git a/web/viewer.js b/web/viewer.js index eae5b383e..f0ecd4667 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -38,21 +38,20 @@ var PDFView = { set page(val) { var pages = this.pages; - if (val <= 0 || val == this.page || val > pages.length) { - // TODO If the hash if set to a dumb value, like #123456, the input field - // of the UI will be set to it even if no page is changed because its out - // of bound. - val = this.page || 1; - } else { - // Draw the page before jumping to it in order to avoid seeing the - // possible gap between pages if the page has never been draw before. - pages[val - 1].draw(); - document.location.hash = val; + var input = document.getElementById('pageNumber'); + if (val <= 0 || val > pages.length) { + input.value = this.page; + return; } - - var event = document.createEvent("UIEvents"); - event.initUIEvent("pagechange", false, false, window, val); - window.dispatchEvent(event); + + document.location.hash = val; + document.getElementById('previous').disabled = (val == 1); + document.getElementById('next').disabled = (val == pages.length); + if (input.value == val) + return; + + input.value = val; + pages[val - 1].draw(); }, get page() { @@ -60,7 +59,7 @@ var PDFView = { }, open: function(url, scale) { - if (url.indexOf("http") == 0) + if (url.indexOf('http') == 0) return; document.title = url; @@ -82,6 +81,19 @@ var PDFView = { xhr.send(null); }, + navigateTo: function(dest) { + if (typeof dest === 'string') + dest = this.destinations[dest]; + // dest array looks like that: + var destRef = dest[0]; + var pageNumber = this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R']; + if (pageNumber) { + this.page = pageNumber; + // TODO scroll to specific region on the page, the precise scaling + // required. + } + }, + load: function(data, scale) { var sidebar = document.getElementById('sidebarView'); sidebar.parentNode.scrollTop = 0; @@ -99,18 +111,21 @@ var PDFView = { document.getElementById('numPages').innerHTML = pagesCount; var pages = this.pages = []; + var pagesRefMap = {}; var thumbnails = this.thumbnails = []; for (var i = 1; i <= pagesCount; i++) { var page = pdf.getPage(i); - var mediaBox = page.mediaBox; - var width = (mediaBox[2] - mediaBox[0]); - var height = (mediaBox[3] - mediaBox[1]); - pages.push(new PageView(container, page, i, width, height, page.stats)); + pages.push(new PageView(container, page, i, page.width, page.height, + page.stats, this.navigateTo.bind(this))); thumbnails.push(new ThumbnailView(sidebar, pages[i - 1])); - }; + var pageRef = page.ref; + pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i; + } this.scale = (scale || kDefaultScale); this.page = parseInt(document.location.hash.substring(1)) || 1; + this.pagesRefMap = pagesRefMap; + this.destinations = pdf.catalog.destinations; }, getVisiblePages: function() { @@ -137,10 +152,11 @@ var PDFView = { } return visiblePages; - }, + } }; -var PageView = function(container, content, id, width, height, stats) { +var PageView = function(container, content, id, width, height, + stats, navigateTo) { this.width = width; this.height = height; this.id = id; @@ -158,13 +174,50 @@ var PageView = function(container, content, id, width, height, stats) { this.update = function(scale) { this.scale = scale || this.scale; - div.style.width = (this.width * this.scale)+ 'px'; + div.style.width = (this.width * this.scale) + 'px'; div.style.height = (this.height * this.scale) + 'px'; while (div.hasChildNodes()) div.removeChild(div.lastChild); }; + function setupLinks(canvas, content, scale) { + var links = content.getLinks(); + var currentLink = null; + if (links.length > 0) { + canvas.addEventListener('mousemove', function(e) { + var x = e.pageX; + var y = e.pageY; + for (var p = canvas; p; p = p.offsetParent) { + x -= p.offsetLeft; + y -= p.offsetTop; + } + x /= scale; + y /= scale; + var i, n = links.length; + for (i = 0; i < n; i++) { + var link = links[i]; + if (link.x <= x && link.y <= y && + x < link.x + link.width && y < link.y + link.height) { + currentLink = link; + canvas.style.cursor = 'pointer'; + return; + } + } + currentLink = null; + canvas.style.cursor = 'default'; + }, false); + canvas.addEventListener('mousedown', function(e) { + if (!currentLink) + return; + if (currentLink.url) + window.location.href = currentLink.url; + if (currentLink.dest) + navigateTo(currentLink.dest); + }, false); + } + } + this.draw = function() { if (div.hasChildNodes()) { this.updateStats(); @@ -188,12 +241,14 @@ var PageView = function(container, content, id, width, height, stats) { stats.begin = Date.now(); this.content.startRendering(ctx, this.updateStats); + setupLinks(canvas, this.content, this.scale); + return true; }; this.updateStats = function() { var t1 = stats.compile, t2 = stats.fonts, t3 = stats.render; - var str = 'Time to compile/fonts/render: ' + + var str = 'Time to compile/fonts/render: ' + (t1 - stats.begin) + '/' + (t2 - t1) + '/' + (t3 - t2) + ' ms'; document.getElementById('info').innerHTML = str; }; @@ -271,11 +326,11 @@ window.addEventListener('scroll', function onscroll(evt) { PDFView.page = firstPage.id; }, true); -window.addEventListener("hashchange", function(evt) { +window.addEventListener('hashchange', function(evt) { PDFView.page = PDFView.page; }); -window.addEventListener("change", function(evt) { +window.addEventListener('change', function(evt) { var files = evt.target.files; if (!files || files.length == 0) return; @@ -301,7 +356,7 @@ window.addEventListener("change", function(evt) { document.location.hash = 1; }, true); -window.addEventListener("transitionend", function(evt) { +window.addEventListener('transitionend', function(evt) { var pageIndex = 0; var pagesCount = PDFView.pages.length; @@ -309,7 +364,7 @@ window.addEventListener("transitionend", function(evt) { container._interval = window.setInterval(function() { if (pageIndex >= pagesCount) return window.clearInterval(container._interval); - + PDFView.thumbnails[pageIndex++].draw(); }, 500); }, true);