diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..95de9fb8e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +pdf.pdf +intelisa.pdf +openweb_tm-PRINT.pdf diff --git a/README b/README index ee537f0a5..423943c5b 100644 --- a/README +++ b/README @@ -7,6 +7,14 @@ You can read more about pdf.js here: http://andreasgal.com/2011/06/15/pdf-js/ http://blog.mozilla.com/cjones/2011/06/15/overview-of-pdf-js-guts/ -Or follow us on twitter: @pdfjs +follow us on twitter: @pdfjs http://twitter.com/#!/pdfjs + +join our mailing list: + +dev-pdf-js@lists.mozilla.org + +and talk to us on IRC: + +#pdfjs on irc.mozilla.org diff --git a/crypto.js b/crypto.js new file mode 100644 index 000000000..e888d0212 --- /dev/null +++ b/crypto.js @@ -0,0 +1,260 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +"use strict"; + +var ARCFourCipher = (function() { + function constructor(key) { + this.a = 0; + this.b = 0; + var s = new Uint8Array(256); + var i, j = 0, tmp, keyLength = key.length; + for (i = 0; i < 256; ++i) + s[i] = i; + for (i = 0; i < 256; ++i) { + tmp = s[i]; + j = (j + tmp + key[i % keyLength]) & 0xFF; + s[i] = s[j]; + s[j] = tmp; + } + this.s = s; + } + + constructor.prototype = { + encryptBlock: function(data) { + var i, n = data.length, tmp, tmp2; + var a = this.a, b = this.b, s = this.s; + var output = new Uint8Array(n); + for (i = 0; i < n; ++i) { + var tmp; + a = (a + 1) & 0xFF; + tmp = s[a]; + b = (b + tmp) & 0xFF; + tmp2 = s[b] + s[a] = tmp2; + s[b] = tmp; + output[i] = data[i] ^ s[(tmp + tmp2) & 0xFF]; + } + this.a = a; + this.b = b; + return output; + } + }; + + return constructor; +})(); + +var md5 = (function() { + var r = new Uint8Array([ + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]); + var k = new Int32Array([ + -680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426, + -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162, + 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632, + 643717713, -373897302, -701558691, 38016083, -660478335, -405537848, 568446438, + -1019803690, -187363961, 1163531501, -1444681467, -51403784, 1735328473, + -1926607734, -378558, -2022574463, 1839030562, -35309556, -1530992060, + 1272893353, -155497632, -1094730640, 681279174, -358537222, -722521979, + 76029189, -640364487, -421815835, 530742520, -995338651, -198630844, 1126891415, + -1416354905, -57434055, 1700485571, -1894986606, -1051523, -2054922799, + 1873313359, -30611744, -1560198380, 1309151649, -145523070, -1120210379, + 718787259, -343485551]); + + function hash(data, offset, length) { + var h0 = 1732584193, h1 = -271733879, h2 = -1732584194, h3 = 271733878; + // pre-processing + var paddedLength = (length + 72) & ~63; // data + 9 extra bytes + var padded = new Uint8Array(paddedLength); + var i, j, n; + for (i = 0; i < length; ++i) + padded[i] = data[offset++]; + padded[i++] = 0x80; + n = paddedLength - 8; + for (; i < n; ++i) + padded[i] = 0; + padded[i++] = (length << 3) & 0xFF; + padded[i++] = (length >> 5) & 0xFF; + padded[i++] = (length >> 13) & 0xFF; + padded[i++] = (length >> 21) & 0xFF; + padded[i++] = (length >>> 29) & 0xFF; + padded[i++] = 0; + padded[i++] = 0; + padded[i++] = 0; + // chunking + // TODO ArrayBuffer ? + var w = new Int32Array(16); + for (i = 0; i < paddedLength;) { + for (j = 0; j < 16; ++j, i += 4) + w[j] = padded[i] | (padded[i + 1] << 8) | (padded[i + 2] << 16) | (padded[i + 3] << 24); + var a = h0, b = h1, c = h2, d = h3, f, g; + for (j = 0; j < 64; ++j) { + if (j < 16) { + f = (b & c) | ((~b) & d); + g = j; + } else if (j < 32) { + f = (d & b) | ((~d) & c); + g = (5 * j + 1) & 15; + } else if (j < 48) { + f = b ^ c ^ d; + g = (3 * j + 5) & 15; + } else { + f = c ^ (b | (~d)); + g = (7 * j) & 15; + } + var tmp = d, rotateArg = (a + f + k[j] + w[g]) | 0, rotate = r[j]; + d = c; + c = b; + b = (b + ((rotateArg << rotate) | (rotateArg >>> (32 - rotate)))) | 0; + a = tmp; + } + h0 = (h0 + a) | 0; + h1 = (h1 + b) | 0; + h2 = (h2 + c) | 0; + h3 = (h3 + d) | 0; + } + return new Uint8Array([ + h0 & 0xFF, (h0 >> 8) & 0xFF, (h0 >> 16) & 0xFF, (h0 >>> 24) & 0xFF, + h1 & 0xFF, (h1 >> 8) & 0xFF, (h1 >> 16) & 0xFF, (h1 >>> 24) & 0xFF, + h2 & 0xFF, (h2 >> 8) & 0xFF, (h2 >> 16) & 0xFF, (h2 >>> 24) & 0xFF, + h3 & 0xFF, (h3 >> 8) & 0xFF, (h3 >> 16) & 0xFF, (h3 >>> 24) & 0xFF + ]); + } + return hash; +})(); + +var CipherTransform = (function() { + function constructor(stringCipherConstructor, streamCipherConstructor) { + this.stringCipherConstructor = stringCipherConstructor; + this.streamCipherConstructor = streamCipherConstructor; + } + constructor.prototype = { + createStream: function (stream) { + var cipher = new this.streamCipherConstructor(); + return new DecryptStream(stream, function(data) { + return cipher.encryptBlock(data); + }); + }, + decryptString: function(s) { + var cipher = new this.stringCipherConstructor(); + var data = string2bytes(s); + data = cipher.encryptBlock(data); + return bytes2string(data); + } + }; + return constructor; +})(); + +var CipherTransformFactory = (function() { + function prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength) { + var defaultPasswordBytes = new Uint8Array([ + 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, + 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]); + var hashData = new Uint8Array(88), i = 0, j, n; + if (password) { + n = Math.min(32, password.length); + for (; i < n; ++i) + hashData[i] = password[i]; + } + j = 0; + while (i < 32) { + hashData[i++] = defaultPasswordBytes[j++]; + } + // as now the padded password in the hashData[0..i] + for (j = 0, n = ownerPassword.length; j < n; ++j) + hashData[i++] = ownerPassword[j]; + hashData[i++] = flags & 0xFF; + hashData[i++] = (flags >> 8) & 0xFF; + hashData[i++] = (flags >> 16) & 0xFF; + hashData[i++] = (flags >>> 24) & 0xFF; + for (j = 0, n = fileId.length; j < n; ++j) + hashData[i++] = fileId[j]; + // TODO rev 4, if metadata is not encrypted pass 0xFFFFFF also + var hash = md5(hashData, 0, i); + var keyLengthInBytes = keyLength >> 3; + if (revision >= 3) { + for (j = 0; j < 50; ++j) { + hash = md5(hash, 0, keyLengthInBytes); + } + } + var encryptionKey = hash.subarray(0, keyLengthInBytes); + var cipher, checkData; + + if (revision >= 3) { + // padded password in hashData, we can use this array for user password check + i = 32; + for(j = 0, n = fileId.length; j < n; ++j) + hashData[i++] = fileId[j]; + cipher = new ARCFourCipher(encryptionKey); + var checkData = cipher.encryptBlock(md5(hashData, 0, i)); + n = encryptionKey.length; + var derrivedKey = new Uint8Array(n), k; + for (j = 1; j <= 19; ++j) { + for (k = 0; k < n; ++k) + derrivedKey[k] = encryptionKey[k] ^ j; + cipher = new ARCFourCipher(derrivedKey); + checkData = cipher.encryptBlock(checkData); + } + } else { + cipher = new ARCFourCipher(encryptionKey); + checkData = cipher.encryptBlock(hashData.subarray(0, 32)); + } + for (j = 0, n = checkData.length; j < n; ++j) { + if (userPassword[j] != checkData[j]) + error("incorrect password"); + } + return encryptionKey; + } + + function constructor(dict, fileId, password) { + var filter = dict.get("Filter"); + if (!IsName(filter) || filter.name != "Standard") + error("unknown encryption method"); + this.dict = dict; + var algorithm = dict.get("V"); + if (!IsInt(algorithm) || + (algorithm != 1 && algorithm != 2)) + error("unsupported encryption algorithm"); + // TODO support algorithm 4 + var keyLength = dict.get("Length") || 40; + if (!IsInt(keyLength) || + keyLength < 40 || (keyLength % 8) != 0) + error("invalid key length"); + // prepare keys + var ownerPassword = stringToBytes(dict.get("O")); + var userPassword = stringToBytes(dict.get("U")); + var flags = dict.get("P"); + var revision = dict.get("R"); + var fileIdBytes = stringToBytes(fileId); + var passwordBytes; + if (password) + passwordBytes = stringToBytes(password); + + this.encryptionKey = prepareKeyData(fileIdBytes, passwordBytes, + ownerPassword, userPassword, flags, revision, keyLength); + } + + constructor.prototype = { + createCipherTransform: function(num, gen) { + var encryptionKey = this.encryptionKey; + var key = new Uint8Array(encryptionKey.length + 5), i, n; + for (i = 0, n = encryptionKey.length; i < n; ++i) + key[i] = encryptionKey[i]; + key[i++] = num & 0xFF; + key[i++] = (num >> 8) & 0xFF; + key[i++] = (num >> 16) & 0xFF; + key[i++] = gen & 0xFF; + key[i++] = (gen >> 8) & 0xFF; + var hash = md5(key, 0, i); + key = hash.subarray(0, Math.min(key.length, 16)); + var cipherConstructor = function() { + return new ARCFourCipher(key); + }; + return new CipherTransform(cipherConstructor, cipherConstructor); + } + }; + + return constructor; +})(); diff --git a/fonts.js b/fonts.js index 4ac5935b5..19d16cbed 100644 --- a/fonts.js +++ b/fonts.js @@ -34,7 +34,7 @@ var kDisableFonts = false; * http://cgit.freedesktop.org/poppler/poppler/tree/poppler/GfxFont.cc#n65 */ -var kScalePrecision = 100; +var kScalePrecision = 40; var Fonts = { _active: null, @@ -68,8 +68,8 @@ var Fonts = { var unicode = encoding[charcode]; // Check if the glyph has already been converted - if (unicode instanceof Name) - unicode = encoding[unicode] = GlyphsUnicode[unicode.name]; + if (!IsNum(unicode)) + unicode = encoding[unicode] = GlyphsUnicode[unicode.name]; // Handle surrogate pairs if (unicode > 0xFFFF) { @@ -95,7 +95,7 @@ var Fonts = { } }; -var FontsLoader = { +var FontLoader = { bind: function(fonts) { var worker = (typeof window == "undefined"); var ready = true; @@ -105,10 +105,10 @@ var FontsLoader = { if (Fonts[font.name]) { ready = ready && !Fonts[font.name].loading; continue; - } else { - ready = false; } + ready = false; + var obj = new Font(font.name, font.file, font.properties); var str = ""; @@ -180,6 +180,7 @@ var Font = (function () { warn("Font " + properties.type + " is not supported"); break; } + this.data = data; Fonts[name] = { data: data, @@ -793,9 +794,18 @@ var Font = (function () { }); }, - bindDOM: function font_bindDom(data) { + bindDOM: function font_bindDom(data, callback) { var fontName = this.name; + // Just adding the font-face to the DOM doesn't make it load. It + // seems it's loaded once Gecko notices it's used. Therefore, + // add a div on the page using the loaded font. + var div = document.createElement("div"); + var style = 'font-family:"' + name + + '";position: absolute;top:-99999;left:-99999;z-index:-99999'; + div.setAttribute("style", style); + document.body.appendChild(div); + /** Hack begin */ // Actually there is not event when a font has finished downloading so // the following code are a dirty hack to 'guess' when a font is ready @@ -815,15 +825,19 @@ var Font = (function () { // For some reasons the font has not loaded, so mark it loaded for the // page to proceed but cry - if ((Date.now() - this.start) >= kMaxWaitForFontFace) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - warn("Is " + fontName + " loaded?"); - this.start = 0; - } else if (textWidth != ctx.measureText(testString).width) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - this.start = 0; + if (textWidth == ctx.measureText(testString).width) { + if ((Date.now() - this.start) < kMaxWaitForFontFace) { + return; + } else { + warn("Is " + fontName + " loaded?"); + } + } + + window.clearInterval(interval); + Fonts[fontName].loading = false; + this.start = 0; + if (callback) { + callback(); } }, 30, this); diff --git a/multi_page_viewer.css b/multi_page_viewer.css index b3eaab792..2eaca4870 100644 --- a/multi_page_viewer.css +++ b/multi_page_viewer.css @@ -74,6 +74,20 @@ span { width: 100%; } +.thumbnailPageNumber { + color: #fff; + font-size: 0.55em; + text-align: right; + margin: -6px 2px 6px 0px; + width: 102px; +} + +.thumbnail { + width: 104px; + height: 134px; + margin: 0px auto 10px; +} + .page { width: 816px; height: 1056px; @@ -163,27 +177,46 @@ span { } #sidebar { - background-color: rgba(0, 0, 0, 0.8); position: fixed; - width: 150px; + width: 200px; top: 62px; bottom: 18px; + left: -170px; + transition: left 0.25s ease-in-out 1s; + -moz-transition: left 0.25s ease-in-out 1s; + -webkit-transition: left 0.25s ease-in-out 1s; +} + +#sidebar:hover { + left: 0px; + transition: left 0.25s ease-in-out 0s; + -moz-transition: left 0.25s ease-in-out 0s; + -webkit-transition: left 0.25s ease-in-out 0s; +} + +#sidebarBox { + background-color: rgba(0, 0, 0, 0.7); + width: 150px; + height: 100%; 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; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; } #sidebarScrollView { position: absolute; overflow: hidden; overflow-y: auto; - top: 40px; - right: 10px; + top: 10px; bottom: 10px; left: 10px; + width: 130px; } #sidebarContentView { diff --git a/multi_page_viewer.html b/multi_page_viewer.html index 47234686d..e90606a23 100644 --- a/multi_page_viewer.html +++ b/multi_page_viewer.html @@ -6,6 +6,7 @@ + @@ -39,13 +40,16 @@ Open File - + --> + +
diff --git a/multi_page_viewer.js b/multi_page_viewer.js index 1631433ca..b2c0dc3ed 100644 --- a/multi_page_viewer.js +++ b/multi_page_viewer.js @@ -10,6 +10,8 @@ var PDFViewer = { element: null, + sidebarContentView: null, + previousPageButton: null, nextPageButton: null, pageNumberInput: null, @@ -26,41 +28,125 @@ var PDFViewer = { scale: 1.0, - pageWidth: function() { - return 816 * PDFViewer.scale; + pageWidth: function(page) { + return page.mediaBox[2] * PDFViewer.scale; }, - pageHeight: function() { - return 1056 * PDFViewer.scale; + pageHeight: function(page) { + return page.mediaBox[3] * PDFViewer.scale; }, lastPagesDrawn: [], - visiblePages: function() { - var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. + visiblePages: function() { + const pageBottomMargin = 20; var windowTop = window.pageYOffset; var windowBottom = window.pageYOffset + window.innerHeight; - var pageStartIndex = Math.floor(windowTop / pageHeight); - var pageStopIndex = Math.ceil(windowBottom / pageHeight); + + var pageHeight, page; + var i, n = PDFViewer.numberOfPages, currentHeight = 0; + for (i = 1; i <= n; i++) { + var page = PDFViewer.pdf.getPage(i); + pageHeight = PDFViewer.pageHeight(page) + pageBottomMargin; + if (currentHeight + pageHeight > windowTop) + break; + currentHeight += pageHeight; + } var pages = []; - - for (var i = pageStartIndex; i <= pageStopIndex; i++) { - pages.push(i + 1); + for (; i <= n && currentHeight < windowBottom; i++) { + var page = PDFViewer.pdf.getPage(i); + pageHeight = PDFViewer.pageHeight(page) + pageBottomMargin; + currentHeight += pageHeight; + pages.push(i); } return pages; }, + createThumbnail: function(num) { + if (PDFViewer.sidebarContentView) { + var anchor = document.createElement('a'); + anchor.href = '#' + num; + + var containerDiv = document.createElement('div'); + containerDiv.id = 'thumbnailContainer' + num; + containerDiv.className = 'thumbnail'; + + var pageNumberDiv = document.createElement('div'); + pageNumberDiv.className = 'thumbnailPageNumber'; + pageNumberDiv.innerHTML = '' + num; + + anchor.appendChild(containerDiv); + PDFViewer.sidebarContentView.appendChild(anchor); + PDFViewer.sidebarContentView.appendChild(pageNumberDiv); + } + }, + + removeThumbnail: function(num) { + var div = document.getElementById('thumbnailContainer' + num); + + if (div) { + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + } + }, + + drawThumbnail: function(num) { + if (!PDFViewer.pdf) + return; + + var div = document.getElementById('thumbnailContainer' + num); + + if (div && !div.hasChildNodes()) { + var page = PDFViewer.pdf.getPage(num); + var canvas = document.createElement('canvas'); + + canvas.id = 'thumbnail' + num; + canvas.mozOpaque = true; + + // Canvas dimensions must be specified in CSS pixels. CSS pixels + // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. + canvas.width = 104; + canvas.height = 134; + div.appendChild(canvas); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + var gfx = new CanvasGraphics(ctx); + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + var fonts = []; + page.compile(gfx, fonts); + + var loadFont = function() { + if (!FontLoader.bind(fonts)) { + pageTimeout = window.setTimeout(loadFont, 10); + return; + } + page.display(gfx); + } + loadFont(); + } + }, + createPage: function(num) { + var page = PDFViewer.pdf.getPage(num); + var anchor = document.createElement('a'); anchor.name = '' + num; var div = document.createElement('div'); div.id = 'pageContainer' + num; div.className = 'page'; - div.style.width = PDFViewer.pageWidth() + 'px'; - div.style.height = PDFViewer.pageHeight() + 'px'; + div.style.width = PDFViewer.pageWidth(page) + 'px'; + div.style.height = PDFViewer.pageHeight(page) + 'px'; PDFViewer.element.appendChild(anchor); PDFViewer.element.appendChild(div); @@ -81,18 +167,18 @@ var PDFViewer = { return; var div = document.getElementById('pageContainer' + num); - var canvas = document.createElement('canvas'); - + if (div && !div.hasChildNodes()) { var page = PDFViewer.pdf.getPage(num); - + var canvas = document.createElement('canvas'); + canvas.id = 'page' + num; canvas.mozOpaque = true; // Canvas dimensions must be specified in CSS pixels. CSS pixels // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. - canvas.width = PDFViewer.pageWidth(); - canvas.height = PDFViewer.pageHeight(); + canvas.width = PDFViewer.pageWidth(page); + canvas.height = PDFViewer.pageHeight(page); div.appendChild(canvas); var ctx = canvas.getContext('2d'); @@ -109,7 +195,7 @@ var PDFViewer = { page.compile(gfx, fonts); var loadFont = function() { - if (!FontsLoader.bind(fonts)) { + if (!FontLoader.bind(fonts)) { pageTimeout = window.setTimeout(loadFont, 10); return; } @@ -130,12 +216,9 @@ var PDFViewer = { if (PDFViewer.pdf) { for (i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createThumbnail(i); PDFViewer.createPage(i); } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - } } for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { @@ -153,6 +236,12 @@ var PDFViewer = { } PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; + + // Clear the array of the last pages drawn to force a redraw. + PDFViewer.lastPagesDrawn = []; + + // Jump the scroll position to the correct page. + PDFViewer.goToPage(PDFViewer.pageNumber); }, goToPage: function(num) { @@ -205,6 +294,10 @@ var PDFViewer = { PDFViewer.element.removeChild(PDFViewer.element.firstChild); } + while (PDFViewer.sidebarContentView.hasChildNodes()) { + PDFViewer.sidebarContentView.removeChild(PDFViewer.sidebarContentView.firstChild); + } + PDFViewer.pdf = new PDFDoc(new Stream(data)); PDFViewer.numberOfPages = PDFViewer.pdf.numPages; document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); @@ -216,6 +309,13 @@ var PDFViewer = { if (PDFViewer.numberOfPages > 0) { PDFViewer.drawPage(1); document.location.hash = 1; + + setTimeout(function() { + for (var i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createThumbnail(i); + PDFViewer.drawThumbnail(i); + } + }, 500); } PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; @@ -240,6 +340,8 @@ window.onload = function() { PDFViewer.element = document.getElementById('viewer'); + PDFViewer.sidebarContentView = document.getElementById('sidebarContentView'); + PDFViewer.pageNumberInput = document.getElementById('pageNumber'); PDFViewer.pageNumberInput.onkeydown = function(evt) { var charCode = evt.charCode || evt.keyCode; diff --git a/pdf.js b/pdf.js index f09ccd866..33a21e38c 100644 --- a/pdf.js +++ b/pdf.js @@ -56,6 +56,14 @@ function bytesToString(bytes) { return str; } +function stringToBytes(str) { + var length = str.length; + var bytes = new Uint8Array(length); + for (var n = 0; n < length; ++n) + bytes[n] = str.charCodeAt(n) & 0xFF; + return bytes; +} + var Stream = (function() { function constructor(arrayBuffer, start, length, dict) { this.bytes = new Uint8Array(arrayBuffer); @@ -633,7 +641,7 @@ var PredictorStream = (function() { 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); return this; } @@ -708,7 +716,7 @@ var PredictorStream = (function() { var rawBytes = this.stream.getBytes(rowBytes); var bufferLength = this.bufferLength; - var buffer = this.ensureBuffer(bufferLength + pixBytes); + var buffer = this.ensureBuffer(bufferLength + rowBytes); var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); @@ -800,11 +808,34 @@ var JpegStream = (function() { return constructor; })(); var DecryptStream = (function() { - function constructor(str, fileKey, encAlgorithm, keyLength) { - TODO("decrypt stream is not implemented"); + function constructor(str, decrypt) { + this.str = str; + this.dict = str.dict; + this.decrypt = decrypt; + + DecodeStream.call(this); } - constructor.prototype = Stream.prototype; + var chunkSize = 512; + + constructor.prototype = Object.create(DecodeStream.prototype); + constructor.prototype.readBlock = function() { + var chunk = this.str.getBytes(chunkSize); + if (!chunk || chunk.length == 0) { + this.eof = true; + return; + } + var decrypt = this.decrypt; + chunk = decrypt(chunk); + + var bufferLength = this.bufferLength; + var i, n = chunk.length; + var buffer = this.ensureBuffer(bufferLength + n); + for (i = 0; i < n; i++) + buffer[bufferLength++] = chunk[i]; + this.bufferLength = bufferLength; + this.eof = n < chunkSize; + }; return constructor; })(); @@ -877,6 +908,984 @@ var Ascii85Stream = (function() { return constructor; })(); +var CCITTFaxStream = (function() { + + var ccittEOL = -2; + var twoDimPass = 0; + var twoDimHoriz = 1; + var twoDimVert0 = 2; + var twoDimVertR1 = 3; + var twoDimVertL1 = 4; + var twoDimVertR2 = 5; + var twoDimVertL2 = 6; + var twoDimVertR3 = 7; + var twoDimVertL3 = 8; + + var twoDimTable = [ + [-1, -1], [-1, -1], // 000000x + [7, twoDimVertL3], // 0000010 + [7, twoDimVertR3], // 0000011 + [6, twoDimVertL2], [6, twoDimVertL2], // 000010x + [6, twoDimVertR2], [6, twoDimVertR2], // 000011x + [4, twoDimPass], [4, twoDimPass], // 0001xxx + [4, twoDimPass], [4, twoDimPass], + [4, twoDimPass], [4, twoDimPass], + [4, twoDimPass], [4, twoDimPass], + [3, twoDimHoriz], [3, twoDimHoriz], // 001xxxx + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimHoriz], [3, twoDimHoriz], + [3, twoDimVertL1], [3, twoDimVertL1], // 010xxxx + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertL1], [3, twoDimVertL1], + [3, twoDimVertR1], [3, twoDimVertR1], // 011xxxx + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [3, twoDimVertR1], [3, twoDimVertR1], + [1, twoDimVert0], [1, twoDimVert0], // 1xxxxxx + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0], + [1, twoDimVert0], [1, twoDimVert0] + ]; + + var whiteTable1 = [ + [-1, -1], // 00000 + [12, ccittEOL], // 00001 + [-1, -1], [-1, -1], // 0001x + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 001xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 010xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 011xx + [11, 1792], [11, 1792], // 1000x + [12, 1984], // 10010 + [12, 2048], // 10011 + [12, 2112], // 10100 + [12, 2176], // 10101 + [12, 2240], // 10110 + [12, 2304], // 10111 + [11, 1856], [11, 1856], // 1100x + [11, 1920], [11, 1920], // 1101x + [12, 2368], // 11100 + [12, 2432], // 11101 + [12, 2496], // 11110 + [12, 2560] // 11111 + ]; + + var whiteTable2 = [ + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000000xx + [8, 29], [8, 29], // 00000010x + [8, 30], [8, 30], // 00000011x + [8, 45], [8, 45], // 00000100x + [8, 46], [8, 46], // 00000101x + [7, 22], [7, 22], [7, 22], [7, 22], // 0000011xx + [7, 23], [7, 23], [7, 23], [7, 23], // 0000100xx + [8, 47], [8, 47], // 00001010x + [8, 48], [8, 48], // 00001011x + [6, 13], [6, 13], [6, 13], [6, 13], // 000011xxx + [6, 13], [6, 13], [6, 13], [6, 13], + [7, 20], [7, 20], [7, 20], [7, 20], // 0001000xx + [8, 33], [8, 33], // 00010010x + [8, 34], [8, 34], // 00010011x + [8, 35], [8, 35], // 00010100x + [8, 36], [8, 36], // 00010101x + [8, 37], [8, 37], // 00010110x + [8, 38], [8, 38], // 00010111x + [7, 19], [7, 19], [7, 19], [7, 19], // 0001100xx + [8, 31], [8, 31], // 00011010x + [8, 32], [8, 32], // 00011011x + [6, 1], [6, 1], [6, 1], [6, 1], // 000111xxx + [6, 1], [6, 1], [6, 1], [6, 1], + [6, 12], [6, 12], [6, 12], [6, 12], // 001000xxx + [6, 12], [6, 12], [6, 12], [6, 12], + [8, 53], [8, 53], // 00100100x + [8, 54], [8, 54], // 00100101x + [7, 26], [7, 26], [7, 26], [7, 26], // 0010011xx + [8, 39], [8, 39], // 00101000x + [8, 40], [8, 40], // 00101001x + [8, 41], [8, 41], // 00101010x + [8, 42], [8, 42], // 00101011x + [8, 43], [8, 43], // 00101100x + [8, 44], [8, 44], // 00101101x + [7, 21], [7, 21], [7, 21], [7, 21], // 0010111xx + [7, 28], [7, 28], [7, 28], [7, 28], // 0011000xx + [8, 61], [8, 61], // 00110010x + [8, 62], [8, 62], // 00110011x + [8, 63], [8, 63], // 00110100x + [8, 0], [8, 0], // 00110101x + [8, 320], [8, 320], // 00110110x + [8, 384], [8, 384], // 00110111x + [5, 10], [5, 10], [5, 10], [5, 10], // 00111xxxx + [5, 10], [5, 10], [5, 10], [5, 10], + [5, 10], [5, 10], [5, 10], [5, 10], + [5, 10], [5, 10], [5, 10], [5, 10], + [5, 11], [5, 11], [5, 11], [5, 11], // 01000xxxx + [5, 11], [5, 11], [5, 11], [5, 11], + [5, 11], [5, 11], [5, 11], [5, 11], + [5, 11], [5, 11], [5, 11], [5, 11], + [7, 27], [7, 27], [7, 27], [7, 27], // 0100100xx + [8, 59], [8, 59], // 01001010x + [8, 60], [8, 60], // 01001011x + [9, 1472], // 010011000 + [9, 1536], // 010011001 + [9, 1600], // 010011010 + [9, 1728], // 010011011 + [7, 18], [7, 18], [7, 18], [7, 18], // 0100111xx + [7, 24], [7, 24], [7, 24], [7, 24], // 0101000xx + [8, 49], [8, 49], // 01010010x + [8, 50], [8, 50], // 01010011x + [8, 51], [8, 51], // 01010100x + [8, 52], [8, 52], // 01010101x + [7, 25], [7, 25], [7, 25], [7, 25], // 0101011xx + [8, 55], [8, 55], // 01011000x + [8, 56], [8, 56], // 01011001x + [8, 57], [8, 57], // 01011010x + [8, 58], [8, 58], // 01011011x + [6, 192], [6, 192], [6, 192], [6, 192], // 010111xxx + [6, 192], [6, 192], [6, 192], [6, 192], + [6, 1664], [6, 1664], [6, 1664], [6, 1664], // 011000xxx + [6, 1664], [6, 1664], [6, 1664], [6, 1664], + [8, 448], [8, 448], // 01100100x + [8, 512], [8, 512], // 01100101x + [9, 704], // 011001100 + [9, 768], // 011001101 + [8, 640], [8, 640], // 01100111x + [8, 576], [8, 576], // 01101000x + [9, 832], // 011010010 + [9, 896], // 011010011 + [9, 960], // 011010100 + [9, 1024], // 011010101 + [9, 1088], // 011010110 + [9, 1152], // 011010111 + [9, 1216], // 011011000 + [9, 1280], // 011011001 + [9, 1344], // 011011010 + [9, 1408], // 011011011 + [7, 256], [7, 256], [7, 256], [7, 256], // 0110111xx + [4, 2], [4, 2], [4, 2], [4, 2], // 0111xxxxx + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 2], [4, 2], [4, 2], [4, 2], + [4, 3], [4, 3], [4, 3], [4, 3], // 1000xxxxx + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [4, 3], [4, 3], [4, 3], [4, 3], + [5, 128], [5, 128], [5, 128], [5, 128], // 10010xxxx + [5, 128], [5, 128], [5, 128], [5, 128], + [5, 128], [5, 128], [5, 128], [5, 128], + [5, 128], [5, 128], [5, 128], [5, 128], + [5, 8], [5, 8], [5, 8], [5, 8], // 10011xxxx + [5, 8], [5, 8], [5, 8], [5, 8], + [5, 8], [5, 8], [5, 8], [5, 8], + [5, 8], [5, 8], [5, 8], [5, 8], + [5, 9], [5, 9], [5, 9], [5, 9], // 10100xxxx + [5, 9], [5, 9], [5, 9], [5, 9], + [5, 9], [5, 9], [5, 9], [5, 9], + [5, 9], [5, 9], [5, 9], [5, 9], + [6, 16], [6, 16], [6, 16], [6, 16], // 101010xxx + [6, 16], [6, 16], [6, 16], [6, 16], + [6, 17], [6, 17], [6, 17], [6, 17], // 101011xxx + [6, 17], [6, 17], [6, 17], [6, 17], + [4, 4], [4, 4], [4, 4], [4, 4], // 1011xxxxx + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 4], [4, 4], [4, 4], [4, 4], + [4, 5], [4, 5], [4, 5], [4, 5], // 1100xxxxx + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [4, 5], [4, 5], [4, 5], [4, 5], + [6, 14], [6, 14], [6, 14], [6, 14], // 110100xxx + [6, 14], [6, 14], [6, 14], [6, 14], + [6, 15], [6, 15], [6, 15], [6, 15], // 110101xxx + [6, 15], [6, 15], [6, 15], [6, 15], + [5, 64], [5, 64], [5, 64], [5, 64], // 11011xxxx + [5, 64], [5, 64], [5, 64], [5, 64], + [5, 64], [5, 64], [5, 64], [5, 64], + [5, 64], [5, 64], [5, 64], [5, 64], + [4, 6], [4, 6], [4, 6], [4, 6], // 1110xxxxx + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 6], [4, 6], [4, 6], [4, 6], + [4, 7], [4, 7], [4, 7], [4, 7], // 1111xxxxx + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7], + [4, 7], [4, 7], [4, 7], [4, 7] + ]; + + var blackTable1 = [ + [-1, -1], [-1, -1], // 000000000000x + [12, ccittEOL], [12, ccittEOL], // 000000000001x + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000001xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000010xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000011xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000100xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000101xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000110xx + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 00000000111xx + [11, 1792], [11, 1792], [11, 1792], [11, 1792], // 00000001000xx + [12, 1984], [12, 1984], // 000000010010x + [12, 2048], [12, 2048], // 000000010011x + [12, 2112], [12, 2112], // 000000010100x + [12, 2176], [12, 2176], // 000000010101x + [12, 2240], [12, 2240], // 000000010110x + [12, 2304], [12, 2304], // 000000010111x + [11, 1856], [11, 1856], [11, 1856], [11, 1856], // 00000001100xx + [11, 1920], [11, 1920], [11, 1920], [11, 1920], // 00000001101xx + [12, 2368], [12, 2368], // 000000011100x + [12, 2432], [12, 2432], // 000000011101x + [12, 2496], [12, 2496], // 000000011110x + [12, 2560], [12, 2560], // 000000011111x + [10, 18], [10, 18], [10, 18], [10, 18], // 0000001000xxx + [10, 18], [10, 18], [10, 18], [10, 18], + [12, 52], [12, 52], // 000000100100x + [13, 640], // 0000001001010 + [13, 704], // 0000001001011 + [13, 768], // 0000001001100 + [13, 832], // 0000001001101 + [12, 55], [12, 55], // 000000100111x + [12, 56], [12, 56], // 000000101000x + [13, 1280], // 0000001010010 + [13, 1344], // 0000001010011 + [13, 1408], // 0000001010100 + [13, 1472], // 0000001010101 + [12, 59], [12, 59], // 000000101011x + [12, 60], [12, 60], // 000000101100x + [13, 1536], // 0000001011010 + [13, 1600], // 0000001011011 + [11, 24], [11, 24], [11, 24], [11, 24], // 00000010111xx + [11, 25], [11, 25], [11, 25], [11, 25], // 00000011000xx + [13, 1664], // 0000001100100 + [13, 1728], // 0000001100101 + [12, 320], [12, 320], // 000000110011x + [12, 384], [12, 384], // 000000110100x + [12, 448], [12, 448], // 000000110101x + [13, 512], // 0000001101100 + [13, 576], // 0000001101101 + [12, 53], [12, 53], // 000000110111x + [12, 54], [12, 54], // 000000111000x + [13, 896], // 0000001110010 + [13, 960], // 0000001110011 + [13, 1024], // 0000001110100 + [13, 1088], // 0000001110101 + [13, 1152], // 0000001110110 + [13, 1216], // 0000001110111 + [10, 64], [10, 64], [10, 64], [10, 64], // 0000001111xxx + [10, 64], [10, 64], [10, 64], [10, 64] + ]; + + var blackTable2 = [ + [8, 13], [8, 13], [8, 13], [8, 13], // 00000100xxxx + [8, 13], [8, 13], [8, 13], [8, 13], + [8, 13], [8, 13], [8, 13], [8, 13], + [8, 13], [8, 13], [8, 13], [8, 13], + [11, 23], [11, 23], // 00000101000x + [12, 50], // 000001010010 + [12, 51], // 000001010011 + [12, 44], // 000001010100 + [12, 45], // 000001010101 + [12, 46], // 000001010110 + [12, 47], // 000001010111 + [12, 57], // 000001011000 + [12, 58], // 000001011001 + [12, 61], // 000001011010 + [12, 256], // 000001011011 + [10, 16], [10, 16], [10, 16], [10, 16], // 0000010111xx + [10, 17], [10, 17], [10, 17], [10, 17], // 0000011000xx + [12, 48], // 000001100100 + [12, 49], // 000001100101 + [12, 62], // 000001100110 + [12, 63], // 000001100111 + [12, 30], // 000001101000 + [12, 31], // 000001101001 + [12, 32], // 000001101010 + [12, 33], // 000001101011 + [12, 40], // 000001101100 + [12, 41], // 000001101101 + [11, 22], [11, 22], // 00000110111x + [8, 14], [8, 14], [8, 14], [8, 14], // 00000111xxxx + [8, 14], [8, 14], [8, 14], [8, 14], + [8, 14], [8, 14], [8, 14], [8, 14], + [8, 14], [8, 14], [8, 14], [8, 14], + [7, 10], [7, 10], [7, 10], [7, 10], // 0000100xxxxx + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 10], [7, 10], [7, 10], [7, 10], + [7, 11], [7, 11], [7, 11], [7, 11], // 0000101xxxxx + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [7, 11], [7, 11], [7, 11], [7, 11], + [9, 15], [9, 15], [9, 15], [9, 15], // 000011000xxx + [9, 15], [9, 15], [9, 15], [9, 15], + [12, 128], // 000011001000 + [12, 192], // 000011001001 + [12, 26], // 000011001010 + [12, 27], // 000011001011 + [12, 28], // 000011001100 + [12, 29], // 000011001101 + [11, 19], [11, 19], // 00001100111x + [11, 20], [11, 20], // 00001101000x + [12, 34], // 000011010010 + [12, 35], // 000011010011 + [12, 36], // 000011010100 + [12, 37], // 000011010101 + [12, 38], // 000011010110 + [12, 39], // 000011010111 + [11, 21], [11, 21], // 00001101100x + [12, 42], // 000011011010 + [12, 43], // 000011011011 + [10, 0], [10, 0], [10, 0], [10, 0], // 0000110111xx + [7, 12], [7, 12], [7, 12], [7, 12], // 0000111xxxxx + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12], + [7, 12], [7, 12], [7, 12], [7, 12] + ]; + + var blackTable3 = [ + [-1, -1], [-1, -1], [-1, -1], [-1, -1], // 0000xx + [6, 9], // 000100 + [6, 8], // 000101 + [5, 7], [5, 7], // 00011x + [4, 6], [4, 6], [4, 6], [4, 6], // 0010xx + [4, 5], [4, 5], [4, 5], [4, 5], // 0011xx + [3, 1], [3, 1], [3, 1], [3, 1], // 010xxx + [3, 1], [3, 1], [3, 1], [3, 1], + [3, 4], [3, 4], [3, 4], [3, 4], // 011xxx + [3, 4], [3, 4], [3, 4], [3, 4], + [2, 3], [2, 3], [2, 3], [2, 3], // 10xxxx + [2, 3], [2, 3], [2, 3], [2, 3], + [2, 3], [2, 3], [2, 3], [2, 3], + [2, 3], [2, 3], [2, 3], [2, 3], + [2, 2], [2, 2], [2, 2], [2, 2], // 11xxxx + [2, 2], [2, 2], [2, 2], [2, 2], + [2, 2], [2, 2], [2, 2], [2, 2], + [2, 2], [2, 2], [2, 2], [2, 2] + ]; + + function constructor(str, params) { + this.str = str; + this.dict = str.dict; + + params = params || new Dict(); + + this.encoding = params.get("K") || 0; + this.eoline = params.get("EndOfLine") || false; + this.byteAlign = params.get("EncodedByteAlign") || false; + this.columns = params.get("Columns") || 1728; + this.rows = params.get("Rows") || 0; + var eoblock = params.get("EndOfBlock"); + if (eoblock == null) + eoblock = true; + this.eoblock = eoblock; + this.black = params.get("BlackIs1") || false; + + this.codingLine = new Uint32Array(this.columns + 1); + this.refLine = new Uint32Array(this.columns + 2); + + this.codingLine[0] = this.columns; + this.codingPos = 0; + + this.row = 0; + this.nextLine2D = this.encoding < 0; + this.inputBits = 0; + this.inputBuf; + this.outputBits = 0; + this.buf = EOF; + + var code1; + while ((code1 = this.lookBits(12)) == 0) { + this.eatBits(1); + } + if (code1 == 1) { + this.eatBits(12); + } + if (this.encoding > 0) { + this.nextLine2D = !this.lookBits(1); + this.eatBits(1); + } + + DecodeStream.call(this); + } + + constructor.prototype = Object.create(DecodeStream.prototype); + constructor.prototype.readBlock = function() { + while (!this.eof) { + var c = this.lookChar(); + this.buf = EOF; + this.ensureBuffer(this.bufferLength + 1); + this.buffer[this.bufferLength++] = c; + } + }; + constructor.prototype.addPixels = function(a1, blackPixels) { + var codingLine = this.codingLine; + var codingPos = this.codingPos; + + if (a1 > codingLine[codingPos]) { + if (a1 > this.columns) { + warn("row is wrong length"); + this.err = true; + a1 = this.columns; + } + if ((codingPos & 1) ^ blackPixels) { + ++codingPos; + } + + codingLine[codingPos] = a1; + } + this.codingPos = codingPos; + }; + constructor.prototype.addPixelsNeg = function(a1, blackPixels) { + var codingLine = this.codingLine; + var codingPos = this.codingPos; + + if (a1 > codingLine[codingPos]) { + if (a1 > this.columns) { + warn("row is wrong length"); + this.err = true; + a1 = this.columns; + } + if ((codingPos & 1) ^ blackPixels) + ++codingPos; + + codingLine[codingPos] = a1; + } else if (a1 < codingLine[codingPos]) { + if (a1 < 0) { + warn("invalid code"); + this.err = true; + a1 = 0; + } + while (codingPos > 0 && a1 < codingLine[codingPos - 1]) + --codingPos; + codingLine[codingPos] = a1; + } + + this.codingPos = codingPos; + }; + constructor.prototype.lookChar = function() { + 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; + + this.err = false; + + if (this.nextLine2D) { + for (var i = 0; codingLine[i] < columns; ++i) + refLine[i] = codingLine[i]; + + refLine[i++] = columns; + refLine[i] = columns; + codingLine[0] = 0; + this.codingPos = 0; + refPos = 0; + blackPixels = 0; + + while (codingLine[this.codingPos] < columns) { + var code1 = this.getTwoDimCode(); + switch (code1) { + case twoDimPass: + this.addPixels(refLine[refPos + 1], blackPixels); + if (refLine[refPos + 1] < columns) + refPos += 2; + break; + case twoDimHoriz: + var code1 = 0, code2 = 0; + if (blackPixels) { + var code3; + do { + code1 += (code3 = this.getBlackCode()); + } while (code3 >= 64); + do { + code2 += (code3 = this.getWhiteCode()); + } while (code3 >= 64); + } else { + var code3; + do { + code1 += (code3 = this.getWhiteCode()); + } while (code3 >= 64); + do { + code2 += (code3 = this.getBlackCode()); + } while (code3 >= 64); + } + this.addPixels(codingLine[this.codingPos] + code1, blackPixels); + if (codingLine[this.codingPos] < columns) { + this.addPixels(codingLine[this.codingPos] + code2, + blackPixels ^ 1); + } + while (refLine[refPos] <= codingLine[this.codingPos] + && refLine[refPos] < columns) { + refPos += 2; + } + break; + case twoDimVertR3: + this.addPixels(refLine[refPos] + 3, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case twoDimVertR2: + this.addPixels(refLine[refPos] + 2, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) { + refPos += 2; + } + } + break; + case twoDimVertR1: + this.addPixels(refLine[refPos] + 1, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case twoDimVert0: + this.addPixels(refLine[refPos], blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case twoDimVertL3: + this.addPixelsNeg(refLine[refPos] - 3, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + if (refPos > 0) + --refPos; + else + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case twoDimVertL2: + this.addPixelsNeg(refLine[refPos] - 2, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + if (refPos > 0) + --refPos; + else + ++refPos; + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case twoDimVertL1: + this.addPixelsNeg(refLine[refPos] - 1, blackPixels); + blackPixels ^= 1; + if (codingLine[this.codingPos] < columns) { + if (refPos > 0) + --refPos; + else + ++refPos; + + while (refLine[refPos] <= codingLine[this.codingPos] && + refLine[refPos] < columns) + refPos += 2; + } + break; + case EOF: + this.addPixels(columns, 0); + this.eof = true; + break; + default: + warn("bad 2d code"); + this.addPixels(columns, 0); + this.err = true; + break; + } + } + } else { + codingLine[0] = 0; + this.codingPos = 0; + blackPixels = 0; + while(codingLine[this.codingPos] < columns) { + code1 = 0; + if (blackPixels) { + do { + code1 += (code3 = this.getBlackCode()); + } while (code3 >= 64); + } else { + do { + code1 += (code3 = this.getWhiteCode()); + } while (code3 >= 64); + } + this.addPixels(codingLine[this.codingPos] + code1, blackPixels); + blackPixels ^= 1; + } + } + + if (this.byteAlign) + inputBits &= ~7; + + var gotEOL = false; + + if (!this.eoblock && this.row == this.rows - 1) { + this.eof = true; + } else { + code1 = this.lookBits(12); + while (code1 == 0) { + this.eatBits(1); + code1 = this.lookBits(12); + } + if (code1 == 1) { + this.eatBits(12); + gotEOL = true; + } else if (code1 == EOF) { + this.eof = true; + } + } + + if (!this.eof && this.encoding > 0) { + this.nextLine2D = !this.lookBits(1); + this.eatBits(1); + } + + if (this.eoblock && gotEOL) { + code1 = this.lookBits(12); + if (code1 == 1) { + this.eatBits(12); + if (this.encoding > 0) { + this.lookBits(1); + this.eatBits(1); + } + if (this.encoding >= 0) { + for (var i = 0; i < 4; ++i) { + code1 = this.lookBits(12); + if (code1 != 1) + warning("bad rtc code"); + this.eatBits(12); + if (this.encoding > 0) { + this.lookBits(1); + this.eatBits(1); + } + } + } + this.eof = true; + } + } else if (this.err && this.eoline) { + var code1; + while (true) { + code1 = this.lookBits(13); + if (code1 == EOF) { + this.eof = true; + return; + } + if ((code1 >> 1) == 1) { + break; + } + this.eatBits(1); + } + this.eatBits(12); + if (this.encoding > 0) { + this.eatBits(1); + this.nextLine2D = !(code1 & 1); + } + } + + if (codingLine[0] > 0) + this.outputBits = codingLine[this.codingPos = 0]; + else + this.outputBits = codingLine[this.codingPos = 1]; + this.row++; + } + + if (this.outputBits >= 8) { + this.buf = (this.codingPos & 1) ? 0 : 0xFF; + this.outputBits -= 8; + if (this.outputBits == 0 && codingLine[this.codingPos] < columns) { + this.codingPos++; + this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1]; + } + } else { + var bits = 8; + this.buf = 0; + do { + if (this.outputBits > bits) { + this.buf <<= bits; + if (!(this.codingPos & 1)) { + this.buf |= 0xFF >> (8 - bits); + } + this.outputBits -= bits; + bits = 0; + } else { + this.buf <<= this.outputBits; + if (!(this.codingPos & 1)) { + this.buf |= 0xFF >> (8 - this.outputBits); + } + bits -= this.outputBits; + this.outputBits = 0; + if (codingLine[this.codingPos] < columns) { + this.codingPos++; + this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1]; + } else if (bits > 0) { + this.buf <<= bits; + bits = 0; + } + } + } while (bits); + } + if (this.black) { + this.buf ^= 0xFF; + } + return this.buf; + }; + constructor.prototype.getTwoDimCode = function() { + var code = 0; + var p; + if (this.eoblock) { + code = this.lookBits(7); + p = twoDimTable[code]; + if (p[0] > 0) { + this.eatBits(p[0]); + return p[1]; + } + } else { + for (var n = 1; n <= 7; ++n) { + code = this.lookBits(n); + if (n < 7) { + code <<= 7 - n; + } + p = twoDimTable[code]; + if (p[0] == n) { + this.eatBits(n); + return p[1]; + } + } + } + warn("Bad two dim code"); + return EOF; + }; + + constructor.prototype.getWhiteCode = function() { + var code = 0; + var p; + var n; + if (this.eoblock) { + code = this.lookBits(12); + if (code == EOF) + return 1; + + if ((code >> 5) == 0) + p = whiteTable1[code]; + else + p = whiteTable2[code >> 3]; + + if (p[0] > 0) { + this.eatBits(p[0]); + return p[1]; + } + } else { + for (var n = 1; n <= 9; ++n) { + code = this.lookBits(n); + if (code == EOF) + return 1; + + if (n < 9) + code <<= 9 - n; + p = whiteTable2[code]; + if (p[0] == n) { + this.eatBits(n); + return p[0]; + } + } + for (var n = 11; n <= 12; ++n) { + code == this.lookBits(n); + if (code == EOF) + return 1; + if (n < 12) + code <<= 12 - n; + p = whiteTable1[code]; + if (p[0] == n) { + this.eatBits(n); + return p[1]; + } + } + } + warn("bad white code"); + this.eatBits(1); + return 1; + }; + constructor.prototype.getBlackCode = function() { + var code, p, n; + if (this.eoblock) { + code = this.lookBits(13); + if (code == EOF) + return 1; + if ((code >> 7) == 0) + p = blackTable1[code]; + else if ((code >> 9) == 0 && (code >> 7) != 0) + p = blackTable2[(code >> 1) - 64]; + else + p = blackTable3[code >> 7]; + + if (p[0] > 0) { + this.eatBits(p[0]); + return p[1]; + } + } else { + for (var n = 2; n <= 6; ++n) { + code = this.lookBits(n); + if (code == EOF) + return 1; + if (n < 6) + code <<= 6 - n; + + p = blackTable3[code]; + if (p[0] == n) { + this.eatBits(n); + return p[1]; + } + } + for (var n = 7; n <= 12; ++n) { + code = this.lookBits(n); + if (code == EOF) + return 1; + if (n < 12) + code <<= 12 - n; + if (code >= 64) { + p = blackTable2[code - 64]; + if (p[0] == n) { + this.eatBits(n); + return p[1]; + } + } + } + for (n = 10; n <= 13; ++n) { + code = this.lookBits(n); + if (code == EOF) + return 1; + if (n < 13) + code << 13 - n; + p = blackTable1[code]; + if (p[0] == n) { + this.eatBits(n); + return p[1]; + } + } + } + warn("bad black code"); + this.eatBits(1); + return 1; + }; + constructor.prototype.lookBits = function(n) { + var c; + while (this.inputBits < n) { + if ((c = this.str.getByte()) == null) { + if (this.inputBits == 0) + return EOF; + return (this.inputBuf << (n - this.inputBits)) + & (0xFFFF >> (16 - n)); + } + this.inputBuf = (this.inputBuf << 8) + c; + this.inputBits += 8; + } + return (this.inputBuf >> (this.inputBits - n)) & (0xFFFF >> (16 - n)); + }; + constructor.prototype.eatBits = function(n) { + if ((this.inputBits -= n) < 0) + this.inputBits = 0; + } + + return constructor; +})(); + var Name = (function() { function constructor(name) { this.name = name; @@ -979,7 +1988,7 @@ function IsArray(v) { } function IsStream(v) { - return typeof v == "object" && "getChar" in v; + return typeof v == "object" && v != null && ("getChar" in v); } function IsRef(v) { @@ -1048,10 +2057,10 @@ var Lexer = (function() { function ToHexDigit(ch) { if (ch >= "0" && ch <= "9") - return ch - "0"; - ch = ch.toLowerCase(); - if (ch >= "a" && ch <= "f") - return ch - "a"; + return ch.charCodeAt(0) - 48; + ch = ch.toUpperCase(); + if (ch >= "A" && ch <= "F") + return ch.charCodeAt(0) - 55; return -1; } @@ -1345,7 +2354,7 @@ var Parser = (function() { // don't buffer inline image data this.buf2 = (this.inlineImg > 0) ? null : this.lexer.getObj(); }, - getObj: function() { + getObj: function(cipherTransform) { // refill buffer after inline image data if (this.inlineImg == 2) this.refill(); @@ -1371,7 +2380,7 @@ var Parser = (function() { this.shift(); if (IsEOF(this.buf1)) break; - dict.set(key, this.getObj()); + dict.set(key, this.getObj(cipherTransform)); } } if (IsEOF(this.buf1)) @@ -1380,7 +2389,7 @@ var Parser = (function() { // stream objects are not allowed inside content streams or // object streams if (this.allowStreams && IsCmd(this.buf2, "stream")) { - return this.makeStream(dict); + return this.makeStream(dict, cipherTransform); } else { this.shift(); } @@ -1399,17 +2408,8 @@ var Parser = (function() { } else if (IsString(this.buf1)) { // string var str = this.buf1; this.shift(); - if (this.fileKey) { - var decrypt = new DecryptStream(new StringStream(str), - this.fileKey, - this.encAlgorithm, - this.keyLength); - var str = ""; - var pos = decrypt.pos; - var length = decrypt.length; - while (pos++ > length) - str += decrypt.getChar(); - } + if (cipherTransform) + str = cipherTransform.decryptString(str); return str; } @@ -1418,7 +2418,7 @@ var Parser = (function() { this.shift(); return obj; }, - makeStream: function(dict) { + makeStream: function(dict, cipherTransform) { var lexer = this.lexer; var stream = lexer.stream; @@ -1445,12 +2445,8 @@ var Parser = (function() { this.shift(); stream = stream.makeSubStream(pos, length, dict); - if (this.fileKey) { - stream = new DecryptStream(stream, - this.fileKey, - this.encAlgorithm, - this.keyLength); - } + if (cipherTransform) + stream = cipherTransform.createStream(stream); stream = this.filter(stream, dict, length); stream.parameters = dict; return stream; @@ -1490,7 +2486,7 @@ var Parser = (function() { return new Ascii85Stream(stream); } else if (name == "CCITTFaxDecode") { TODO("implement fax stream"); - return new FakeStream(stream); + return new CCITTFaxStream(stream, params); } else { error("filter '" + name + "' not supported yet"); } @@ -1584,12 +2580,18 @@ var XRef = (function() { this.xrefstms = {}; var trailerDict = this.readXRef(startXRef); + // prepare the XRef cache + this.cache = []; + + var encrypt = trailerDict.get("Encrypt"); + if (encrypt) { + var fileId = trailerDict.get("ID"); + this.encrypt = new CipherTransformFactory(this.fetch(encrypt), fileId[0] /*, password */); + } + // get the root dictionary (catalog) object if (!IsRef(this.root = trailerDict.get("Root"))) error("Invalid root reference"); - - // prepare the XRef cache - this.cache = []; } constructor.prototype = { @@ -1778,7 +2780,11 @@ var XRef = (function() { } error("bad XRef entry"); } - e = parser.getObj(); + if (this.encrypt) { + e = parser.getObj(this.encrypt.createCipherTransform(num, gen)); + } else { + e = parser.getObj(); + } // Don't cache streams since they are mutable. if (!IsStream(e)) this.cache[num] = e; @@ -2075,30 +3081,6 @@ var PDFDoc = (function() { return constructor; })(); -var IDENTITY_MATRIX = [ 1, 0, 0, 1, 0, 0 ]; - -// contexts store most of the state we need natively. -// However, PDF needs a bit more state, which we store here. -var CanvasExtraState = (function() { - function constructor() { - // Are soft masks and alpha values shapes or opacities? - this.alphaIsShape = false; - this.fontSize = 0.0; - this.textMatrix = IDENTITY_MATRIX; - this.leading = 0.0; - this.colorSpace = null; - // Current point (in user coordinates) - this.x = 0.0; - this.y = 0.0; - // Start of text line (in text coordinates) - this.lineX = 0.0; - this.lineY = 0.0; - } - constructor.prototype = { - }; - return constructor; -})(); - var Encodings = { get ExpertEncoding() { return shadow(this, "ExpertEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, @@ -2273,6 +3255,34 @@ var Encodings = { } }; +var IDENTITY_MATRIX = [ 1, 0, 0, 1, 0, 0 ]; + +// contexts store most of the state we need natively. +// However, PDF needs a bit more state, which we store here. +var CanvasExtraState = (function() { + function constructor() { + // Are soft masks and alpha values shapes or opacities? + this.alphaIsShape = false; + this.fontSize = 0; + this.textMatrix = IDENTITY_MATRIX; + this.leading = 0; + this.colorSpace = null; + // Current point (in user coordinates) + this.x = 0; + this.y = 0; + // Start of text line (in text coordinates) + this.lineX = 0; + this.lineY = 0; + // Character and word spacing + this.charSpace = 0; + this.wordSpace = 0; + this.textHScale = 100; + } + constructor.prototype = { + }; + return constructor; +})(); + function ScratchCanvas(width, height) { var canvas = document.createElement("canvas"); canvas.width = width; @@ -2771,13 +3781,13 @@ var CanvasGraphics = (function() { endText: function() { }, setCharSpacing: function(spacing) { - TODO("character (glyph?) spacing"); + this.ctx.charSpacing = spacing; }, setWordSpacing: function(spacing) { - TODO("word spacing"); + this.ctx.wordSpacing = spacing; }, setHScale: function(scale) { - TODO("horizontal text scale"); + this.ctx.textHScale = (scale % 100) * 0.01; }, setLeading: function(leading) { this.current.leading = leading; @@ -2810,7 +3820,11 @@ var CanvasGraphics = (function() { this.current.fontName = fontName; this.current.fontSize = size; - this.ctx.font = this.current.fontSize + 'px "' + this.current.fontName + '"'; + + this.ctx.font = this.current.fontSize + 'px "' + fontName + '"'; + if (this.ctx.$setFont) { + this.ctx.$setFont(fontName); + } }, setTextRenderingMode: function(mode) { TODO("text rendering mode"); @@ -2842,6 +3856,8 @@ var CanvasGraphics = (function() { this.moveText(0, this.current.leading); }, showText: function(text) { + // TODO: apply charSpacing, wordSpacing, textHScale + this.ctx.save(); this.ctx.transform.apply(this.ctx, this.current.textMatrix); this.ctx.scale(1, -1); @@ -2908,6 +3924,8 @@ var CanvasGraphics = (function() { this.setStrokeGray.apply(this, arguments); } else if (3 === arguments.length) { this.setStrokeRGBColor.apply(this, arguments); + } else if (4 === arguments.length) { + this.setStrokeCMYKColor.apply(this, arguments); } }, setStrokeColorN: function(/*...*/) { @@ -2920,6 +3938,8 @@ var CanvasGraphics = (function() { this.setFillGray.apply(this, arguments); } else if (3 === arguments.length) { this.setFillRGBColor.apply(this, arguments); + } else if (4 === arguments.length) { + this.setFillCMYKColor.apply(this, arguments); } }, setFillColorN: function(/*...*/) { @@ -3004,10 +4024,14 @@ var CanvasGraphics = (function() { // we want the canvas to be as large as the step size var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix); - var tmpCanvas = new this.ScratchCanvas( - Math.ceil(botRight[0] - topLeft[0]), // width - Math.ceil(botRight[1] - topLeft[1]) // height - ); + var width = botRight[0] - topLeft[0]; + var height = botRight[1] - topLeft[1]; + + // TODO: hack to avoid OOM, remove then pattern code is fixed + if (Math.abs(width) > 8192 || Math.abs(height) > 8192) + return false; + + var tmpCanvas = new this.ScratchCanvas(width, height); // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext("2d"); @@ -3059,10 +4083,10 @@ var CanvasGraphics = (function() { this.ctx.fillStyle = this.makeCssRgb(r, g, b); }, setStrokeCMYKColor: function(c, m, y, k) { - TODO("CMYK space"); + this.ctx.strokeStyle = this.makeCssCmyk(c, m, y, k); }, setFillCMYKColor: function(c, m, y, k) { - TODO("CMYK space"); + this.ctx.fillStyle = this.makeCssCmyk(c, m, y, k); }, // Shading @@ -3220,23 +4244,11 @@ var CanvasGraphics = (function() { paintImageXObject: function(ref, image, inline) { this.save(); - if (image.getParams) { - // JPX/JPEG2000 streams directly contain bits per component - // and color space mode information. - TODO("get params from actual stream"); - // var bits = ... - // var colorspace = ... - } - // TODO cache rendered images? + var ctx = this.ctx; var dict = image.dict; var w = dict.get2("Width", "W"); var h = dict.get2("Height", "H"); - - if (w < 1 || h < 1) - error("Invalid image width or height"); - - var ctx = this.ctx; // scale the image to the unit square ctx.scale(1/w, -1/h); @@ -3251,154 +4263,15 @@ var CanvasGraphics = (function() { return; } - var interpolate = dict.get2("Interpolate", "I"); - if (!IsBool(interpolate)) - interpolate = false; - var imageMask = dict.get2("ImageMask", "IM"); - if (!IsBool(imageMask)) - imageMask = false; - - var bitsPerComponent = image.bitsPerComponent; - if (!bitsPerComponent) { - bitsPerComponent = dict.get2("BitsPerComponent", "BPC"); - if (!bitsPerComponent) { - if (imageMask) - bitsPerComponent = 1; - else - error("Bits per component missing in image"); - } - } - - if (bitsPerComponent !== 8) { - TODO("Support bpc="+ bitsPerComponent); - this.restore(); - return; - } - - var xref = this.xref; - var colorSpaces = this.colorSpaces; - - if (imageMask) { - error("support image masks"); - } - - // actual image - var csStream = dict.get2("ColorSpace", "CS"); - csStream = xref.fetchIfRef(csStream); - if (IsName(csStream) && inline) - csStream = colorSpaces.get(csStream); - - var colorSpace = new ColorSpace(xref, csStream); - var decode = dict.get2("Decode", "D"); - - TODO("create color map"); - - var mask = image.dict.get("Mask"); - mask = xref.fetchIfRef(mask); - var smask = image.dict.get("SMask"); - smask = xref.fetchIfRef(smask); - - if (IsStream(smask)) { - if (inline) - error("cannot combine smask and inlining"); - - var maskDict = smask.dict; - var maskW = maskDict.get2("Width", "W"); - var maskH = maskDict.get2("Height", "H"); - if (!IsNum(maskW) || !IsNum(maskH) || maskW < 1 || maskH < 1) - error("Invalid image width or height"); - if (maskW !== w || maskH !== h) - error("Invalid image width or height"); - - var maskInterpolate = maskDict.get2("Interpolate", "I"); - if (!IsBool(maskInterpolate)) - maskInterpolate = false; - - var maskBPC = maskDict.get2("BitsPerComponent", "BPC"); - if (!maskBPC) - error("Invalid image mask bpc"); - - var maskCsStream = maskDict.get2("ColorSpace", "CS"); - maskCsStream = xref.fetchIfRef(maskCsStream); - var maskColorSpace = new ColorSpace(xref, maskCsStream); - if (maskColorSpace.mode !== "DeviceGray") - error("Invalid color space for smask"); - - var maskDecode = maskDict.get2("Decode", "D"); - if (maskDecode) - TODO("Handle mask decode"); - // handle matte object - } + var imageObj = new PDFImage(this.xref, this.res, image, inline); var tmpCanvas = new this.ScratchCanvas(w, h); var tmpCtx = tmpCanvas.getContext("2d"); var imgData = tmpCtx.getImageData(0, 0, w, h); var pixels = imgData.data; - if (bitsPerComponent != 8) - error("unhandled number of bits per component"); - - if (smask) { - if (maskColorSpace.numComps != 1) - error("Incorrect number of components in smask"); - - var numComps = colorSpace.numComps; - var imgArray = image.getBytes(numComps * w * h); - var imgIdx = 0; - - var smArray = smask.getBytes(w * h); - var smIdx = 0; - - var length = 4 * w * h; - switch (numComps) { - case 1: - for (var i = 0; i < length; i += 4) { - var p = imgArray[imgIdx++]; - pixels[i] = p; - pixels[i+1] = p; - pixels[i+2] = p; - pixels[i+3] = smArray[smIdx++]; - } - break; - case 3: - for (var i = 0; i < length; i += 4) { - pixels[i] = imgArray[imgIdx++]; - pixels[i+1] = imgArray[imgIdx++]; - pixels[i+2] = imgArray[imgIdx++]; - pixels[i+3] = smArray[smIdx++]; - } - break; - default: - TODO("Images with "+ numComps + " components per pixel"); - } - } else { - var numComps = colorSpace.numComps; - var imgArray = image.getBytes(numComps * w * h); - var imgIdx = 0; - - var length = 4 * w * h; - switch (numComps) { - case 1: - for (var i = 0; i < length; i += 4) { - var p = imgArray[imgIdx++]; - pixels[i] = p; - pixels[i+1] = p; - pixels[i+2] = p; - pixels[i+3] = 255; - } - break; - case 3: - for (var i = 0; i < length; i += 4) { - pixels[i] = imgArray[imgIdx++]; - pixels[i+1] = imgArray[imgIdx++]; - pixels[i+2] = imgArray[imgIdx++]; - pixels[i+3] = 255; - } - break; - default: - TODO("Images with "+ numComps + " components per pixel"); - } - } + imageObj.fillRgbaBuffer(pixels); + tmpCtx.putImageData(imgData, 0, 0); ctx.drawImage(tmpCanvas, 0, -h); this.restore(); @@ -3451,6 +4324,13 @@ var CanvasGraphics = (function() { var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0; return "rgb("+ ri +","+ gi +","+ bi +")"; }, + makeCssCmyk: function(c, m, y, k) { + // while waiting on CSS's cmyk()... http://www.ilkeratalay.com/colorspacesfaq.php#rgb + var ri = (255 * (1 - Math.min(1, c * (1 - k) + k))) | 0; + var gi = (255 * (1 - Math.min(1, m * (1 - k) + k))) | 0; + var bi = (255 * (1 - Math.min(1, y * (1 - k) + k))) | 0; + return "rgb("+ ri +","+ gi +","+ bi +")"; + }, // We generally keep the canvas context set for // nonzero-winding, and just set evenodd for the operations // that need them. @@ -3527,6 +4407,203 @@ var ColorSpace = (function() { return constructor; })(); +var PDFImage = (function() { + function constructor(xref, res, image, inline) { + this.image = image; + if (image.getParams) { + // JPX/JPEG2000 streams directly contain bits per component + // and color space mode information. + TODO("get params from actual stream"); + // var bits = ... + // var colorspace = ... + } + // TODO cache rendered images? + + var dict = image.dict; + this.width = dict.get2("Width", "W"); + this.height = dict.get2("Height", "H"); + + if (this.width < 1 || this.height < 1) + error("Invalid image width or height"); + + this.interpolate = dict.get2("Interpolate", "I") || false; + this.imageMask = dict.get2("ImageMask", "IM") || false; + + var bitsPerComponent = image.bitsPerComponent; + if (!bitsPerComponent) { + bitsPerComponent = dict.get2("BitsPerComponent", "BPC"); + if (!bitsPerComponent) { + if (this.imageMask) + bitsPerComponent = 1; + else + error("Bits per component missing in image"); + } + } + this.bpc = bitsPerComponent; + + var colorSpaces = res.get("ColorSpace"); + var csStream = xref.fetchIfRef(dict.get2("ColorSpace", "CS")); + if (IsName(csStream) && inline) + csStream = colorSpaces.get(csStream); + this.colorSpace = new ColorSpace(xref, csStream); + + this.numComps = this.colorSpace.numComps; + this.decode = dict.get2("Decode", "D"); + + var mask = xref.fetchIfRef(image.dict.get("Mask")); + var smask = xref.fetchIfRef(image.dict.get("SMask")); + + if (mask) { + TODO("masked images"); + } else if (smask) { + this.smask = new PDFImage(xref, res, smask); + } + }; + + constructor.prototype = { + getComponents: function getComponents(buffer) { + var bpc = this.bpc; + if (bpc == 8) + return buffer; + + var width = this.width; + var height = this.height; + var numComps = this.numComps; + + var length = width * height; + var bufferPos = 0; + var output = new Uint8Array(length); + + if (bpc == 1) { + var rowComps = width * numComps; + var mask = 0; + var buf = 0; + + for (var i = 0, ii = length; i < ii; ++i) { + if (i % rowComps == 0) { + mask = 0; + buf = 0; + } else { + mask >>= 1; + } + + if (mask <= 0) { + buf = buffer[bufferPos++]; + mask = 128; + } + + var t = buf & mask; + if (t == 0) + output[i] = 0; + else + output[i] = 255; + } + } else { + var rowComps = width * numComps; + var bits = 0; + var buf = 0; + + for (var i = 0, ii = length; i < ii; ++i) { + while (bits < bpc) { + buf = (buf << 8) | buffer[bufferPos++]; + bits += 8; + } + var remainingBits = bits - bpc; + var ret = buf >> remainingBits; + + if (i % rowComps == 0) { + buf = 0; + bits = 0; + } else { + buf = buf & ((1 << remainingBits) - 1); + bits = remainingBits; + } + output[i] = Math.round(255 * ret / ((1 << bpc) - 1)); + } + } + return output; + }, + getOpacity: function getOpacity() { + var smask = this.smask; + var width = this.width; + var height = this.height; + var buf = new Uint8Array(width * height); + + if (smask) { + var sw = smask.width; + var sh = smask.height; + if (sw != this.width || sh != this.height) + error("smask dimensions do not match image dimensions"); + + smask.fillGrayBuffer(buf); + return buf; + } else { + for (var i = 0, ii = width * height; i < ii; ++i) + buf[i] = 255; + } + return buf; + }, + fillRgbaBuffer: function fillRgbaBuffer(buffer) { + var numComps = this.numComps; + var width = this.width; + var height = this.height; + var bpc = this.bpc; + + // rows start at byte boundary; + var rowBytes = (width * numComps * bpc + 7) >> 3; + var imgArray = this.image.getBytes(height * rowBytes); + + var comps = this.getComponents(imgArray); + var compsPos = 0; + var opacity = this.getOpacity(); + var opacityPos = 0; + var length = width * height * 4; + + switch (numComps) { + case 1: + for (var i = 0; i < length; i += 4) { + var p = comps[compsPos++]; + buffer[i] = p; + buffer[i+1] = p; + buffer[i+2] = p; + buffer[i+3] = opacity[opacityPos++]; + } + break; + case 3: + for (var i = 0; i < length; i += 4) { + buffer[i] = comps[compsPos++]; + buffer[i+1] = comps[compsPos++]; + buffer[i+2] = comps[compsPos++]; + buffer[i+3] = opacity[opacityPos++]; + } + break; + default: + TODO("Images with "+ numComps + " components per pixel"); + } + }, + fillGrayBuffer: function fillGrayBuffer(buffer) { + var numComps = this.numComps; + if (numComps != 1) + error("Reading gray scale from a color image"); + + var width = this.width; + var height = this.height; + var bpc = this.bpc; + + // rows start at byte boundary; + var rowBytes = (width * numComps * bpc + 7) >> 3; + var imgArray = this.image.getBytes(height * rowBytes); + + var comps = this.getComponents(imgArray); + var length = width * height; + + for (var i = 0; i < length; ++i) + buffer[i] = comps[i]; + }, + }; + return constructor; +})(); + var PDFFunction = (function() { function constructor(xref, fn) { var dict = fn.dict; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index ef853ef61..95de9fb8e 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -1 +1,3 @@ pdf.pdf +intelisa.pdf +openweb_tm-PRINT.pdf diff --git a/test/pdfs/intelisa.pdf.link b/test/pdfs/intelisa.pdf.link new file mode 100644 index 000000000..371cdf947 --- /dev/null +++ b/test/pdfs/intelisa.pdf.link @@ -0,0 +1 @@ +http://www.intel.com/Assets/PDF/manual/253665.pdf \ No newline at end of file diff --git a/test/pdfs/openweb_tm-PRINT.pdf.link b/test/pdfs/openweb_tm-PRINT.pdf.link new file mode 100644 index 000000000..08c897140 --- /dev/null +++ b/test/pdfs/openweb_tm-PRINT.pdf.link @@ -0,0 +1 @@ +http://openweb.flossmanuals.net/materials/openweb_tm-PRINT.pdf \ No newline at end of file diff --git a/test/pdfs/sizes.pdf b/test/pdfs/sizes.pdf new file mode 100644 index 000000000..f621f821e Binary files /dev/null and b/test/pdfs/sizes.pdf differ diff --git a/test/resources/browser_manifests/browser_manifest.json.linux b/test/resources/browser_manifests/browser_manifest.json.linux new file mode 100644 index 000000000..a576899b5 --- /dev/null +++ b/test/resources/browser_manifests/browser_manifest.json.linux @@ -0,0 +1,10 @@ +[ + { + "name":"firefox7", + "path":"/home/sayrer/firefoxen/nightly/firefox" + }, + { + "name":"chrome14", + "path":"/opt/google/chrome/chrome" + } +] diff --git a/test/resources/browser_manifests/browser_manifest.json.mac b/test/resources/browser_manifests/browser_manifest.json.mac index 7c9dda943..5b93ff196 100644 --- a/test/resources/browser_manifests/browser_manifest.json.mac +++ b/test/resources/browser_manifests/browser_manifest.json.mac @@ -6,5 +6,9 @@ { "name":"firefox6", "path":"/Users/sayrer/firefoxen/Aurora.app" + }, + { + "name":"chrome14", + "path":"/Applications/Google Chrome.app" } ] diff --git a/test/resources/favicon.ico b/test/resources/favicon.ico new file mode 100644 index 000000000..d44438903 Binary files /dev/null and b/test/resources/favicon.ico differ diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js index d4b9d4130..c92af9167 100644 --- a/test/resources/firefox/user.js +++ b/test/resources/firefox/user.js @@ -32,3 +32,7 @@ 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/resources/reftest-analyzer.xhtml b/test/resources/reftest-analyzer.xhtml new file mode 100644 index 000000000..e4071f232 --- /dev/null +++ b/test/resources/reftest-analyzer.xhtml @@ -0,0 +1,601 @@ + + + + + + + Reftest analyzer + + + + + + +
+ +

Reftest analyzer: load reftest log

+ +

Either paste your log into this textarea:
+