diff --git a/images/buttons.png b/images/buttons.png index 682212660..3357b47d6 100644 Binary files a/images/buttons.png and b/images/buttons.png differ diff --git a/images/source/FileButton.psd.zip b/images/source/FileButton.psd.zip new file mode 100644 index 000000000..1f2b51cee Binary files /dev/null and b/images/source/FileButton.psd.zip differ diff --git a/multi-page-viewer.css b/multi-page-viewer.css index f9a7837b1..7f4701022 100644 --- a/multi-page-viewer.css +++ b/multi-page-viewer.css @@ -84,7 +84,7 @@ span { background-color: #eee; border-bottom: 1px solid #666; padding: 4px 0px 0px 8px; - position:fixed; + position: fixed; left: 0px; top: 0px; height: 40px; @@ -136,10 +136,61 @@ span { background: url('images/buttons.png') no-repeat -28px 0px; } +#openFileButton { + background: url('images/buttons.png') no-repeat -56px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px 0px 0px 3px; + width: 29px; + height: 23px; +} + +#openFileButton.down { + background: url('images/buttons.png') no-repeat -56px -46px; +} + +#openFileButton.disabled { + background: url('images/buttons.png') no-repeat -56px 0px; +} + +#fileInput { + display: none; +} + #pageNumber { text-align: right; } +#sidebar { + background-color: rgba(0, 0, 0, 0.8); + position: fixed; + width: 150px; + top: 62px; + bottom: 18px; + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + -moz-border-radius-topright: 8px; + -moz-border-radius-bottomright: 8px; + -webkit-border-top-right-radius: 8px; + -webkit-border-bottom-right-radius: 8px; +} + +#sidebarScrollView { + position: absolute; + overflow: hidden; + overflow-y: auto; + top: 40px; + right: 10px; + bottom: 10px; + left: 10px; +} + +#sidebarContentView { + height: auto; + width: 100px; +} + #viewer { margin: 44px 0px 0px; padding: 8px 0px; diff --git a/multi-page-viewer.html b/multi-page-viewer.html index 4e15cf4f8..ffbdfe707 100644 --- a/multi-page-viewer.html +++ b/multi-page-viewer.html @@ -33,7 +33,19 @@ Zoom + + + + Open File + +
diff --git a/multi-page-viewer.js b/multi-page-viewer.js index 9d9cec702..baad7809e 100644 --- a/multi-page-viewer.js +++ b/multi-page-viewer.js @@ -12,6 +12,7 @@ var PDFViewer = { nextPageButton: null, pageNumberInput: null, scaleSelect: null, + fileInput: null, willJumpToPage: false, @@ -215,7 +216,7 @@ var PDFViewer = { } }, - open: function(url) { + openURL: function(url) { PDFViewer.url = url; document.title = url; @@ -231,26 +232,35 @@ var PDFViewer = { req.responseArrayBuffer || req.response; - PDFViewer.pdf = new PDFDoc(new Stream(data)); - PDFViewer.numberOfPages = PDFViewer.pdf.numPages; - document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); - - for (var i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - } - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; + PDFViewer.readPDF(data); } }; req.send(null); + }, + + readPDF: function(data) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } + + PDFViewer.pdf = new PDFDoc(new Stream(data)); + PDFViewer.numberOfPages = PDFViewer.pdf.numPages; + document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); + + for (var i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + document.location.hash = 1; + } + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; } }; @@ -354,11 +364,63 @@ window.onload = function() { PDFViewer.scaleSelect.onchange = function(evt) { PDFViewer.changeScale(parseInt(this.value)); }; + + if (window.File && window.FileReader && window.FileList && window.Blob) { + var openFileButton = document.getElementById('openFileButton'); + openFileButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.fileInput.click(); + } + }; + openFileButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + openFileButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + openFileButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.fileInput = document.getElementById('fileInput'); + PDFViewer.fileInput.onchange = function(evt) { + var files = evt.target.files; + + if (files.length > 0) { + var file = files[0]; + var fileReader = new FileReader(); + + document.title = file.name; + + // Read the local file into a Uint8Array. + fileReader.onload = function(evt) { + var data = evt.target.result; + var buffer = new ArrayBuffer(data.length); + var uint8Array = new Uint8Array(buffer); + + for (var i = 0; i < data.length; i++) { + uint8Array[i] = data.charCodeAt(i); + } + + PDFViewer.readPDF(uint8Array); + }; + + // Read as a binary string since "readAsArrayBuffer" is not yet + // implemented in Firefox. + fileReader.readAsBinaryString(file); + } + }; + PDFViewer.fileInput.value = null; + } else { + document.getElementById('fileWrapper').style.display = 'none'; + } PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; - PDFViewer.open(PDFViewer.queryParams.file || PDFViewer.url); + PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); window.onscroll = function(evt) { var lastPagesDrawn = PDFViewer.lastPagesDrawn; diff --git a/pdf.js b/pdf.js index c186f0cdf..e955c25ea 100644 --- a/pdf.js +++ b/pdf.js @@ -65,45 +65,46 @@ var Stream = (function() { this.dict = dict; } + // required methods for a stream. if a particular stream does not + // implement these, an error should be thrown constructor.prototype = { get length() { return this.end - this.start; }, getByte: function() { - var bytes = this.bytes; if (this.pos >= this.end) - return -1; - return bytes[this.pos++]; + return; + return this.bytes[this.pos++]; }, // returns subarray of original buffer // should only be read getBytes: function(length) { var bytes = this.bytes; var pos = this.pos; + var strEnd = this.end; + + if (!length) + return bytes.subarray(pos, strEnd); var end = pos + length; - var strEnd = this.end; - if (!end || end > strEnd) + if (end > strEnd) end = strEnd; this.pos = end; return bytes.subarray(pos, end); }, lookChar: function() { - var bytes = this.bytes; if (this.pos >= this.end) return; - return String.fromCharCode(bytes[this.pos]); + return String.fromCharCode(this.bytes[this.pos]); }, getChar: function() { - var ch = this.lookChar(); - if (!ch) - return ch; - this.pos++; - return ch; + if (this.pos >= this.end) + return; + return String.fromCharCode(this.bytes[this.pos++]); }, skip: function(n) { - if (!n && !IsNum(n)) + if (!n) n = 1; this.pos += n; }, @@ -135,6 +136,84 @@ var StringStream = (function() { return constructor; })(); +// super class for the decoding streams +var DecodeStream = (function() { + function constructor() { + this.pos = 0; + this.bufferLength = 0; + this.eof = false; + this.buffer = null; + } + + constructor.prototype = { + ensureBuffer: function(requested) { + var buffer = this.buffer; + var current = buffer ? buffer.byteLength : 0; + if (requested < current) + return buffer; + var size = 512; + while (size < requested) + size <<= 1; + var buffer2 = Uint8Array(size); + for (var i = 0; i < current; ++i) + buffer2[i] = buffer[i]; + return this.buffer = buffer2; + }, + getByte: function() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return; + this.readBlock(); + } + return this.buffer[this.pos++]; + }, + getBytes: function(length) { + var pos = this.pos; + + this.ensureBuffer(pos + length); + while (!this.eof && this.bufferLength < pos + length) + this.readBlock(); + + var end = pos + length; + var bufEnd = this.bufferLength; + + if (end > bufEnd) + end = bufEnd; + + this.pos = end; + return this.buffer.subarray(pos, end) + }, + lookChar: function() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return; + this.readBlock(); + } + return String.fromCharCode(this.buffer[this.pos]); + }, + getChar: function() { + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return; + this.readBlock(); + } + return String.fromCharCode(this.buffer[this.pos++]); + }, + skip: function(n) { + if (!n) + n = 1; + this.pos += n; + } + }; + + return constructor; +})(); + + + var FlateStream = (function() { const codeLenCodeMap = Uint32Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 @@ -259,273 +338,393 @@ var FlateStream = (function() { this.bytes = bytes; this.bytesPos = bytesPos; - this.eof = false; + this.codeSize = 0; this.codeBuf = 0; - - this.pos = 0; - this.bufferLength = 0; + + DecodeStream.call(this); } - constructor.prototype = { - getBits: function(bits) { - var codeSize = this.codeSize; - var codeBuf = this.codeBuf; - var bytes = this.bytes; - var bytesPos = this.bytesPos; + constructor.prototype = Object.create(DecodeStream.prototype); - var b; - while (codeSize < bits) { - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad encoding in flate stream"); - codeBuf |= b << codeSize; - codeSize += 8; - } - b = codeBuf & ((1 << bits) - 1); - this.codeBuf = codeBuf >> bits; - this.codeSize = codeSize -= bits; - this.bytesPos = bytesPos; - return b; - }, - getCode: function(table) { - var codes = table[0]; - var maxLen = table[1]; - var codeSize = this.codeSize; - var codeBuf = this.codeBuf; - var bytes = this.bytes; - var bytesPos = this.bytesPos; + constructor.prototype.getBits = function(bits) { + var codeSize = this.codeSize; + var codeBuf = this.codeBuf; + var bytes = this.bytes; + var bytesPos = this.bytesPos; - while (codeSize < maxLen) { - var b; - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad encoding in flate stream"); - codeBuf |= (b << codeSize); - codeSize += 8; - } - var code = codes[codeBuf & ((1 << maxLen) - 1)]; - var codeLen = code >> 16; - var codeVal = code & 0xffff; - if (codeSize == 0|| codeSize < codeLen || codeLen == 0) + var b; + while (codeSize < bits) { + if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad encoding in flate stream"); - this.codeBuf = (codeBuf >> codeLen); - this.codeSize = (codeSize - codeLen); - this.bytesPos = bytesPos; - return codeVal; - }, - ensureBuffer: function(requested) { - var buffer = this.buffer; - var current = buffer ? buffer.byteLength : 0; - if (requested < current) - return buffer; - var size = 512; - while (size < requested) - size <<= 1; - var buffer2 = Uint8Array(size); - for (var i = 0; i < current; ++i) - buffer2[i] = buffer[i]; - return this.buffer = buffer2; - }, - getByte: function() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return; - this.readBlock(); - } - return this.buffer[this.pos++]; - }, - getBytes: function(length) { - var pos = this.pos; - - while (!this.eof && this.bufferLength < pos + length) - this.readBlock(); - - var end = pos + length; - var bufEnd = this.bufferLength; - - if (end > bufEnd) - end = bufEnd; - - this.pos = end; - return this.buffer.subarray(pos, end) - }, - lookChar: function() { - var pos = this.pos; - while (this.bufferLength <= pos) { - if (this.eof) - return; - this.readBlock(); - } - return String.fromCharCode(this.buffer[pos]); - }, - getChar: function() { - var ch = this.lookChar(); - // shouldnt matter what the position is if we get past the eof - // so no need to check if ch is undefined - this.pos++; - return ch; - }, - skip: function(n) { - if (!n) - n = 1; - this.pos += n; - }, - generateHuffmanTable: function(lengths) { - var n = lengths.length; - - // find max code length - var maxLen = 0; - for (var i = 0; i < n; ++i) { - if (lengths[i] > maxLen) - maxLen = lengths[i]; - } - - // build the table - var size = 1 << maxLen; - var codes = Uint32Array(size); - for (var len = 1, code = 0, skip = 2; - len <= maxLen; - ++len, code <<= 1, skip <<= 1) { - for (var val = 0; val < n; ++val) { - if (lengths[val] == len) { - // bit-reverse the code - var code2 = 0; - var t = code; - for (var i = 0; i < len; ++i) { - code2 = (code2 << 1) | (t & 1); - t >>= 1; - } - - // fill the table entries - for (var i = code2; i < size; i += skip) - codes[i] = (len << 16) | val; - - ++code; - } - } - } - - return [codes, maxLen]; - }, - readBlock: function() { - function repeat(stream, array, len, offset, what) { - var repeat = stream.getBits(len) + offset; - while (repeat-- > 0) - array[i++] = what; - } - - var bytes = this.bytes; - var bytesPos = this.bytesPos; - - // read block header - var hdr = this.getBits(3); - if (hdr & 1) - this.eof = true; - hdr >>= 1; + codeBuf |= b << codeSize; + codeSize += 8; + } + b = codeBuf & ((1 << bits) - 1); + this.codeBuf = codeBuf >> bits; + this.codeSize = codeSize -= bits; + this.bytesPos = bytesPos; + return b; + }; + constructor.prototype.getCode = function(table) { + var codes = table[0]; + var maxLen = table[1]; + var codeSize = this.codeSize; + var codeBuf = this.codeBuf; + var bytes = this.bytes; + var bytesPos = this.bytesPos; + while (codeSize < maxLen) { var b; - if (hdr == 0) { // uncompressed block - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad block header in flate stream"); - var blockLen = b; - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad block header in flate stream"); - blockLen |= (b << 8); - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad block header in flate stream"); - var check = b; - if (typeof (b = bytes[bytesPos++]) == "undefined") - error("Bad block header in flate stream"); - check |= (b << 8); - if (check != (~this.blockLen & 0xffff)) - error("Bad uncompressed block length in flate stream"); - var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + blockLen); - this.bufferLength = bufferLength + blockLen; - for (var n = bufferLength; n < blockLen; ++n) { - if (typeof (b = bytes[bytesPos++]) == "undefined") { - this.eof = true; - break; + if (typeof (b = bytes[bytesPos++]) == "undefined") + error("Bad encoding in flate stream"); + codeBuf |= (b << codeSize); + codeSize += 8; + } + var code = codes[codeBuf & ((1 << maxLen) - 1)]; + var codeLen = code >> 16; + var codeVal = code & 0xffff; + if (codeSize == 0|| codeSize < codeLen || codeLen == 0) + error("Bad encoding in flate stream"); + this.codeBuf = (codeBuf >> codeLen); + this.codeSize = (codeSize - codeLen); + this.bytesPos = bytesPos; + return codeVal; + }; + constructor.prototype.generateHuffmanTable = function(lengths) { + var n = lengths.length; + + // find max code length + var maxLen = 0; + for (var i = 0; i < n; ++i) { + if (lengths[i] > maxLen) + maxLen = lengths[i]; + } + + // build the table + var size = 1 << maxLen; + var codes = Uint32Array(size); + for (var len = 1, code = 0, skip = 2; + len <= maxLen; + ++len, code <<= 1, skip <<= 1) { + for (var val = 0; val < n; ++val) { + if (lengths[val] == len) { + // bit-reverse the code + var code2 = 0; + var t = code; + for (var i = 0; i < len; ++i) { + code2 = (code2 << 1) | (t & 1); + t >>= 1; } - buffer[n] = b; + + // fill the table entries + for (var i = code2; i < size; i += skip) + codes[i] = (len << 16) | val; + + ++code; } + } + } + + return [codes, maxLen]; + }; + constructor.prototype.readBlock = function() { + function repeat(stream, array, len, offset, what) { + var repeat = stream.getBits(len) + offset; + while (repeat-- > 0) + array[i++] = what; + } + + var bytes = this.bytes; + var bytesPos = this.bytesPos; + + // read block header + var hdr = this.getBits(3); + if (hdr & 1) + this.eof = true; + hdr >>= 1; + + var b; + if (hdr == 0) { // uncompressed block + if (typeof (b = bytes[bytesPos++]) == "undefined") + error("Bad block header in flate stream"); + var blockLen = b; + if (typeof (b = bytes[bytesPos++]) == "undefined") + error("Bad block header in flate stream"); + blockLen |= (b << 8); + if (typeof (b = bytes[bytesPos++]) == "undefined") + error("Bad block header in flate stream"); + var check = b; + if (typeof (b = bytes[bytesPos++]) == "undefined") + error("Bad block header in flate stream"); + check |= (b << 8); + if (check != (~this.blockLen & 0xffff)) + error("Bad uncompressed block length in flate stream"); + var bufferLength = this.bufferLength; + var buffer = this.ensureBuffer(bufferLength + blockLen); + this.bufferLength = bufferLength + blockLen; + for (var n = bufferLength; n < blockLen; ++n) { + if (typeof (b = bytes[bytesPos++]) == "undefined") { + this.eof = true; + break; + } + buffer[n] = b; + } + return; + } + + var litCodeTable; + var distCodeTable; + if (hdr == 1) { // compressed block, fixed codes + litCodeTable = fixedLitCodeTab; + distCodeTable = fixedDistCodeTab; + } else if (hdr == 2) { // compressed block, dynamic codes + var numLitCodes = this.getBits(5) + 257; + var numDistCodes = this.getBits(5) + 1; + var numCodeLenCodes = this.getBits(4) + 4; + + // build the code lengths code table + var codeLenCodeLengths = Array(codeLenCodeMap.length); + var i = 0; + while (i < numCodeLenCodes) + codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3); + var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); + + // build the literal and distance code tables + var len = 0; + var i = 0; + var codes = numLitCodes + numDistCodes; + var codeLengths = new Array(codes); + while (i < codes) { + var code = this.getCode(codeLenCodeTab); + if (code == 16) { + repeat(this, codeLengths, 2, 3, len); + } else if (code == 17) { + repeat(this, codeLengths, 3, 3, len = 0); + } else if (code == 18) { + repeat(this, codeLengths, 7, 11, len = 0); + } else { + codeLengths[i++] = len = code; + } + } + + litCodeTable = + this.generateHuffmanTable(codeLengths.slice(0, numLitCodes)); + distCodeTable = + this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes)); + } else { + error("Unknown block type in flate stream"); + } + + var pos = this.bufferLength; + while (true) { + var code1 = this.getCode(litCodeTable); + if (code1 == 256) { + this.bufferLength = pos; return; } - - var litCodeTable; - var distCodeTable; - if (hdr == 1) { // compressed block, fixed codes - litCodeTable = fixedLitCodeTab; - distCodeTable = fixedDistCodeTab; - } else if (hdr == 2) { // compressed block, dynamic codes - var numLitCodes = this.getBits(5) + 257; - var numDistCodes = this.getBits(5) + 1; - var numCodeLenCodes = this.getBits(4) + 4; - - // build the code lengths code table - var codeLenCodeLengths = Array(codeLenCodeMap.length); - var i = 0; - while (i < numCodeLenCodes) - codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3); - var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths); - - // build the literal and distance code tables - var len = 0; - var i = 0; - var codes = numLitCodes + numDistCodes; - var codeLengths = new Array(codes); - while (i < codes) { - var code = this.getCode(codeLenCodeTab); - if (code == 16) { - repeat(this, codeLengths, 2, 3, len); - } else if (code == 17) { - repeat(this, codeLengths, 3, 3, len = 0); - } else if (code == 18) { - repeat(this, codeLengths, 7, 11, len = 0); - } else { - codeLengths[i++] = len = code; - } - } - - litCodeTable = this.generateHuffmanTable(codeLengths.slice(0, numLitCodes)); - distCodeTable = this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes)); + if (code1 < 256) { + var buffer = this.ensureBuffer(pos + 1); + buffer[pos++] = code1; } else { - error("Unknown block type in flate stream"); - } - - var pos = this.bufferLength; - while (true) { - var code1 = this.getCode(litCodeTable); - if (code1 == 256) { - this.bufferLength = pos; - return; - } - if (code1 < 256) { - var buffer = this.ensureBuffer(pos + 1); - buffer[pos++] = code1; - } else { - code1 -= 257; - code1 = lengthDecode[code1]; - var code2 = code1 >> 16; - if (code2 > 0) - code2 = this.getBits(code2); - var len = (code1 & 0xffff) + code2; - code1 = this.getCode(distCodeTable); - code1 = distDecode[code1]; - code2 = code1 >> 16; - if (code2 > 0) - code2 = this.getBits(code2); - var dist = (code1 & 0xffff) + code2; - var buffer = this.ensureBuffer(pos + len); - for (var k = 0; k < len; ++k, ++pos) - buffer[pos] = buffer[pos - dist]; - } + code1 -= 257; + code1 = lengthDecode[code1]; + var code2 = code1 >> 16; + if (code2 > 0) + code2 = this.getBits(code2); + var len = (code1 & 0xffff) + code2; + code1 = this.getCode(distCodeTable); + code1 = distDecode[code1]; + code2 = code1 >> 16; + if (code2 > 0) + code2 = this.getBits(code2); + var dist = (code1 & 0xffff) + code2; + var buffer = this.ensureBuffer(pos + len); + for (var k = 0; k < len; ++k, ++pos) + buffer[pos] = buffer[pos - dist]; } } }; return constructor; })(); + +var PredictorStream = (function() { + function constructor(stream, params) { + var predictor = this.predictor = params.get("Predictor") || 1; + + if (predictor <= 1) + return stream; // no prediction + if (predictor !== 2 && (predictor < 10 || predictor > 15)) + error("Unsupported predictor"); + + if (predictor === 2) + this.readBlock = this.readBlockTiff; + else + this.readBlock = this.readBlockPng; + + 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; + + var pixBytes = this.pixBytes = (colors * bits + 7) >> 3; + // add an extra pixByte to represent the pixel left of column 0 + var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3; + + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + + constructor.prototype.readBlockTiff = function() { + var buffer = this.buffer; + var pos = this.pos; + + var rowBytes = this.rowBytes; + var pixBytes = this.pixBytes; + + + var buffer = this.buffer; + var bufferLength = this.bufferLength; + this.ensureBuffer(bufferLength + rowBytes); + var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); + + var bits = this.bits; + var colors = this.colors; + + var rawBytes = this.stream.getBytes(rowBytes); + + if (bits === 1) { + var inbuf = 0; + for (var i = 0; i < rowBytes; ++i) { + var c = rawBytes[i]; + inBuf = (inBuf << 8) | c; + // bitwise addition is exclusive or + // first shift inBuf and then add + currentRow[i] = (c ^ (inBuf >> colors)) & 0xFF; + // truncate inBuf (assumes colors < 16) + inBuf &= 0xFFFF; + } + } else if (bits === 8) { + for (var i = 0; i < colors; ++i) + currentRow[i] = rawBytes[i]; + for (; i < rowBytes; ++i) + currentRow[i] = currentRow[i - colors] + rawBytes[i]; + } else { + var compArray = new Uint8Array(colors + 1); + var bitMask = (1 << bits) - 1; + var inbuf = 0, outbut = 0; + var inbits = 0, outbits = 0; + var j = 0, k = 0; + var columns = this.columns; + for (var i = 0; i < columns; ++i) { + for (var kk = 0; kk < colors; ++kk) { + if (inbits < bits) { + inbuf = (inbuf << 8) | (rawBytes[j++] & 0xFF); + inbits += 8; + } + compArray[kk] = (compArray[kk] + + (inbuf >> (inbits - bits))) & bitMask; + inbits -= bits; + outbuf = (outbuf << bits) | compArray[kk]; + outbits += bits; + if (outbits >= 8) { + currentRow[k++] = (outbuf >> (outbits - 8)) & 0xFF; + outbits -= 8; + } + } + } + if (outbits > 0) { + currentRow[k++] = (outbuf << (8 - outbits)) + + (inbuf & ((1 << (8 - outbits)) - 1)) + } + } + this.bufferLength += rowBytes; + }; + constructor.prototype.readBlockPng = function() { + var buffer = this.buffer; + var pos = this.pos; + + var rowBytes = this.rowBytes; + var pixBytes = this.pixBytes; + + var predictor = this.stream.getByte(); + var rawBytes = this.stream.getBytes(rowBytes); + + var buffer = this.buffer; + var bufferLength = this.bufferLength; + this.ensureBuffer(bufferLength + pixBytes); + + var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); + var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); + if (prevRow.length == 0) + prevRow = currentRow; + + switch (predictor) { + case 0: + break; + case 1: + for (var i = 0; i < pixBytes; ++i) + currentRow[i] = rawBytes[i]; + for (; i < rowBytes; ++i) + currentRow[i] = (currentRow[i - pixBytes] + rawBytes[i]) & 0xFF; + break; + case 2: + for (var i = 0; i < rowBytes; ++i) + currentRow[i] = (prevRow[i] + rawBytes[i]) & 0xFF; + break; + case 3: + for (var i = 0; i < pixBytes; ++i) + currentRow[i] = (prevRow[i] >> 1) + rawBytes[i]; + for (; i < rowBytes; ++i) + currentRow[i] = (((prevRow[i] + currentRow[i - pixBytes]) + >> 1) + rawBytes[i]) & 0xFF; + break; + case 4: + // we need to save the up left pixels values. the simplest way + // is to create a new buffer + for (var i = 0; i < pixBytes; ++i) + currentRow[i] = rawBytes[i]; + for (; i < rowBytes; ++i) { + var up = prevRow[i]; + var upLeft = lastRow[i - pixBytes]; + var left = currentRow[i - pixBytes]; + var p = left + up - upLeft; + + var pa = p - left; + if (pa < 0) + pa = -pa; + var pb = p - up; + if (pb < 0) + pb = -pb; + var pc = p - upLeft; + if (pc < 0) + pc = -pc; + + var c = rawBytes[i]; + if (pa <= pb && pa <= pc) + currentRow[i] = left + c; + else if (pb <= pc) + currentRow[i] = up + c; + else + currentRow[i] = upLeft + c; + break; + } + default: + error("Unsupported predictor"); + break; + } + this.bufferLength += rowBytes; + }; + + return constructor; +})(); + // A JpegStream can't be read directly. We use the platform to render the underlying // JPEG data for us. var JpegStream = (function() { @@ -550,196 +749,6 @@ var JpegStream = (function() { return constructor; })(); - -var PredictorStream = (function() { - function constructor(stream, params) { - var predictor = this.predictor = params.get("Predictor") || 1; - - if (predictor <= 1) - return stream; // no prediction - if (predictor !== 2 && (predictor < 10 || predictor > 15)) - error("Unsupported predictor"); - - if (predictor === 2) - this.readRow = this.readRowTiff; - else - this.readRow = this.readRowPng; - - 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; - - var pixBytes = this.pixBytes = (colors * bits + 7) >> 3; - // add an extra pixByte to represent the pixel left of column 0 - var rowBytes = this.rowBytes = ((columns * colors * bits + 7) >> 3) + pixBytes; - - this.currentRow = new Uint8Array(rowBytes); - this.bufferLength = rowBytes; - this.pos = rowBytes; - } - - constructor.prototype = { - readRowTiff : function() { - var currentRow = this.currentRow; - var rowBytes = this.rowBytes; - var pixBytes = this.pixBytes; - var bits = this.bits; - var colors = this.colors; - - var rawBytes = this.stream.getBytes(rowBytes - pixBytes); - - if (bits === 1) { - var inbuf = 0; - for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j) { - var c = rawBytes[j]; - inBuf = (inBuf << 8) | c; - // bitwise addition is exclusive or - // first shift inBuf and then add - currentRow[i] = (c ^ (inBuf >> colors)) & 0xFF; - // truncate inBuf (assumes colors < 16) - inBuf &= 0xFFFF; - } - } else if (bits === 8) { - for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j) - currentRow[i] = currentRow[i - colors] + rawBytes[j]; - } else { - var compArray = new Uint8Array(colors + 1); - var bitMask = (1 << bits) - 1; - var inbuf = 0, outbut = 0; - var inbits = 0, outbits = 0; - var j = 0, k = pixBytes; - var columns = this.columns; - for (var i = 0; i < columns; ++i) { - for (var kk = 0; kk < colors; ++kk) { - if (inbits < bits) { - inbuf = (inbuf << 8) | (rawBytes[j++] & 0xFF); - inbits += 8; - } - compArray[kk] = (compArray[kk] + - (inbuf >> (inbits - bits))) & bitMask; - inbits -= bits; - outbuf = (outbuf << bits) | compArray[kk]; - outbits += bits; - if (outbits >= 8) { - currentRow[k++] = (outbuf >> (outbits - 8)) & 0xFF; - outbits -= 8; - } - } - } - if (outbits > 0) { - currentRow[k++] = (outbuf << (8 - outbits)) + - (inbuf & ((1 << (8 - outbits)) - 1)) - } - } - this.pos = pixBytes; - }, - readRowPng : function() { - var currentRow = this.currentRow; - - var rowBytes = this.rowBytes; - var pixBytes = this.pixBytes; - - var predictor = this.stream.getByte(); - var rawBytes = this.stream.getBytes(rowBytes - pixBytes); - - switch (predictor) { - case 0: - break; - case 1: - for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j) - currentRow[i] = (currentRow[i - pixBytes] + rawBytes[j]) & 0xFF; - break; - case 2: - for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j) - currentRow[i] = (currentRow[i] + rawBytes[j]) & 0xFF; - break; - case 3: - for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j) - currentRow[i] = (((currentRow[i] + currentRow[i - pixBytes]) - >> 1) + rawBytes[j]) & 0xFF; - break; - case 4: - // we need to save the up left pixels values. the simplest way - // is to create a new buffer - var lastRow = currentRow; - var currentRow = new Uint8Array(rowBytes); - for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j) { - var up = lastRow[i]; - var upLeft = lastRow[i - pixBytes]; - var left = currentRow[i - pixBytes]; - var p = left + up - upLeft; - - var pa = p - left; - if (pa < 0) - pa = -pa; - var pb = p - up; - if (pb < 0) - pb = -pb; - var pc = p - upLeft; - if (pc < 0) - pc = -pc; - - var c = rawBytes[j]; - if (pa <= pb && pa <= pc) - currentRow[i] = left + c; - else if (pb <= pc) - currentRow[i] = up + c; - else - currentRow[i] = upLeft + c; - break; - this.currentRow = currentRow; - } - default: - error("Unsupported predictor"); - break; - } - this.pos = pixBytes; - }, - getByte : function() { - if (this.pos >= this.bufferLength) - this.readRow(); - return this.currentRow[this.pos++]; - }, - getBytes : function(n) { - var i, bytes; - bytes = new Uint8Array(n); - for (i = 0; i < n; ++i) { - if (this.pos >= this.bufferLength) - this.readRow(); - bytes[i] = this.currentRow[this.pos++]; - } - return bytes; - }, - getChar : function() { - return String.formCharCode(this.getByte()); - }, - lookChar : function() { - if (this.pos >= this.bufferLength) - this.readRow(); - return String.formCharCode(this.currentRow[this.pos]); - }, - skip : function(n) { - var i; - if (!n) { - n = 1; - } - while (n > this.bufferLength - this.pos) { - n -= this.bufferLength - this.pos; - this.readRow(); - if (this.bufferLength === 0) break; - } - this.pos += n; - } - }; - - return constructor; -})(); - var DecryptStream = (function() { function constructor(str, fileKey, encAlgorithm, keyLength) { TODO("decrypt stream is not implemented"); @@ -921,6 +930,9 @@ var Dict = (function() { get2: function(key1, key2) { return this.get(key1) || this.get(key2); }, + get3: function(key1, key2, key3) { + return this.get(key1) || this.get(key2) || this.get(key3); + }, has: function(key) { return key in this.map; }, @@ -2396,7 +2408,7 @@ var CanvasGraphics = (function() { assertWellFormed(IsName(fontName), "invalid font name"); fontName = fontName.name.replace("+", "_"); - var fontFile = descriptor.get2("FontFile", "FontFile2"); + var fontFile = descriptor.get3("FontFile", "FontFile2", "FontFile3"); if (!fontFile) error("FontFile not found for font: " + fontName); fontFile = xref.fetchIfRef(fontFile); @@ -2762,7 +2774,7 @@ var CanvasGraphics = (function() { setWordSpacing: function(spacing) { TODO("word spacing"); }, - setHSpacing: function(scale) { + setHScale: function(scale) { TODO("horizontal text scale"); }, setLeading: function(leading) { diff --git a/test.py b/test.py index 46d30fef5..dae723b8a 100644 --- a/test.py +++ b/test.py @@ -1,7 +1,10 @@ -import json, os, sys, subprocess +import json, os, sys, subprocess, urllib2 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from urlparse import urlparse ANAL = True +DEFAULT_MANIFEST_FILE = 'test_manifest.json' +REFDIR = 'ref' VERBOSE = False MIMEs = { @@ -34,8 +37,11 @@ class PDFTestHandler(BaseHTTPRequestHandler): BaseHTTPRequestHandler.log_request(code, size) def do_GET(self): + url = urlparse(self.path) + # Ignore query string + path, _ = url.path, url.query cwd = os.getcwd() - path = os.path.abspath(os.path.realpath(cwd + os.sep + self.path)) + path = os.path.abspath(os.path.realpath(cwd + os.sep + path)) cwd = os.path.abspath(cwd) prefix = os.path.commonprefix(( path, cwd )) _, ext = os.path.splitext(path) @@ -69,19 +75,21 @@ class PDFTestHandler(BaseHTTPRequestHandler): self.end_headers() result = json.loads(self.rfile.read(numBytes)) - browser = 'firefox4' - id, failure, round, page, snapshot = result['id'], result['failure'], result['round'], result['page'], result['snapshot'] + browser, id, failure, round, page, snapshot = result['browser'], result['id'], result['failure'], result['round'], result['page'], result['snapshot'] taskResults = State.taskResults[browser][id] - taskResults[round][page - 1] = Result(snapshot, failure) + taskResults[round].append(Result(snapshot, failure)) + assert len(taskResults[round]) == page if result['taskDone']: check(State.manifest[id], taskResults, browser) + # Please oh please GC this ... + del State.taskResults[browser][id] State.remaining -= 1 State.done = (0 == State.remaining) -def set_up(): +def set_up(manifestFile): # Only serve files from a pdf.js clone assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git') @@ -90,10 +98,27 @@ def set_up(): #'chrome12', 'chrome13', 'firefox5', 'firefox6','opera11' ): if os.access(b, os.R_OK | os.X_OK) ] - mf = open('test_manifest.json') + mf = open(manifestFile) manifestList = json.load(mf) mf.close() + for item in manifestList: + f, isLink = item['file'], item.get('link', False) + if isLink and not os.access(f, os.R_OK): + linkFile = open(f +'.link') + link = linkFile.read() + linkFile.close() + + sys.stdout.write('Downloading '+ link +' to '+ f +' ...') + sys.stdout.flush() + response = urllib2.urlopen(link) + + out = open(f, 'w') + out.write(response.read()) + out.close() + + print 'done' + for b in testBrowsers: State.taskResults[b] = { } for item in manifestList: @@ -101,15 +126,16 @@ def set_up(): State.manifest[id] = item taskResults = [ ] for r in xrange(rounds): - taskResults.append([ None ] * 100) + taskResults.append([ ]) State.taskResults[b][id] = taskResults State.remaining = len(manifestList) for b in testBrowsers: print 'Launching', b + qs = 'browser='+ b +'&manifestFile='+ manifestFile subprocess.Popen(( os.path.abspath(os.path.realpath(b)), - 'http://localhost:8080/test_slave.html' )) + 'http://localhost:8080/test_slave.html?'+ qs)) def check(task, results, browser): @@ -129,7 +155,7 @@ def check(task, results, browser): return kind = task['type'] - if '==' == kind: + if 'eq' == kind: checkEq(task, results, browser) elif 'fbf' == kind: checkFBF(task, results, browser) @@ -140,23 +166,42 @@ def check(task, results, browser): def checkEq(task, results, browser): - print ' !!! [TODO: == tests] !!!' - print 'TEST-PASS | == test', task['id'], '| in', browser + pfx = os.path.join(REFDIR, sys.platform, browser, task['id']) + results = results[0] + passed = True + for page in xrange(len(results)): + ref = None + try: + path = os.path.join(pfx, str(page + 1)) + f = open(path) + ref = f.read() + f.close() + except IOError, ioe: + continue + + snapshot = results[page] + if ref != snapshot: + print 'TEST-UNEXPECTED-FAIL | eq', task['id'], '| in', browser, '| rendering of page', page + 1, '!= reference rendering' + passed = False + if passed: + print 'TEST-PASS | eq test', task['id'], '| in', browser -printed = [False] def checkFBF(task, results, browser): round0, round1 = results[0], results[1] assert len(round0) == len(round1) + passed = True for page in xrange(len(round1)): r0Page, r1Page = round0[page], round1[page] if r0Page is None: break if r0Page.snapshot != r1Page.snapshot: print 'TEST-UNEXPECTED-FAIL | forward-back-forward test', task['id'], '| in', browser, '| first rendering of page', page + 1, '!= second' - print 'TEST-PASS | forward-back-forward test', task['id'], '| in', browser + passed = False + if passed: + print 'TEST-PASS | forward-back-forward test', task['id'], '| in', browser def checkLoad(task, results, browser): @@ -165,11 +210,12 @@ def checkLoad(task, results, browser): print 'TEST-PASS | load test', task['id'], '| in', browser -def main(): - set_up() +def main(args): + manifestFile = args[0] if len(args) == 1 else DEFAULT_MANIFEST_FILE + set_up(manifestFile) server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler) while not State.done: server.handle_request() if __name__ == '__main__': - main() + main(sys.argv[1:]) diff --git a/test_manifest.json b/test_manifest.json index 2f45a026c..036b7aafc 100644 --- a/test_manifest.json +++ b/test_manifest.json @@ -1,8 +1,8 @@ [ - { "id": "tracemonkey-==", + { "id": "tracemonkey-eq", "file": "tests/tracemonkey.pdf", "rounds": 1, - "type": "==" + "type": "eq" }, { "id": "tracemonkey-fbf", "file": "tests/tracemonkey.pdf", @@ -13,5 +13,11 @@ "file": "tests/canvas.pdf", "rounds": 1, "type": "load" + }, + { "id": "pdfspec-load", + "file": "tests/pdf.pdf", + "link": true, + "rounds": 1, + "type": "load" } ] diff --git a/test_slave.html b/test_slave.html index c560d90d0..cff9b3f7d 100644 --- a/test_slave.html +++ b/test_slave.html @@ -5,9 +5,24 @@