From 34357eac191fbe1ed3495e4c244221d53bb3fc08 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Tue, 21 Jun 2011 21:11:59 +0200 Subject: [PATCH 01/98] Start displaying TrueType fonts --- fonts.js | 10 +++++----- pdf.js | 9 +++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/fonts.js b/fonts.js index ad3d4fd35..80a9f4bc2 100644 --- a/fonts.js +++ b/fonts.js @@ -103,7 +103,7 @@ var Font = (function () { // If the font is to be ignored, register it like an already loaded font // to avoid the cost of waiting for it be be loaded by the platform. - if (properties.ignore || properties.type == "TrueType" || kDisableFonts) { + if (properties.ignore || kDisableFonts) { Fonts[name] = { data: file, loading: false, @@ -368,11 +368,11 @@ var Font = (function () { var length = FontsUtils.bytesToInteger(file.getBytes(4)); // Read the table associated data - var currentPosition = file.pos; - file.pos = file.start + offset; - + var previousPosition = file.pos; + file.pos = file.start ? file.start : 0; + file.skip(offset); var data = file.getBytes(length); - file.pos = currentPosition; + file.pos = previousPosition; return { tag: tag, diff --git a/pdf.js b/pdf.js index 4db4ef06f..8268f1673 100644 --- a/pdf.js +++ b/pdf.js @@ -2199,8 +2199,13 @@ var CanvasGraphics = (function() { var tokens = []; var token = ""; - var buffer = cmapObj.ensureBuffer ? cmapObj.ensureBuffer() : cmapObj; - var cmap = cmapObj.getBytes(buffer.byteLength); + var length = cmapObj.length; + if (cmapObj instanceof FlateStream) { + cmapObj.readBlock(); + length = cmapObj.bufferLength; + } + + var cmap = cmapObj.getBytes(length); for (var i =0; i < cmap.length; i++) { var byte = cmap[i]; if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) { From 0073e3c6184aff0ade0fcf30d0a727a7a74d5c1a Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 13:34:35 -0700 Subject: [PATCH 02/98] initial impl of ASCII85 Decoding --- pdf.js | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/pdf.js b/pdf.js index fff135816..dfb47ea16 100644 --- a/pdf.js +++ b/pdf.js @@ -750,6 +750,116 @@ var DecryptStream = (function() { return constructor; })(); +var Ascii85Stream = (function() { + function constructor(str) { + this.str = str; + this.dict = str.dict; + this.eof = false; + this.pos = 0; + this.bufferLength = 0; + this.buffer = new Uint8Array(4); + } + constructor.prototype = { + getByte: function() { + if (this.pos >= this.bufferLength) + this.readBlock(); + return this.buffer[this.pos++]; + }, + getBytes: function(n) { + var i, bytes; + bytes = new Uint8Array(n); + for (i = 0; i < n; ++i) { + if (this.pos >= this.bufferLength) + this.readBlock(); + if (this.eof) + break; + bytes[i] = this.buffer[this.pos++]; + } + return bytes; + }, + getChar : function() { + return String.fromCharCode(this.getByte()); + }, + lookChar : function() { + if (this.pos >= this.bufferLength) + this.readRow(); + return String.fromCharCode(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.readBlock(); + if (this.bufferLength === 0) break; + } + this.pos += n; + }, + readBlock: function() { + if (this.eof) { + this.bufferLength = 0; + this.buffer = []; + this.pos = 0; + return; + } + + const tildaCode = "~".charCodeAt(0); + const zCode = "z".charCodeAt(0); + var str = this.str; + + var c = str.getByte(); + while (Lexer.isSpace(String.fromCharCode(c))) + c = str.getByte(); + if (!c || c === tildaCode) { + this.eof = true; + return; + } + + var buffer = this.buffer; + // special code for z + if (c == zCode) { + buffer[0] = 0; + buffer[1] = 0; + buffer[2] = 0; + buffer[3] = 0; + this.bufferLength = 4; + } else { + var input = new Uint8Array(5); + input[0] = c; + for (var i = 1; i < 5; ++i){ + c = str.getByte(); + while (Lexer.isSpace(String.fromCharCode(c))) + c = str.getByte(); + + input[i] = c; + + if (!c || c == tildaCode) + break; + } + this.bufferLength = i - 1; + // partial ending; + if (i < 5) { + for (++i; i < 5; ++i) + input[i] = 0x21 + 84; + this.eof = true; + } + var t = 0; + for (var i = 0; i < 5; ++i) + t = t * 85 + (input[i] - 0x21); + + for (var i = 3; i >= 0; --i){ + buffer[i] = t & 0xFF; + t >>= 8; + } + } + } + }; + + return constructor; +})(); + var Name = (function() { function constructor(name) { this.name = name; @@ -1354,6 +1464,8 @@ var Parser = (function() { } else if (name == "DCTDecode") { var bytes = stream.getBytes(length); return new JpegStream(bytes, stream.dict); + } else if (name == "ASCII85Decode") { + return new Ascii85Stream(stream); } else { error("filter '" + name + "' not supported yet"); } From 4ced0495137c116d2bf551ac3942fa19bff366d1 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 14:34:13 -0700 Subject: [PATCH 03/98] fixed getBytes in Ascii85Stream --- pdf.js | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/pdf.js b/pdf.js index dfb47ea16..5ff97fa91 100644 --- a/pdf.js +++ b/pdf.js @@ -766,16 +766,38 @@ var Ascii85Stream = (function() { return this.buffer[this.pos++]; }, getBytes: function(n) { - var i, bytes; - bytes = new Uint8Array(n); - for (i = 0; i < n; ++i) { - if (this.pos >= this.bufferLength) - this.readBlock(); - if (this.eof) - break; - bytes[i] = this.buffer[this.pos++]; + if (n) { + var i, bytes; + bytes = new Uint8Array(n); + for (i = 0; i < n; ++i) { + if (this.pos >= this.bufferLength) + this.readBlock(); + if (this.eof) + break; + bytes[i] = this.buffer[this.pos++]; + } + return bytes; + } else { + var length = 0; + var size = 1 << 8; + var bytes = new Uint8Array(size); + while (true) { + if (this.pos >= this.bufferLength) + this.readBlock(); + if (this.eof) + break; + if (length == size) { + var oldSize = size; + size <<= 1; + var oldBytes = bytes; + bytes = new Uint8Array(size); + for (var i = 0; i < oldSize; ++i) + bytes[i] = oldBytes[i]; + } + bytes[length++] = this.buffer[this.pos++]; + } + return bytes.subarray(0, length); } - return bytes; }, getChar : function() { return String.fromCharCode(this.getByte()); @@ -1441,8 +1463,8 @@ var Parser = (function() { if (IsArray(filter)) { var filterArray = filter; var paramsArray = params; - for (var i = 0, ii = filter.length; i < ii; ++i) { - filter = filter[i]; + for (var i = 0, ii = filterArray.length; i < ii; ++i) { + filter = filterArray[i]; if (!IsName(filter)) error("Bad filter name"); else { From e0853abddaa648003d3e7ff15c7c4c1ec7527718 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 15:16:04 -0700 Subject: [PATCH 04/98] combined redudent code in Stream --- pdf.js | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/pdf.js b/pdf.js index 52a65f1e3..8ea56ae22 100644 --- a/pdf.js +++ b/pdf.js @@ -65,45 +65,47 @@ 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; + lookByte: function() { if (this.pos >= this.end) - return -1; - return bytes[this.pos++]; + return; + return this.bytes[this.pos]; + }, + getByte: function() { + if (this.pos >= this.end) + 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.lookByte()); }, getChar: function() { - var ch = this.lookChar(); - if (!ch) - return ch; - this.pos++; - return ch; + return String.fromCharCode(this.getByte()); }, skip: function(n) { - if (!n && !IsNum(n)) + if (!n) n = 1; this.pos += n; }, From 00e2ff5a60a693fc9503e7a7d8797fc98ab631a3 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Tue, 21 Jun 2011 18:31:07 -0400 Subject: [PATCH 05/98] avoid constantly calling ensureBuffer --- pdf.js | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/pdf.js b/pdf.js index fff135816..1b751b5a4 100644 --- a/pdf.js +++ b/pdf.js @@ -314,7 +314,7 @@ var FlateStream = (function() { }, ensureBuffer: function(requested) { var buffer = this.buffer; - var current = buffer ? buffer.byteLength : 0; + var current = buffer ? buffer.length : 0; if (requested < current) return buffer; var size = 512; @@ -494,32 +494,40 @@ var FlateStream = (function() { } var pos = this.bufferLength; + var buffer = this.buffer; + var limit = buffer ? buffer.length : 0; while (true) { var code1 = this.getCode(litCodeTable); + if (code1 < 256) { + if (pos + 1 >= limit) { + buffer = this.ensureBuffer(pos + 1); + limit = buffer.length; + } + buffer[pos++] = code1; + continue; + } 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; + if (pos + len >= limit) { + buffer = this.ensureBuffer(pos + len); + limit = buffer.length; } + for (var k = 0; k < len; ++k, ++pos) + buffer[pos] = buffer[pos - dist]; } } }; From 99ffc9991e00210d9f4c2dc1a97fee5021553264 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 17:02:50 -0700 Subject: [PATCH 06/98] rewrote streams to be object oriented --- pdf.js | 543 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 282 insertions(+), 261 deletions(-) diff --git a/pdf.js b/pdf.js index 8ea56ae22..3331d5d4d 100644 --- a/pdf.js +++ b/pdf.js @@ -71,11 +71,6 @@ var Stream = (function() { get length() { return this.end - this.start; }, - lookByte: function() { - if (this.pos >= this.end) - return; - return this.bytes[this.pos]; - }, getByte: function() { if (this.pos >= this.end) return; @@ -99,10 +94,14 @@ var Stream = (function() { return bytes.subarray(pos, end); }, lookChar: function() { - return String.fromCharCode(this.lookByte()); + if (this.pos >= this.end) + return; + return String.fromCharCode(this.bytes[this.pos]); }, getChar: function() { - return String.fromCharCode(this.getByte()); + if (this.pos >= this.end) + return; + return String.fromCharCode(this.bytes[this.pos++]); }, skip: function(n) { if (!n) @@ -137,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 @@ -261,12 +338,11 @@ 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 = { @@ -314,64 +390,6 @@ var FlateStream = (function() { 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; @@ -489,8 +507,10 @@ var FlateStream = (function() { } } - litCodeTable = this.generateHuffmanTable(codeLengths.slice(0, numLitCodes)); - distCodeTable = this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes)); + litCodeTable = this.generateHuffmanTable( + codeLengths.slice(0, numLitCodes)); + distCodeTable = this.generateHuffmanTable( + codeLengths.slice(numLitCodes, codes)); } else { error("Unknown block type in flate stream"); } @@ -526,8 +546,199 @@ var FlateStream = (function() { } }; + var thisPrototype = constructor.prototype; + var superPrototype = DecodeStream.prototype; + for (var i in superPrototype) { + if (!thisPrototype[i]) + thisPrototype[i] = superPrototype[i]; + } + 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 = { + 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; + }, + 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; + }, + }; + + var thisPrototype = constructor.prototype; + var superPrototype = DecodeStream.prototype; + for (var i in superPrototype) { + if (!thisPrototype[i]) + thisPrototype[i] = superPrototype[i]; + } + + return constructor; +})(); + // A JpegStream can't be read directly. We use the platform to render the underlying // JPEG data for us. var JpegStream = (function() { @@ -552,196 +763,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"); From 24e541e9de4f551757d1c3c41a4eb5acac9d0324 Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo Date: Tue, 21 Jun 2011 22:15:13 -0400 Subject: [PATCH 07/98] Implemented support for opening files from the local file system using the HTML5 File API. --- images/buttons.png | Bin 2787 -> 3854 bytes images/source/FileButton.psd.zip | Bin 0 -> 19244 bytes multi-page-viewer.css | 53 ++++++++++++++++- multi-page-viewer.html | 12 ++++ multi-page-viewer.js | 98 +++++++++++++++++++++++++------ 5 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 images/source/FileButton.psd.zip diff --git a/images/buttons.png b/images/buttons.png index 682212660d388acaa181d9df7102a5f8eaefda22..3357b47d6bb6ffbc46f6e0bb545d06cdf2d7434e 100644 GIT binary patch delta 3234 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 B2$28) delta 2159 zcmZ{mdpHw}7sp3S$emo8q2Si-MVk|ZHD+c}{{^tL(roUrpkA-3XH!%KPlcsv~HJ5-ubN}BP znEZ~V862=C=JQn@0Dwd8SPa@VeqyPhC(ISA+O3;`9SJs;)64|hzs<^+mRp%NN1TL0 zDRt95Vnw8Qf%>QEz3En)Ik=hl3>q!ath)&Nw^AJM(*&@#>o`9Un^TS?De^hgW=_UYe{me6_N@z1>fd*HP`7=M;l_Jy8FdoS5J#HQ(4hx~(fU+`m|Q!q9* zcJbBc)rH~y+1^~lda6pd?9S@K`4TQsFnx;|RI1BwN}NA^7V9I>b}S68yxfp;wWjrn zRi9aQ?dLp1klbx(8Rvn8sUoP*GVv9Pn7B?DatjC>$T#Ut+WBd;hy1y<7FKJ5!{Ow^ zN4y&2eV&(>|8R`zNRtg5Vc;Wjq4=tb3JJ?IV3LyIl^q`o>zwt?AM@Qawn2S#AIIX; z;cPa$updwLb$KMlUUrf4o!s8q>MOz0d{NqIRIlcw1vd1PCMvJMG%`P1>nt&Fsj9rZ zw}7^CK05m4B;=llti;C%%2#*-&TaPVWGxc|Pdc8O($pRMR$4-0XT(nAkh{d_kW5l& zP^keeS+di6dwcM^0*UC=biF2Qt}m}WT^%nobP(7OZqK#-2Nv_iH7-kP_OM~*a@Sv= zIUXz8wPDb|Zf0+6a&j_+Q%|(2NFK@8D6A;`Ry)691RB^K9UNS#(h9E9?zqx2;Taiy zM`ePdzQ*j28`p!_a$%e@C}@tJUi~2obtq$B(`{c0^O%0A+{T1x0$d#$_ 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 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; From 595a384ca79b335ed173c42df2766da6725a8a09 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Wed, 22 Jun 2011 04:41:31 +0200 Subject: [PATCH 08/98] Support Format 6 cmap table, but does not pass the sanitizer yet --- fonts.js | 59 ++++++++++++++++++++++++++++++++++++++++++-------------- pdf.js | 4 +--- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/fonts.js b/fonts.js index 80a9f4bc2..f5a531785 100644 --- a/fonts.js +++ b/fonts.js @@ -242,7 +242,7 @@ var Font = (function () { return ranges; }; - function createCMAPTable(glyphs) { + function createCMapTable(glyphs) { var ranges = getRanges(glyphs); var headerSize = (12 * 2 + (ranges.length * 4 * 2)); @@ -274,7 +274,7 @@ var Font = (function () { var bias = 0; for (var i = 0; i < segCount - 1; i++) { var range = ranges[i]; - var start = range[0]; + var start = range[0]; var end = range[1]; var delta = (((start - 1) - bias) ^ 0xffff) + 1; bias += (end - start + 1); @@ -393,6 +393,47 @@ var Font = (function () { } }; + function replaceCMapTable(font, properties) { + var version = FontsUtils.bytesToInteger(font.getBytes(2)); + var numTables = FontsUtils.bytesToInteger(font.getBytes(2)); + + var tables = []; + for (var i = 0; i < numTables; i++) { + var platformID = FontsUtils.bytesToInteger(font.getBytes(2)); + var encodingID = FontsUtils.bytesToInteger(font.getBytes(2)); + var offset = FontsUtils.bytesToInteger(font.getBytes(4)); + var format = FontsUtils.bytesToInteger(font.getBytes(2)); + var length = FontsUtils.bytesToInteger(font.getBytes(2)); + var language = FontsUtils.bytesToInteger(font.getBytes(2)); + + if (format == 0 && numTables == 1) { + // Format 0 alone is not allowed by the sanitizer so let's rewrite + // that to a 3-1-4 Unicode BMP table + var charset = properties.charset; + var glyphs = []; + for (var i = 0; i < charset.length; i++) { + glyphs.push({ + unicode: GlyphsUnicode[charset[i]] || 0 + }); + } + + cmap.data = createCMapTable(glyphs); + } else if (format == 6 && numTables == 1) { + // Format 6 is a 2-bytes dense mapping, which means the font data + // lives glue together even if they are pretty far in the unicode + // table. (This looks weird, so I can have missed something) + var firstCode = FontsUtils.bytesToInteger(font.getBytes(2)); + var entryCount = FontsUtils.bytesToInteger(font.getBytes(2)); + + var encoding = properties.encoding; + for (var j = 0; j < entryCount; j++) { + var charcode = FontsUtils.bytesToInteger(font.getBytes(2)); + encoding[charcode + firstCode] = charcode + firstCode; + } + } + } + }; + // Check that required tables are present var requiredTables = [ "OS/2", "cmap", "head", "hhea", "hmtx", "maxp", "name", "post" ]; @@ -448,18 +489,8 @@ var Font = (function () { data: OS2 }); - // If the font is missing a OS/2 table it's could be an old mac font - // without a 3-1-4 Unicode BMP table, so let's rewrite it. - var charset = properties.charset; - var glyphs = []; - for (var i = 0; i < charset.length; i++) { - glyphs.push({ - unicode: GlyphsUnicode[charset[i]] - }); - } - // Replace the old CMAP table with a shiny new one - cmap.data = createCMAPTable(glyphs); + replaceCMapTable(font, properties); // Rewrite the 'post' table if needed if (!post) { @@ -599,7 +630,7 @@ var Font = (function () { var charstrings = font.getOrderedCharStrings(properties.glyphs); /** CMAP */ - cmap = createCMAPTable(charstrings); + cmap = createCMapTable(charstrings); createTableEntry(otf, offsets, "cmap", cmap); /** HEAD */ diff --git a/pdf.js b/pdf.js index 8268f1673..72a7b7970 100644 --- a/pdf.js +++ b/pdf.js @@ -2193,8 +2193,6 @@ var CanvasGraphics = (function() { } else if (IsStream(cmapObj)) { var encoding = Encodings["WinAnsiEncoding"]; var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); - for (var i = firstChar; i < encoding.length; i++) - encodingMap[i] = new Name(encoding[i]); var tokens = []; var token = ""; @@ -2538,7 +2536,7 @@ var CanvasGraphics = (function() { } this.current.fontSize = size; - this.ctx.font = this.current.fontSize +'px "' + fontName + '"'; + this.ctx.font = this.current.fontSize +'px "' + fontName + '", Symbol'; }, setTextRenderingMode: function(mode) { TODO("text rendering mode"); From f2952ec29637a39b52bad466ae49c3139d60a6c2 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 21:12:50 -0700 Subject: [PATCH 09/98] modified Ascii85Stream to be more like other streams --- pdf.js | 100 ++++++++++++++++++++------------------------------------- 1 file changed, 34 insertions(+), 66 deletions(-) diff --git a/pdf.js b/pdf.js index 5ff97fa91..ed456a69b 100644 --- a/pdf.js +++ b/pdf.js @@ -757,76 +757,39 @@ var Ascii85Stream = (function() { this.eof = false; this.pos = 0; this.bufferLength = 0; - this.buffer = new Uint8Array(4); + 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() { - if (this.pos >= this.bufferLength) + while (this.pos >= this.bufferLength) this.readBlock(); return this.buffer[this.pos++]; }, getBytes: function(n) { if (n) { - var i, bytes; - bytes = new Uint8Array(n); - for (i = 0; i < n; ++i) { - if (this.pos >= this.bufferLength) - this.readBlock(); - if (this.eof) - break; - bytes[i] = this.buffer[this.pos++]; - } - return bytes; + while (this.bufferLength < n || !this.eof) + this.readBlock(); + return this.buffer.subarray(0, n); } else { - var length = 0; - var size = 1 << 8; - var bytes = new Uint8Array(size); - while (true) { - if (this.pos >= this.bufferLength) - this.readBlock(); - if (this.eof) - break; - if (length == size) { - var oldSize = size; - size <<= 1; - var oldBytes = bytes; - bytes = new Uint8Array(size); - for (var i = 0; i < oldSize; ++i) - bytes[i] = oldBytes[i]; - } - bytes[length++] = this.buffer[this.pos++]; - } - return bytes.subarray(0, length); + while (!this.eof) + this.readBlock(); + return this.buffer; } }, - getChar : function() { - return String.fromCharCode(this.getByte()); - }, - lookChar : function() { - if (this.pos >= this.bufferLength) - this.readRow(); - return String.fromCharCode(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.readBlock(); - if (this.bufferLength === 0) break; - } - this.pos += n; - }, readBlock: function() { - if (this.eof) { - this.bufferLength = 0; - this.buffer = []; - this.pos = 0; - return; - } - const tildaCode = "~".charCodeAt(0); const zCode = "z".charCodeAt(0); var str = this.str; @@ -839,14 +802,17 @@ var Ascii85Stream = (function() { return; } - var buffer = this.buffer; + var bufferLength = this.bufferLength; + // special code for z if (c == zCode) { - buffer[0] = 0; - buffer[1] = 0; - buffer[2] = 0; - buffer[3] = 0; - this.bufferLength = 4; + this.ensureBuffer(bufferLength + 4); + var buffer = this.buffer; + buffer[bufferLength++] = 0; + buffer[bufferLength++] = 0; + buffer[bufferLength++] = 0; + buffer[bufferLength++] = 0; + this.bufferLength += 4; } else { var input = new Uint8Array(5); input[0] = c; @@ -860,7 +826,9 @@ var Ascii85Stream = (function() { if (!c || c == tildaCode) break; } - this.bufferLength = i - 1; + this.ensureBuffer(bufferLength + i - 1); + var buffer = this.buffer; + this.bufferLength += i - 1; // partial ending; if (i < 5) { for (++i; i < 5; ++i) @@ -872,7 +840,7 @@ var Ascii85Stream = (function() { t = t * 85 + (input[i] - 0x21); for (var i = 3; i >= 0; --i){ - buffer[i] = t & 0xFF; + buffer[bufferLength++] = t & 0xFF; t >>= 8; } } From 20556026cd22cea93c09b96bf5fb1af2ca4fe8e1 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 21:26:48 -0700 Subject: [PATCH 10/98] work in progress on ascii85 --- pdf.js | 57 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/pdf.js b/pdf.js index ed456a69b..c186f0cdf 100644 --- a/pdf.js +++ b/pdf.js @@ -774,20 +774,57 @@ var Ascii85Stream = (function() { return this.buffer = buffer2; }, getByte: function() { - while (this.pos >= this.bufferLength) - this.readBlock(); + var pos = this.pos; + while (this.bufferLength <= pos) { + if (this.eof) + return; + this.readBlock(); + } return this.buffer[this.pos++]; }, - getBytes: function(n) { - if (n) { - while (this.bufferLength < n || !this.eof) + getBytes: function(length) { + var pos = this.pos; + + this.ensureBuffer(pos + length); + if (length) { + while (!this.eof && this.bufferLength < pos + length) this.readBlock(); - return this.buffer.subarray(0, n); + + var end = pos + length; + var bufEnd = this.bufferLength; + + if (end > bufEnd) + end = bufEnd; } else { - while (!this.eof) + while(!this.eof) this.readBlock(); - return this.buffer; + var end = this.bufferLength; } + 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; }, readBlock: function() { const tildaCode = "~".charCodeAt(0); @@ -840,7 +877,7 @@ var Ascii85Stream = (function() { t = t * 85 + (input[i] - 0x21); for (var i = 3; i >= 0; --i){ - buffer[bufferLength++] = t & 0xFF; + buffer[bufferLength + i] = t & 0xFF; t >>= 8; } } @@ -1712,6 +1749,8 @@ var XRef = (function() { return this.fetch(obj); }, fetch: function(ref) { + if (!ref) + console.trace(); var num = ref.num; var e = this.cache[num]; if (e) From 438c565f45dd6fe80a294d330e8205b3cd8c8557 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 21:48:50 -0700 Subject: [PATCH 11/98] rewrote prototype inhertence --- pdf.js | 650 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 318 insertions(+), 332 deletions(-) diff --git a/pdf.js b/pdf.js index 3331d5d4d..dcd7e858b 100644 --- a/pdf.js +++ b/pdf.js @@ -345,213 +345,206 @@ var FlateStream = (function() { 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); + 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 (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; - }, - generateHuffmanTable: function(lengths) { - var n = lengths.length; + }; + 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]; - } + // 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; + // 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; } - } - } - return [codes, maxLen]; - }, - readBlock: function() { - function repeat(stream, array, len, offset, what) { - var repeat = stream.getBits(len) + offset; - while (repeat-- > 0) - array[i++] = what; - } + // fill the table entries + for (var i = code2; i < size; i += skip) + codes[i] = (len << 16) | val; - 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; - } - 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]; + ++code; } } } - }; - var thisPrototype = constructor.prototype; - var superPrototype = DecodeStream.prototype; - for (var i in superPrototype) { - if (!thisPrototype[i]) - thisPrototype[i] = superPrototype[i]; - } + 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; + } + 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]; + } + } + }; return constructor; })(); @@ -586,155 +579,148 @@ var PredictorStream = (function() { DecodeStream.call(this); } - constructor.prototype = { - readBlockTiff : function() { - var buffer = this.buffer; - var pos = this.pos; + constructor.prototype = Object.create(DecodeStream.prototype); - var rowBytes = this.rowBytes; - var pixBytes = this.pixBytes; + 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 buffer = this.buffer; + var bufferLength = this.bufferLength; + this.ensureBuffer(bufferLength + rowBytes); + var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); - 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; - } + 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; - }, - 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; + if (outbits > 0) { + currentRow[k++] = (outbuf << (8 - outbits)) + + (inbuf & ((1 << (8 - outbits)) - 1)) } - this.bufferLength += rowBytes; - }, + } + 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; }; - - var thisPrototype = constructor.prototype; - var superPrototype = DecodeStream.prototype; - for (var i in superPrototype) { - if (!thisPrototype[i]) - thisPrototype[i] = superPrototype[i]; - } return constructor; })(); From a215ef5ae850c90d40c6da58c4ec722e17e8d6c7 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 21:51:54 -0700 Subject: [PATCH 12/98] fixed indent --- pdf.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pdf.js b/pdf.js index dcd7e858b..38a2e0dd4 100644 --- a/pdf.js +++ b/pdf.js @@ -387,9 +387,9 @@ var FlateStream = (function() { 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; + this.codeSize = (codeSize - codeLen); + this.bytesPos = bytesPos; + return codeVal; }; constructor.prototype.generateHuffmanTable = function(lengths) { var n = lengths.length; From 55df4f40cb9ec09855fc610db02bc63f53c4d33d Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Wed, 22 Jun 2011 01:12:27 -0400 Subject: [PATCH 13/98] avoid frequent calls to ensureBuffer during decompression --- pdf.js | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/pdf.js b/pdf.js index 38a2e0dd4..abdd91075 100644 --- a/pdf.js +++ b/pdf.js @@ -61,7 +61,7 @@ var Stream = (function() { this.bytes = Uint8Array(arrayBuffer); this.start = start || 0; this.pos = this.start; - this.end = (start + length) || this.bytes.byteLength; + this.end = (start + length) || this.bytes.length; this.dict = dict; } @@ -516,33 +516,41 @@ var FlateStream = (function() { error("Unknown block type in flate stream"); } + var buffer = this.buffer; + var limit = buffer ? buffer.length : 0; var pos = this.bufferLength; while (true) { var code1 = this.getCode(litCodeTable); + if (code1 < 256) { + if (pos + 1 >= limit) { + buffer = this.ensureBuffer(pos + 1); + limit = buffer.length; + } + buffer[pos++] = code1; + continue; + } 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; + if (pos + len >= limit) { + buffer = this.ensureBuffer(pos + len); + limit = buffer.length; } + for (var k = 0; k < len; ++k, ++pos) + buffer[pos] = buffer[pos - dist]; } }; From 42f8b52639dad387f80884d23a3dc9dd93ffc229 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 22:39:38 -0700 Subject: [PATCH 14/98] made Ascii85Stream be a child of DecodeStream --- pdf.js | 196 ++++++++++++++++++++------------------------------------- 1 file changed, 68 insertions(+), 128 deletions(-) diff --git a/pdf.js b/pdf.js index e955c25ea..5ed0eb59d 100644 --- a/pdf.js +++ b/pdf.js @@ -171,15 +171,22 @@ var DecodeStream = (function() { getBytes: function(length) { var pos = this.pos; - this.ensureBuffer(pos + length); - while (!this.eof && this.bufferLength < pos + length) - this.readBlock(); + if (length) { + this.ensureBuffer(pos + length); + var end = pos + length; - var end = pos + length; - var bufEnd = this.bufferLength; + while (!this.eof && this.bufferLength < end) + this.readBlock(); - if (end > bufEnd) - end = bufEnd; + var bufEnd = this.bufferLength; + if (end > bufEnd) + end = bufEnd; + } else { + while (!this.eof) + this.readBlock(); + + var end = this.bufferLength; + } this.pos = end; return this.buffer.subarray(pos, end) @@ -763,132 +770,64 @@ var Ascii85Stream = (function() { function constructor(str) { this.str = str; this.dict = str.dict; - this.eof = false; - this.pos = 0; - this.bufferLength = 0; - this.buffer = null; + + DecodeStream.call(this); } - constructor.prototype = { - ensureBuffer: function(requested) { + + constructor.prototype = Object.create(DecodeStream.prototype); + constructor.prototype.readBlock = function() { + const tildaCode = "~".charCodeAt(0); + const zCode = "z".charCodeAt(0); + var str = this.str; + + var c = str.getByte(); + while (Lexer.isSpace(String.fromCharCode(c))) + c = str.getByte(); + + if (!c || c === tildaCode) { + this.eof = true; + return; + } + + var bufferLength = this.bufferLength; + + // special code for z + if (c == zCode) { + this.ensureBuffer(bufferLength + 4); 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); - if (length) { - while (!this.eof && this.bufferLength < pos + length) - this.readBlock(); - - var end = pos + length; - var bufEnd = this.bufferLength; - - if (end > bufEnd) - end = bufEnd; - } else { - while(!this.eof) - this.readBlock(); - var end = this.bufferLength; - } - 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; - }, - readBlock: function() { - const tildaCode = "~".charCodeAt(0); - const zCode = "z".charCodeAt(0); - var str = this.str; - - var c = str.getByte(); - while (Lexer.isSpace(String.fromCharCode(c))) + for (var i = 0; i < 4; ++i) + buffer[bufferLength + i] = 0; + this.bufferLength += 4; + } else { + var input = new Uint8Array(5); + input[0] = c; + for (var i = 1; i < 5; ++i){ c = str.getByte(); - if (!c || c === tildaCode) { - this.eof = true; - return; - } - - var bufferLength = this.bufferLength; - - // special code for z - if (c == zCode) { - this.ensureBuffer(bufferLength + 4); - var buffer = this.buffer; - buffer[bufferLength++] = 0; - buffer[bufferLength++] = 0; - buffer[bufferLength++] = 0; - buffer[bufferLength++] = 0; - this.bufferLength += 4; - } else { - var input = new Uint8Array(5); - input[0] = c; - for (var i = 1; i < 5; ++i){ + while (Lexer.isSpace(String.fromCharCode(c))) c = str.getByte(); - while (Lexer.isSpace(String.fromCharCode(c))) - c = str.getByte(); - input[i] = c; - - if (!c || c == tildaCode) - break; - } - this.ensureBuffer(bufferLength + i - 1); - var buffer = this.buffer; - this.bufferLength += i - 1; - // partial ending; - if (i < 5) { - for (++i; i < 5; ++i) - input[i] = 0x21 + 84; - this.eof = true; - } - var t = 0; - for (var i = 0; i < 5; ++i) - t = t * 85 + (input[i] - 0x21); - - for (var i = 3; i >= 0; --i){ - buffer[bufferLength + i] = t & 0xFF; - t >>= 8; - } + input[i] = c; + + if (!c || c == tildaCode) + break; + } + this.ensureBuffer(bufferLength + i - 1); + var buffer = this.buffer; + this.bufferLength += i - 1; + + // partial ending; + if (i < 5) { + for (; i < 5; ++i) + input[i] = 0x21 + 84; + this.eof = true; + } + var t = 0; + for (var i = 0; i < 5; ++i) + t = t * 85 + (input[i] - 0x21); + + for (var i = 3; i >= 0; --i){ + buffer[bufferLength + i] = t & 0xFF; + t >>= 8; } } }; @@ -2402,6 +2341,7 @@ var CanvasGraphics = (function() { constructor.prototype = { translateFont: function(fontDict, xref, resources) { + return; var descriptor = xref.fetch(fontDict.get("FontDescriptor")); var fontName = descriptor.get("FontName"); From f79f77174ceff97b4ce60bd7bec006057c51cb53 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 22:43:03 -0700 Subject: [PATCH 15/98] clean up code --- pdf.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/pdf.js b/pdf.js index 5ed0eb59d..766ea3b28 100644 --- a/pdf.js +++ b/pdf.js @@ -1700,8 +1700,6 @@ var XRef = (function() { return this.fetch(obj); }, fetch: function(ref) { - if (!ref) - console.trace(); var num = ref.num; var e = this.cache[num]; if (e) @@ -2341,7 +2339,6 @@ var CanvasGraphics = (function() { constructor.prototype = { translateFont: function(fontDict, xref, resources) { - return; var descriptor = xref.fetch(fontDict.get("FontDescriptor")); var fontName = descriptor.get("FontName"); From e9ed96d97cee3b957ad25c7af4ac46841fa13004 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Wed, 22 Jun 2011 07:46:41 +0200 Subject: [PATCH 16/98] Start of a TTF Format6 to Format4 converter (sigh) --- fonts.js | 30 ++++++++++++++++++++---------- pdf.js | 7 +++++-- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/fonts.js b/fonts.js index f5a531785..c7230a55a 100644 --- a/fonts.js +++ b/fonts.js @@ -284,8 +284,8 @@ var Font = (function () { idDeltas += string16(delta); idRangeOffsets += string16(0); - for (var j = start; j <= end; j++) - glyphsIds += String.fromCharCode(j); + for (var j = 0; j < range.length; j++) + glyphsIds += String.fromCharCode(range[j]); } startCount += "\xFF\xFF"; @@ -393,11 +393,10 @@ var Font = (function () { } }; - function replaceCMapTable(font, properties) { + function replaceCMapTable(cmap, font, properties) { var version = FontsUtils.bytesToInteger(font.getBytes(2)); var numTables = FontsUtils.bytesToInteger(font.getBytes(2)); - var tables = []; for (var i = 0; i < numTables; i++) { var platformID = FontsUtils.bytesToInteger(font.getBytes(2)); var encodingID = FontsUtils.bytesToInteger(font.getBytes(2)); @@ -406,14 +405,15 @@ var Font = (function () { var length = FontsUtils.bytesToInteger(font.getBytes(2)); var language = FontsUtils.bytesToInteger(font.getBytes(2)); - if (format == 0 && numTables == 1) { + if ((format == 0 && numTables == 1) || + (format == 6 && numTables == 1 && !properties.encoding.empty)) { // Format 0 alone is not allowed by the sanitizer so let's rewrite // that to a 3-1-4 Unicode BMP table var charset = properties.charset; var glyphs = []; - for (var i = 0; i < charset.length; i++) { + for (var j = 0; j < charset.length; j++) { glyphs.push({ - unicode: GlyphsUnicode[charset[i]] || 0 + unicode: GlyphsUnicode[charset[j]] || 0 }); } @@ -421,15 +421,25 @@ var Font = (function () { } else if (format == 6 && numTables == 1) { // Format 6 is a 2-bytes dense mapping, which means the font data // lives glue together even if they are pretty far in the unicode - // table. (This looks weird, so I can have missed something) + // table. (This looks weird, so I can have missed something), this + // works on Linux but seems to fails on Mac so let's rewrite the + // cmap table to a 3-1-4 style var firstCode = FontsUtils.bytesToInteger(font.getBytes(2)); var entryCount = FontsUtils.bytesToInteger(font.getBytes(2)); var encoding = properties.encoding; + var glyphs = []; for (var j = 0; j < entryCount; j++) { var charcode = FontsUtils.bytesToInteger(font.getBytes(2)); - encoding[charcode + firstCode] = charcode + firstCode; + glyphs.push({unicode: charcode + firstCode }); } + + var ranges = getRanges(glyphs); + var denseRange = ranges[0]; + var pos = 0; + for (var j = denseRange[0]; j <= denseRange[1]; j++) + encoding[j - 1] = glyphs[pos++].unicode; + cmap.data = createCMapTable(glyphs); } } }; @@ -490,7 +500,7 @@ var Font = (function () { }); // Replace the old CMAP table with a shiny new one - replaceCMapTable(font, properties); + replaceCMapTable(cmap, font, properties); // Rewrite the 'post' table if needed if (!post) { diff --git a/pdf.js b/pdf.js index 72a7b7970..9d06241f7 100644 --- a/pdf.js +++ b/pdf.js @@ -2143,7 +2143,7 @@ var CanvasGraphics = (function() { // Fonts with an embedded cmap but without any assignment in // it are not yet supported, so ask the fonts loader to ignore // them to not pay a stupid one sec latence. - var ignoreFont = true; + var ignoreFont = false; var encodingMap = {}; var charset = []; @@ -2187,6 +2187,7 @@ var CanvasGraphics = (function() { } } } else if (fontDict.has("ToUnicode")) { + encodingMap = {empty: true}; var cmapObj = xref.fetchIfRef(fontDict.get("ToUnicode")); if (IsName(cmapObj)) { error("ToUnicode file cmap translation not implemented"); @@ -2230,7 +2231,9 @@ var CanvasGraphics = (function() { var code = parseInt("0x" + tokens[j+2]); for (var k = startRange; k <= endRange; k++) { - encodingMap[k] = GlyphsUnicode[encoding[code]]; + // The encoding mapping table will be filled + // later during the building phase + //encodingMap[k] = GlyphsUnicode[encoding[code]]; charset.push(encoding[code++]); } } From e3ecfb00b6ced6e8db0d0baf85569b9e354abd58 Mon Sep 17 00:00:00 2001 From: sbarman Date: Tue, 21 Jun 2011 22:53:47 -0700 Subject: [PATCH 17/98] moved array allocation to consturctor --- pdf.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index 766ea3b28..852eb03e7 100644 --- a/pdf.js +++ b/pdf.js @@ -770,6 +770,7 @@ var Ascii85Stream = (function() { function constructor(str) { this.str = str; this.dict = str.dict; + this.input = new Uint8Array(5); DecodeStream.call(this); } @@ -799,7 +800,7 @@ var Ascii85Stream = (function() { buffer[bufferLength + i] = 0; this.bufferLength += 4; } else { - var input = new Uint8Array(5); + var input = this.input; input[0] = c; for (var i = 1; i < 5; ++i){ c = str.getByte(); From 9748c1a75494d7111442656deca8383883d51198 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Wed, 22 Jun 2011 02:34:26 -0400 Subject: [PATCH 18/98] ensureBuffer can modify this.buffer, so read it after ensureBuffer --- pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index a6dedb7d5..b53829abb 100644 --- a/pdf.js +++ b/pdf.js @@ -671,9 +671,9 @@ var PredictorStream = (function() { var predictor = this.stream.getByte(); var rawBytes = this.stream.getBytes(rowBytes); - var buffer = this.buffer; var bufferLength = this.bufferLength; this.ensureBuffer(bufferLength + pixBytes); + var buffer = this.buffer; var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); From afecf35ff50d21050e528c0143c3774f0cd063d4 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Wed, 22 Jun 2011 02:35:42 -0400 Subject: [PATCH 19/98] fix other cases of reading this.buffer before ensureBuffer, too --- pdf.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pdf.js b/pdf.js index b53829abb..abe58ce5a 100644 --- a/pdf.js +++ b/pdf.js @@ -603,10 +603,8 @@ var PredictorStream = (function() { var rowBytes = this.rowBytes; var pixBytes = this.pixBytes; - - var buffer = this.buffer; var bufferLength = this.bufferLength; - this.ensureBuffer(bufferLength + rowBytes); + var buffer this.ensureBuffer(bufferLength + rowBytes); var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); var bits = this.bits; @@ -672,8 +670,7 @@ var PredictorStream = (function() { var rawBytes = this.stream.getBytes(rowBytes); var bufferLength = this.bufferLength; - this.ensureBuffer(bufferLength + pixBytes); - var buffer = this.buffer; + var buffer = this.ensureBuffer(bufferLength + pixBytes); var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); @@ -802,8 +799,7 @@ var Ascii85Stream = (function() { // special code for z if (c == zCode) { - this.ensureBuffer(bufferLength + 4); - var buffer = this.buffer; + var buffer = this.ensureBuffer(bufferLength + 4); for (var i = 0; i < 4; ++i) buffer[bufferLength + i] = 0; this.bufferLength += 4; @@ -820,8 +816,7 @@ var Ascii85Stream = (function() { if (!c || c == tildaCode) break; } - this.ensureBuffer(bufferLength + i - 1); - var buffer = this.buffer; + var buffer = this.ensureBuffer(bufferLength + i - 1); this.bufferLength += i - 1; // partial ending; From fc0deffe0f5b81cc4df5918b0c5710b0bfa06afe Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Wed, 22 Jun 2011 02:36:34 -0400 Subject: [PATCH 20/98] fix typo --- pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index abe58ce5a..dc0f70188 100644 --- a/pdf.js +++ b/pdf.js @@ -604,7 +604,7 @@ var PredictorStream = (function() { var pixBytes = this.pixBytes; var bufferLength = this.bufferLength; - var buffer this.ensureBuffer(bufferLength + rowBytes); + var buffer = this.ensureBuffer(bufferLength + rowBytes); var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes); var bits = this.bits; From f775f886e440ff5845e79bc6d2c44ceb698f8003 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 00:05:45 -0700 Subject: [PATCH 21/98] don't let getPage() errors break the test slave --- test.py | 2 +- test_slave.html | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test.py b/test.py index dae723b8a..9cf740fba 100644 --- a/test.py +++ b/test.py @@ -87,7 +87,7 @@ class PDFTestHandler(BaseHTTPRequestHandler): State.remaining -= 1 State.done = (0 == State.remaining) - + def set_up(manifestFile): # Only serve files from a pdf.js clone diff --git a/test_slave.html b/test_slave.html index cff9b3f7d..06b911810 100644 --- a/test_slave.html +++ b/test_slave.html @@ -83,14 +83,13 @@ function nextPage() { failure = ''; log(" drawing page "+ currentTask.pageNum +"..."); - currentPage = pdfDoc.getPage(currentTask.pageNum); - var ctx = canvas.getContext("2d"); clear(ctx); var fonts = []; var gfx = new CanvasGraphics(ctx); try { + currentPage = pdfDoc.getPage(currentTask.pageNum); currentPage.compile(gfx, fonts); } catch(e) { failure = 'compile: '+ e.toString(); From 8cdc4ed971bbbd35f05f0be3b1434f68a84ae65f Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 01:35:46 -0700 Subject: [PATCH 22/98] add support for test "master mode" and finish impl of eq tests --- test.py | 108 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 13 deletions(-) diff --git a/test.py b/test.py index 9cf740fba..a7be3dfd6 100644 --- a/test.py +++ b/test.py @@ -2,9 +2,15 @@ import json, os, sys, subprocess, urllib2 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from urlparse import urlparse +def prompt(question): + '''Return True iff the user answered "yes" to |question|.''' + inp = raw_input(question +' [yes/no] > ') + return inp == 'yes' + ANAL = True DEFAULT_MANIFEST_FILE = 'test_manifest.json' REFDIR = 'ref' +TMPDIR = 'tmp' VERBOSE = False MIMEs = { @@ -23,6 +29,12 @@ class State: remaining = 0 results = { } done = False + masterMode = False + numErrors = 0 + numEqFailures = 0 + numEqNoSnapshot = 0 + numFBFFailures = 0 + numLoadFailures = 0 class Result: def __init__(self, snapshot, failure): @@ -89,13 +101,22 @@ class PDFTestHandler(BaseHTTPRequestHandler): State.done = (0 == State.remaining) -def set_up(manifestFile): +def setUp(manifestFile, masterMode): # Only serve files from a pdf.js clone assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git') + State.masterMode = masterMode + if masterMode and os.path.isdir(TMPDIR): + print 'Temporary snapshot dir tmp/ is still around.' + print 'tmp/ can be removed if it has nothing you need.' + if prompt('SHOULD THIS SCRIPT REMOVE tmp/? THINK CAREFULLY'): + subprocess.call(( 'rm', '-rf', 'tmp' )) + + assert not os.path.isdir(TMPDIR) + testBrowsers = [ b for b in - ( 'firefox4', ) -#'chrome12', 'chrome13', 'firefox5', 'firefox6','opera11' ): + ( 'firefox5', ) +#'chrome12', 'chrome13', 'firefox4', 'firefox6','opera11' ): if os.access(b, os.R_OK | os.X_OK) ] mf = open(manifestFile) @@ -149,6 +170,7 @@ def check(task, results, browser): failure = pageResult.failure if failure: failed = True + State.numErrors += 1 print 'TEST-UNEXPECTED-FAIL | test failed', task['id'], '| in', browser, '| page', p + 1, 'round', r, '|', failure if failed: @@ -171,19 +193,36 @@ def checkEq(task, results, browser): passed = True for page in xrange(len(results)): + snapshot = results[page].snapshot ref = None - try: - path = os.path.join(pfx, str(page + 1)) + eq = True + + path = os.path.join(pfx, str(page + 1)) + if not os.access(path, os.R_OK): + print 'WARNING: no reference snapshot', path + State.numEqNoSnapshot += 1 + else: 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 + eq = (ref == snapshot) + if not eq: + print 'TEST-UNEXPECTED-FAIL | eq', task['id'], '| in', browser, '| rendering of page', page + 1, '!= reference rendering' + passed = False + State.numEqFailures += 1 + + if State.masterMode and (ref is None or not eq): + tmpTaskDir = os.path.join(TMPDIR, sys.platform, browser, task['id']) + try: + os.makedirs(tmpTaskDir) + except OSError, e: + pass + + of = open(os.path.join(tmpTaskDir, str(page + 1)), 'w') + of.write(snapshot) + of.close() + if passed: print 'TEST-PASS | eq test', task['id'], '| in', browser @@ -200,6 +239,7 @@ def checkFBF(task, results, browser): if r0Page.snapshot != r1Page.snapshot: print 'TEST-UNEXPECTED-FAIL | forward-back-forward test', task['id'], '| in', browser, '| first rendering of page', page + 1, '!= second' passed = False + State.numFBFFailures += 1 if passed: print 'TEST-PASS | forward-back-forward test', task['id'], '| in', browser @@ -210,12 +250,54 @@ def checkLoad(task, results, browser): print 'TEST-PASS | load test', task['id'], '| in', browser +def processResults(): + print '' + numErrors, numEqFailures, numEqNoSnapshot, numFBFFailures = State.numErrors, State.numEqFailures, State.numEqNoSnapshot, State.numFBFFailures + numFatalFailures = (numErrors + numFBFFailures) + if 0 == numEqFailures and 0 == numFatalFailures: + print 'All tests passed.' + else: + print 'OHNOES! Some tests failed!' + if 0 < numErrors: + print ' errors:', numErrors + if 0 < numEqFailures: + print ' different ref/snapshot:', numEqFailures + if 0 < numFBFFailures: + print ' different first/second rendering:', numFBFFailures + + if State.masterMode and (0 < numEqFailures or 0 < numEqNoSnapshot): + print "Some eq tests failed or didn't have snapshots." + print 'Checking to see if master references can be updated...' + if 0 < numFatalFailures: + print ' No. Some non-eq tests failed.' + else: + ' Yes! The references in tmp/ can be synced with ref/.' + if not prompt('Would you like to update the master copy in ref/?'): + print ' OK, not updating.' + else: + sys.stdout.write(' Updating ... ') + + # XXX unclear what to do on errors here ... + # NB: do *NOT* pass --delete to rsync. That breaks this + # entire scheme. + subprocess.check_call(( 'rsync', '-arv', 'tmp/', 'ref/' )) + + print 'done' + + def main(args): - manifestFile = args[0] if len(args) == 1 else DEFAULT_MANIFEST_FILE - set_up(manifestFile) + masterMode = False + if len(args) == 1: + masterMode = (args[0] == '-m') + manifestFile = args[0] if not masterMode else DEFAULT_MANIFEST_FILE + + setUp(manifestFile, masterMode) + server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler) while not State.done: server.handle_request() + processResults() + if __name__ == '__main__': main(sys.argv[1:]) From c506cb5e047c72cbfb60be1afce2f40b9f42fed0 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 01:50:43 -0700 Subject: [PATCH 23/98] deal with apparent lack of FontDescriptor for standard fonts --- pdf.js | 7 ++++++- test.py | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index dc0f70188..19fa125ca 100644 --- a/pdf.js +++ b/pdf.js @@ -2343,7 +2343,12 @@ var CanvasGraphics = (function() { constructor.prototype = { translateFont: function(fontDict, xref, resources) { - var descriptor = xref.fetch(fontDict.get("FontDescriptor")); + var fd = fontDict.get("FontDescriptor"); + if (!fd) + // XXX deprecated "special treatment" for standard + // fonts? What do we need to do here? + return; + var descriptor = xref.fetch(fd); var fontName = descriptor.get("FontName"); assertWellFormed(IsName(fontName), "invalid font name"); diff --git a/test.py b/test.py index a7be3dfd6..0c326ec09 100644 --- a/test.py +++ b/test.py @@ -287,9 +287,10 @@ def processResults(): def main(args): masterMode = False + manifestFile = DEFAULT_MANIFEST_FILE if len(args) == 1: masterMode = (args[0] == '-m') - manifestFile = args[0] if not masterMode else DEFAULT_MANIFEST_FILE + manifestFile = args[0] if not masterMode else manifestFile setUp(manifestFile, masterMode) From 4f7fb7539bf91518d5854d723ea7839a8d2a3b9d Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Wed, 22 Jun 2011 10:56:31 +0200 Subject: [PATCH 24/98] Enhance the converter code by filling Format 6 dense array gaps --- fonts.js | 31 ++++++++++++++++++++++++++----- pdf.js | 2 +- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/fonts.js b/fonts.js index c7230a55a..ded1b70c2 100644 --- a/fonts.js +++ b/fonts.js @@ -427,18 +427,39 @@ var Font = (function () { var firstCode = FontsUtils.bytesToInteger(font.getBytes(2)); var entryCount = FontsUtils.bytesToInteger(font.getBytes(2)); + // Since Format 6 is a dense array, check for gaps in the indexes + // to fill them later if needed + var gaps = []; + for (var j = 1; j <= entryCount; j++) + gaps.push(j); + var encoding = properties.encoding; var glyphs = []; for (var j = 0; j < entryCount; j++) { var charcode = FontsUtils.bytesToInteger(font.getBytes(2)); - glyphs.push({unicode: charcode + firstCode }); + var index = gaps.indexOf(charcode); + if (index != -1) + gaps.splice(index, 1); + + glyphs.push({unicode: charcode + firstCode}); } + while (gaps.length) + glyphs.push({unicode: gaps.pop() + firstCode }); + var ranges = getRanges(glyphs); - var denseRange = ranges[0]; - var pos = 0; - for (var j = denseRange[0]; j <= denseRange[1]; j++) - encoding[j - 1] = glyphs[pos++].unicode; + + var pos = firstCode; + var bias = 1; + for (var j = 0; j < ranges.length; j++) { + var range = ranges[j]; + var start = range[0]; + var end = range[1]; + for (var k = start; k < end; k++) { + encoding[pos] = glyphs[pos - firstCode].unicode; + pos++; + } + } cmap.data = createCMapTable(glyphs); } } diff --git a/pdf.js b/pdf.js index 9d06241f7..23dd5bee9 100644 --- a/pdf.js +++ b/pdf.js @@ -2234,7 +2234,7 @@ var CanvasGraphics = (function() { // The encoding mapping table will be filled // later during the building phase //encodingMap[k] = GlyphsUnicode[encoding[code]]; - charset.push(encoding[code++]); + charset.push(encoding[code++] || ".notdef"); } } break; From 0a74ec7759e80b8d05ea406decca8c8fcf4ce20d Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 02:10:08 -0700 Subject: [PATCH 25/98] band-aid lack of Indexed color space --- pdf.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index 19fa125ca..85ee20da6 100644 --- a/pdf.js +++ b/pdf.js @@ -3419,8 +3419,18 @@ var ColorSpace = (function() { this.dict = dict; this.numComps = dict.get("N"); break; + case "Indexed": + this.stream = stream; + this.dict = stream.dict; + var base = cs[1]; + var hival = cs[2]; + assertWellFormed(0 <= hival && hival <= 255, "hival in range"); + var lookupTable = cs[3]; + TODO("implement 'Indexed' color space"); + this.numComps = 3; // HACK + break; default: - error("unrecognized color space object"); + error("unrecognized color space object '"+ mode +"'"); } } else { error("unrecognized color space object"); From d32cbedd06042ff1c9d3437a979406f6cd70b831 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 02:17:45 -0700 Subject: [PATCH 26/98] TODO() for radial shading --- pdf.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index 85ee20da6..98768dde0 100644 --- a/pdf.js +++ b/pdf.js @@ -3024,7 +3024,7 @@ var CanvasGraphics = (function() { var typeNum = shading.get("ShadingType"); var fillFn = types[typeNum]; if (!fillFn) - error("Unknown type of shading"); + error("Unknown or NYI type of shading '"+ typeNum +"'"); fillFn.apply(this, [shading]); this.restore(); @@ -3077,6 +3077,10 @@ var CanvasGraphics = (function() { this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); }, + fillRadialShading: function(sh) { + TODO("radial shading"); + }, + // Images beginInlineImage: function() { TODO("inline images"); From edb69dff598652dc40a1eb5eba0d0fd3c419d791 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 02:23:15 -0700 Subject: [PATCH 27/98] TODO() for 4-component-per-pixel images --- pdf.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index 98768dde0..1c30ac72d 100644 --- a/pdf.js +++ b/pdf.js @@ -3290,7 +3290,7 @@ var CanvasGraphics = (function() { } break; default: - error("unhandled amount of components per pixel: " + numComps); + TODO("Images with "+ numComps + " components per pixel"); } } else { var numComps = colorSpace.numComps; @@ -3317,7 +3317,7 @@ var CanvasGraphics = (function() { } break; default: - error("unhandled amount of components per pixel: " + numComps); + TODO("Images with "+ numComps + " components per pixel"); } } tmpCtx.putImageData(imgData, 0, 0); From 5f82fc46bf77fdebf5c3c8ff5cd6ac79298487dd Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Wed, 22 Jun 2011 11:25:00 +0200 Subject: [PATCH 28/98] Fill more gaps for Format 6 dense array --- fonts.js | 50 ++++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/fonts.js b/fonts.js index ded1b70c2..a008dfce0 100644 --- a/fonts.js +++ b/fonts.js @@ -427,39 +427,37 @@ var Font = (function () { var firstCode = FontsUtils.bytesToInteger(font.getBytes(2)); var entryCount = FontsUtils.bytesToInteger(font.getBytes(2)); - // Since Format 6 is a dense array, check for gaps in the indexes - // to fill them later if needed - var gaps = []; - for (var j = 1; j <= entryCount; j++) - gaps.push(j); - - var encoding = properties.encoding; var glyphs = []; + var min = 0xffff, max = 0; for (var j = 0; j < entryCount; j++) { var charcode = FontsUtils.bytesToInteger(font.getBytes(2)); - var index = gaps.indexOf(charcode); - if (index != -1) - gaps.splice(index, 1); + glyphs.push(charcode); - glyphs.push({unicode: charcode + firstCode}); + if (charcode < min) + min = charcode; + if (charcode > max) + max = charcode; } - while (gaps.length) - glyphs.push({unicode: gaps.pop() + firstCode }); - - var ranges = getRanges(glyphs); - - var pos = firstCode; - var bias = 1; - for (var j = 0; j < ranges.length; j++) { - var range = ranges[j]; - var start = range[0]; - var end = range[1]; - for (var k = start; k < end; k++) { - encoding[pos] = glyphs[pos - firstCode].unicode; - pos++; - } + // Since Format 6 is a dense array, check for gaps + for (var j = min; j < max; j++) { + if (glyphs.indexOf(j) == -1) + glyphs.push(j); } + + for (var j = 0; j < glyphs.length; j++) + glyphs[j] = { unicode: glyphs[j] + firstCode }; + + var ranges= getRanges(glyphs); + assert(ranges.length == 1, "Got " + ranges.length + " ranges in a dense array"); + + var encoding = properties.encoding; + var denseRange = ranges[0]; + var start = denseRange[0]; + var end = denseRange[1]; + var index = firstCode; + for (var j = start; j <= end; j++) + encoding[index++] = glyphs[j - firstCode - 1].unicode; cmap.data = createCMapTable(glyphs); } } From fc4e4e79c66b57a3bdc385c72280856e89c6c422 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 02:37:59 -0700 Subject: [PATCH 29/98] add TODO() for Shading Pattern --- pdf.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pdf.js b/pdf.js index 1c30ac72d..ec8f74a3b 100644 --- a/pdf.js +++ b/pdf.js @@ -2867,13 +2867,14 @@ var CanvasGraphics = (function() { error("Unable to find pattern resource"); var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); - - const types = [null, this.tilingFill]; - var typeNum = pattern.dict.get("PatternType"); + var patternDict = IsStream(pattern) ? pattern.dict : pattern; + const types = [null, this.tilingFill, + function() { TODO("Shading Patterns"); }]; + var typeNum = patternDict.get("PatternType"); var patternFn = types[typeNum]; if (!patternFn) error("Unhandled pattern type"); - patternFn.call(this, pattern); + patternFn.call(this, pattern, patternDict); } } else { // TODO real impl From 8b1669d5d60a610d5813541ee5f5b73eabfbbb09 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Wed, 22 Jun 2011 16:56:31 -0700 Subject: [PATCH 30/98] Change test.py to use an external browser_manifest.json file, and use OptionParser to handle the command line. --- browser_manifest.json | 7 +++ test.py | 104 ++++++++++++++++++++++++++++++------------ 2 files changed, 81 insertions(+), 30 deletions(-) create mode 100644 browser_manifest.json diff --git a/browser_manifest.json b/browser_manifest.json new file mode 100644 index 000000000..79115d1a4 --- /dev/null +++ b/browser_manifest.json @@ -0,0 +1,7 @@ +[ + { + "name":"Firefox 5", + "path":"/Applications/Firefox.app", + "type":"firefox" + } +] \ No newline at end of file diff --git a/test.py b/test.py index 0c326ec09..8314bd794 100644 --- a/test.py +++ b/test.py @@ -1,18 +1,41 @@ -import json, os, sys, subprocess, urllib2 +import json, platform, os, sys, subprocess, urllib, urllib2 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from optparse import OptionParser from urlparse import urlparse +USAGE_EXAMPLE = "%prog" + +ANAL = True +DEFAULT_MANIFEST_FILE = 'test_manifest.json' +DEFAULT_BROWSER_MANIFEST_FILE = 'browser_manifest.json' +REFDIR = 'ref' +TMPDIR = 'tmp' +VERBOSE = False + +class TestOptions(OptionParser): + def __init__(self, **kwargs): + OptionParser.__init__(self, **kwargs) + self.add_option("-m", "--masterMode", action="store_true", dest="masterMode", + help="Run the script in master mode.", default=False) + self.add_option("--manifestFile", action="store", type="string", dest="manifestFile", + help="A JSON file in the form of test_manifest.json (the default).") + self.add_option("--browserManifestFile", action="store", type="string", + dest="browserManifestFile", + help="A JSON file in the form of browser_manifest.json (the default).", + default=DEFAULT_BROWSER_MANIFEST_FILE) + self.set_usage(USAGE_EXAMPLE) + + def verifyOptions(self, options): + if options.masterMode and options.manifestFile: + self.error("--masterMode and --manifestFile must not be specified at the same time.") + options.manifestFile = DEFAULT_MANIFEST_FILE + return options + def prompt(question): '''Return True iff the user answered "yes" to |question|.''' inp = raw_input(question +' [yes/no] > ') return inp == 'yes' -ANAL = True -DEFAULT_MANIFEST_FILE = 'test_manifest.json' -REFDIR = 'ref' -TMPDIR = 'tmp' -VERBOSE = False - MIMEs = { '.css': 'text/css', '.html': 'text/html', @@ -100,13 +123,34 @@ class PDFTestHandler(BaseHTTPRequestHandler): State.done = (0 == State.remaining) +# this just does Firefox for now +class BrowserCommand(): + def __init__(self, browserRecord): + print browserRecord + self.name = browserRecord["name"] + self.path = browserRecord["path"] + self.type = browserRecord["type"] -def setUp(manifestFile, masterMode): + if platform.system() == "Darwin" and self.path.endswith(".app"): + self._fixupMacPath() + + if not os.path.exists(self.path): + throw("Path to browser '%s' does not exist." % self.path) + + def _fixupMacPath(self): + self.path = self.path + "/Contents/MacOS/firefox-bin" + +def makeBrowserCommands(browserManifestFile): + with open(browserManifestFile) as bmf: + browsers = [BrowserCommand(browser) for browser in json.load(bmf)] + return browsers + +def setUp(options): # Only serve files from a pdf.js clone assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git') - State.masterMode = masterMode - if masterMode and os.path.isdir(TMPDIR): + State.masterMode = options.masterMode + if options.masterMode and os.path.isdir(TMPDIR): print 'Temporary snapshot dir tmp/ is still around.' print 'tmp/ can be removed if it has nothing you need.' if prompt('SHOULD THIS SCRIPT REMOVE tmp/? THINK CAREFULLY'): @@ -114,14 +158,10 @@ def setUp(manifestFile, masterMode): assert not os.path.isdir(TMPDIR) - testBrowsers = [ b for b in - ( 'firefox5', ) -#'chrome12', 'chrome13', 'firefox4', 'firefox6','opera11' ): - if os.access(b, os.R_OK | os.X_OK) ] + testBrowsers = makeBrowserCommands(options.browserManifestFile) - mf = open(manifestFile) - manifestList = json.load(mf) - mf.close() + with open(options.manifestFile) as mf: + manifestList = json.load(mf) for item in manifestList: f, isLink = item['file'], item.get('link', False) @@ -140,22 +180,26 @@ def setUp(manifestFile, masterMode): print 'done' + print testBrowsers + for b in testBrowsers: - State.taskResults[b] = { } + State.taskResults[b.name] = { } for item in manifestList: id, rounds = item['id'], int(item['rounds']) State.manifest[id] = item taskResults = [ ] for r in xrange(rounds): taskResults.append([ ]) - State.taskResults[b][id] = taskResults + State.taskResults[b.name][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)), + print 'Launching', b.name + qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) + subprocess.Popen(( os.path.abspath(os.path.realpath(b.path)), 'http://localhost:8080/test_slave.html?'+ qs)) @@ -285,14 +329,14 @@ def processResults(): print 'done' -def main(args): - masterMode = False - manifestFile = DEFAULT_MANIFEST_FILE - if len(args) == 1: - masterMode = (args[0] == '-m') - manifestFile = args[0] if not masterMode else manifestFile +def main(): + optionParser = TestOptions() + options, args = optionParser.parse_args() + options = optionParser.verifyOptions(options) + if options == None: + sys.exit(1) - setUp(manifestFile, masterMode) + setUp(options) server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler) while not State.done: @@ -301,4 +345,4 @@ def main(args): processResults() if __name__ == '__main__': - main(sys.argv[1:]) + main() From c3ef14bb2f3f500cedc64577b35bd09972a9b607 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Wed, 22 Jun 2011 17:12:02 -0700 Subject: [PATCH 31/98] remove some accidental print statements. --- test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test.py b/test.py index 8314bd794..75810f43e 100644 --- a/test.py +++ b/test.py @@ -126,7 +126,6 @@ class PDFTestHandler(BaseHTTPRequestHandler): # this just does Firefox for now class BrowserCommand(): def __init__(self, browserRecord): - print browserRecord self.name = browserRecord["name"] self.path = browserRecord["path"] self.type = browserRecord["type"] @@ -180,8 +179,6 @@ def setUp(options): print 'done' - print testBrowsers - for b in testBrowsers: State.taskResults[b.name] = { } for item in manifestList: From 541e4269ebfb5637c43ebb9a2897b9cb69fc48bd Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 18:37:58 -0700 Subject: [PATCH 32/98] actually wait for fonts to be loaded, and write out a log of failed eq comparisons --- test.py | 25 +++++++++++++++++- test_slave.html | 68 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/test.py b/test.py index 0c326ec09..26a86b6dd 100644 --- a/test.py +++ b/test.py @@ -9,6 +9,7 @@ def prompt(question): ANAL = True DEFAULT_MANIFEST_FILE = 'test_manifest.json' +EQLOG_FILE = 'eq.log' REFDIR = 'ref' TMPDIR = 'tmp' VERBOSE = False @@ -35,6 +36,7 @@ class State: numEqNoSnapshot = 0 numFBFFailures = 0 numLoadFailures = 0 + eqLog = None class Result: def __init__(self, snapshot, failure): @@ -190,6 +192,7 @@ def check(task, results, browser): def checkEq(task, results, browser): pfx = os.path.join(REFDIR, sys.platform, browser, task['id']) results = results[0] + taskId = task['id'] passed = True for page in xrange(len(results)): @@ -208,7 +211,21 @@ def checkEq(task, results, browser): eq = (ref == snapshot) if not eq: - print 'TEST-UNEXPECTED-FAIL | eq', task['id'], '| in', browser, '| rendering of page', page + 1, '!= reference rendering' + print 'TEST-UNEXPECTED-FAIL | eq', taskId, '| in', browser, '| rendering of page', page + 1, '!= reference rendering' + # XXX need to dump this always, somehow, when we have + # the reference repository + if State.masterMode: + if not State.eqLog: + State.eqLog = open(EQLOG_FILE, 'w') + eqLog = State.eqLog + + # NB: this follows the format of Mozilla reftest + # output so that we can reuse its reftest-analyzer + # script + print >>eqLog, 'REFTEST TEST-UNEXPECTED-FAIL |', browser +'-'+ taskId +'-page'+ str(page + 1), '| image comparison (==)' + print >>eqLog, 'REFTEST IMAGE 1 (TEST):', snapshot + print >>eqLog, 'REFTEST IMAGE 2 (REFERENCE):', ref + passed = False State.numEqFailures += 1 @@ -292,6 +309,12 @@ def main(args): masterMode = (args[0] == '-m') manifestFile = args[0] if not masterMode else manifestFile + + + masterMode = True + + + setUp(manifestFile, masterMode) server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler) diff --git a/test_slave.html b/test_slave.html index 06b911810..718e887e0 100644 --- a/test_slave.html +++ b/test_slave.html @@ -1,6 +1,7 @@ pdf.js test slave + @@ -31,7 +32,7 @@ function load() { stdout = document.getElementById("stdout"); log("Harness thinks this browser is '"+ browser +"'\n"); - log("Fetching manifest ..."); + log("Fetching manifest "+ manifestFile +"..."); var r = new XMLHttpRequest(); r.open("GET", manifestFile, false); @@ -81,38 +82,69 @@ function nextPage() { } failure = ''; - log(" drawing page "+ currentTask.pageNum +"..."); + log(" loading page "+ currentTask.pageNum +"... "); var ctx = canvas.getContext("2d"); clear(ctx); var fonts = []; + var fontsReady = true; var gfx = new CanvasGraphics(ctx); try { currentPage = pdfDoc.getPage(currentTask.pageNum); currentPage.compile(gfx, fonts); + + // Inspect fonts and translate the missing ones + var count = fonts.length; + for (var i = 0; i < count; ++i) { + var font = fonts[i]; + if (Fonts[font.name]) { + fontsReady = fontsReady && !Fonts[font.name].loading; + continue; + } + new Font(font.name, font.file, font.properties); + fontsReady = false; + } } catch(e) { failure = 'compile: '+ e.toString(); } - // TODO load fonts - setTimeout(function() { - if (!failure) { - try { - currentPage.display(gfx); - } catch(e) { - failure = 'render: '+ e.toString(); - } + var checkFontsLoadedIntervalTimer = null; + function checkFontsLoaded() { + for (var i = 0; i < count; i++) { + if (Fonts[font.name].loading) { + return; } - currentTask.taskDone = (currentTask.pageNum == pdfDoc.numPages - && (1 + currentTask.round) == currentTask.rounds); - sendTaskResult(canvas.toDataURL("image/png")); - log("done"+ (failure ? " (failed!)" : "") +"\n"); + } + window.clearInterval(checkFontsLoadedIntervalTimer); - ++currentTask.pageNum, nextPage(); - }, - 0 - ); + snapshotCurrentPage(gfx); + } + + if (failure || fontsReady) { + snapshotCurrentPage(gfx); + } else { + checkFontsLoadedIntervalTimer = setInterval(checkFontsLoaded, 10); + } +} + +function snapshotCurrentPage(gfx) { + log("done, snapshotting... "); + + if (!failure) { + try { + currentPage.display(gfx); + } catch(e) { + failure = 'render: '+ e.toString(); + } + } + + currentTask.taskDone = (currentTask.pageNum == pdfDoc.numPages + && (1 + currentTask.round) == currentTask.rounds); + sendTaskResult(canvas.toDataURL("image/png")); + log("done"+ (failure ? " (failed!)" : "") +"\n"); + + ++currentTask.pageNum, nextPage(); } function done() { From 90b24079d5e4201b3cc1c3814e37e2ef96725ec9 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 18:40:20 -0700 Subject: [PATCH 33/98] remove debugging code --- test.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test.py b/test.py index 26a86b6dd..2d4a3e0f2 100644 --- a/test.py +++ b/test.py @@ -309,12 +309,6 @@ def main(args): masterMode = (args[0] == '-m') manifestFile = args[0] if not masterMode else manifestFile - - - masterMode = True - - - setUp(manifestFile, masterMode) server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler) From 35b6569f5e0bd6c2050b6065108ce10454b40ad6 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 18:50:38 -0700 Subject: [PATCH 34/98] fix strict-mode violations in chrome --- fonts.js | 6 +++--- pdf.js | 50 +++++++++++++++++++++++++------------------------- test.py | 2 +- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/fonts.js b/fonts.js index a008dfce0..d5943b7a3 100644 --- a/fonts.js +++ b/fonts.js @@ -495,7 +495,7 @@ var Font = (function () { if (requiredTables.length && requiredTables[0] == "OS/2") { // Create a new file to hold the new version of our truetype with a new // header and new offsets - var ttf = Uint8Array(kMaxFontFileSize); + var ttf = new Uint8Array(kMaxFontFileSize); // The offsets object holds at the same time a representation of where // to write the table entry information about a table and another offset @@ -581,7 +581,7 @@ var Font = (function () { }, convert: function font_convert(name, font, properties) { - var otf = Uint8Array(kMaxFontFileSize); + var otf = new Uint8Array(kMaxFontFileSize); function createNameTable(name) { var names = [ @@ -1148,7 +1148,7 @@ var Type1Parser = function() { * The CFF class takes a Type1 file and wrap it into a 'Compact Font Format', * which itself embed Type2 charstrings. */ -const CFFStrings = [ +var CFFStrings = [ ".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand", "quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period", "slash","zero","one","two","three","four","five","six","seven","eight","nine", diff --git a/pdf.js b/pdf.js index 39e9e26df..5d9caf490 100644 --- a/pdf.js +++ b/pdf.js @@ -58,7 +58,7 @@ function bytesToString(bytes) { var Stream = (function() { function constructor(arrayBuffer, start, length, dict) { - this.bytes = Uint8Array(arrayBuffer); + this.bytes = new Uint8Array(arrayBuffer); this.start = start || 0; this.pos = this.start; this.end = (start + length) || this.bytes.length; @@ -125,7 +125,7 @@ var Stream = (function() { var StringStream = (function() { function constructor(str) { var length = str.length; - var bytes = Uint8Array(length); + var bytes = new Uint8Array(length); for (var n = 0; n < length; ++n) bytes[n] = str.charCodeAt(n); Stream.call(this, bytes); @@ -154,7 +154,7 @@ var DecodeStream = (function() { var size = 512; while (size < requested) size <<= 1; - var buffer2 = Uint8Array(size); + var buffer2 = new Uint8Array(size); for (var i = 0; i < current; ++i) buffer2[i] = buffer[i]; return this.buffer = buffer2; @@ -222,11 +222,11 @@ var DecodeStream = (function() { var FlateStream = (function() { - const codeLenCodeMap = Uint32Array([ + var codeLenCodeMap = new Uint32Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]); - const lengthDecode = Uint32Array([ + var lengthDecode = new Uint32Array([ 0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, @@ -234,7 +234,7 @@ var FlateStream = (function() { 0x00102, 0x00102, 0x00102 ]); - const distDecode = Uint32Array([ + var distDecode = new Uint32Array([ 0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, @@ -242,7 +242,7 @@ var FlateStream = (function() { 0xd4001, 0xd6001 ]); - const fixedLitCodeTab = [Uint32Array([ + var fixedLitCodeTab = [new Uint32Array([ 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, @@ -319,7 +319,7 @@ var FlateStream = (function() { 0x900ff ]), 9]; - const fixedDistCodeTab = [Uint32Array([ + var fixedDistCodeTab = [new Uint32Array([ 0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, @@ -410,7 +410,7 @@ var FlateStream = (function() { // build the table var size = 1 << maxLen; - var codes = Uint32Array(size); + var codes = new Uint32Array(size); for (var len = 1, code = 0, skip = 2; len <= maxLen; ++len, code <<= 1, skip <<= 1) { @@ -782,8 +782,8 @@ var Ascii85Stream = (function() { constructor.prototype = Object.create(DecodeStream.prototype); constructor.prototype.readBlock = function() { - const tildaCode = "~".charCodeAt(0); - const zCode = "z".charCodeAt(0); + var tildaCode = "~".charCodeAt(0); + var zCode = "z".charCodeAt(0); var str = this.str; var c = str.getByte(); @@ -1001,10 +1001,10 @@ var Lexer = (function() { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx ]; - const MIN_INT = (1<<31) | 0; - const MAX_INT = (MIN_INT - 1) | 0; - const MIN_UINT = 0; - const MAX_UINT = ((1<<30) * 4) - 1; + var MIN_INT = (1<<31) | 0; + var MAX_INT = (MIN_INT - 1) | 0; + var MIN_UINT = 0; + var MAX_UINT = ((1<<30) * 4) - 1; function ToHexDigit(ch) { if (ch >= "0" && ch <= "9") @@ -2031,7 +2031,7 @@ var PDFDoc = (function() { return constructor; })(); -const IDENTITY_MATRIX = [ 1, 0, 0, 1, 0, 0 ]; +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. @@ -2055,7 +2055,7 @@ var CanvasExtraState = (function() { return constructor; })(); -const Encodings = { +var Encodings = { get ExpertEncoding() { return shadow(this, "ExpertEncoding", [ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, "space","exclamsmall","Hungarumlautsmall",,"dollaroldstyle","dollarsuperior", @@ -2333,13 +2333,13 @@ var CanvasGraphics = (function() { }; } - const LINE_CAP_STYLES = [ "butt", "round", "square" ]; - const LINE_JOIN_STYLES = [ "miter", "round", "bevel" ]; - const NORMAL_CLIP = {}; - const EO_CLIP = {}; + var LINE_CAP_STYLES = [ "butt", "round", "square" ]; + var LINE_JOIN_STYLES = [ "miter", "round", "bevel" ]; + var NORMAL_CLIP = {}; + var EO_CLIP = {}; // Used for tiling patterns - const PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; + var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; constructor.prototype = { translateFont: function(fontDict, xref, resources) { @@ -2874,7 +2874,7 @@ var CanvasGraphics = (function() { var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); var patternDict = IsStream(pattern) ? pattern.dict : pattern; - const types = [null, this.tilingFill, + var types = [null, this.tilingFill, function() { TODO("Shading Patterns"); }]; var typeNum = patternDict.get("PatternType"); var patternFn = types[typeNum]; @@ -3023,7 +3023,7 @@ var CanvasGraphics = (function() { if (background) TODO("handle background colors"); - const types = [null, + var types = [null, this.fillFunctionShading, this.fillAxialShading, this.fillRadialShading]; @@ -3460,7 +3460,7 @@ var PDFFunction = (function() { if (!dict) dict = fn; - const types = [this.constructSampled, + var types = [this.constructSampled, null, this.constructInterpolated, this.constructStiched, diff --git a/test.py b/test.py index 2d4a3e0f2..1557714d6 100644 --- a/test.py +++ b/test.py @@ -17,7 +17,7 @@ VERBOSE = False MIMEs = { '.css': 'text/css', '.html': 'text/html', - '.js': 'application/json', + '.js': 'application/javascript', '.json': 'application/json', '.pdf': 'application/pdf', '.xhtml': 'application/xhtml+xml', From 553250a5d2579769723407604f390eb33c6be8d8 Mon Sep 17 00:00:00 2001 From: sbarman Date: Wed, 22 Jun 2011 19:20:03 -0700 Subject: [PATCH 35/98] fake stream for unimplemented filters --- pdf.js | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/pdf.js b/pdf.js index 39e9e26df..cd3cdfba5 100644 --- a/pdf.js +++ b/pdf.js @@ -220,6 +220,43 @@ var DecodeStream = (function() { })(); +var FakeStream = (function() { + function constructor(stream) { + this.dict = stream.dict; + DecodeStream.call(this); + }; + + constructor.prototype = Object.create(DecodeStream.prototype); + constructor.prototype.readBlock = function() { + var bufferLength = this.bufferLength; + bufferLength += 1024; + var buffer = this.ensureBuffer(bufferLength); + this.bufferLength = bufferLength; + }; + constructor.prototype.getBytes = function(length) { + var pos = this.pos; + + if (length) { + this.ensureBuffer(pos + length); + var end = pos + length; + + while (!this.eof && this.bufferLength < end) + this.readBlock(); + + var bufEnd = this.bufferLength; + if (end > bufEnd) + end = bufEnd; + } else { + this.eof = true; + var end = this.bufferLength; + } + + this.pos = end; + return this.buffer.subarray(pos, end) + }; + + return constructor; +})(); var FlateStream = (function() { const codeLenCodeMap = Uint32Array([ @@ -597,9 +634,6 @@ var PredictorStream = (function() { 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; @@ -660,9 +694,6 @@ var PredictorStream = (function() { this.bufferLength += rowBytes; }; constructor.prototype.readBlockPng = function() { - var buffer = this.buffer; - var pos = this.pos; - var rowBytes = this.rowBytes; var pixBytes = this.pixBytes; @@ -1448,6 +1479,9 @@ var Parser = (function() { return new JpegStream(bytes, stream.dict); } else if (name == "ASCII85Decode") { return new Ascii85Stream(stream); + } else if (name == "CCITTFaxDecode") { + TODO("implement fax stream"); + return new FakeStream(stream); } else { error("filter '" + name + "' not supported yet"); } From 7b454d403bdce69d4be1feeaa59932cf6491bc31 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Thu, 23 Jun 2011 05:38:05 +0200 Subject: [PATCH 36/98] Use the real content of the string to measure it's size --- pdf.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pdf.js b/pdf.js index 5d9caf490..addcea6de 100644 --- a/pdf.js +++ b/pdf.js @@ -2787,7 +2787,9 @@ var CanvasGraphics = (function() { this.ctx.transform.apply(this.ctx, this.current.textMatrix); this.ctx.scale(1, -1); this.ctx.translate(0, -2 * this.current.y); - this.ctx.fillText(Fonts.charsToUnicode(text), this.current.x, this.current.y); + + text = Fonts.charsToUnicode(text); + this.ctx.fillText(text, this.current.x, this.current.y); this.current.x += this.ctx.measureText(text).width; this.ctx.restore(); From 0d4be031b98926a27b7a6ceec70ab13e596509ee Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Wed, 22 Jun 2011 20:47:37 -0700 Subject: [PATCH 37/98] fix bug in testing multiple browsers --- test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test.py b/test.py index 1557714d6..9eab0e80e 100644 --- a/test.py +++ b/test.py @@ -117,8 +117,8 @@ def setUp(manifestFile, masterMode): assert not os.path.isdir(TMPDIR) testBrowsers = [ b for b in - ( 'firefox5', ) -#'chrome12', 'chrome13', 'firefox4', 'firefox6','opera11' ): + ( 'firefox5', 'firefox6', ) +#'chrome12', 'chrome13', 'firefox4', 'opera11' ): if os.access(b, os.R_OK | os.X_OK) ] mf = open(manifestFile) @@ -152,7 +152,7 @@ def setUp(manifestFile, masterMode): taskResults.append([ ]) State.taskResults[b][id] = taskResults - State.remaining = len(manifestList) + State.remaining = len(testBrowsers) * len(manifestList) for b in testBrowsers: print 'Launching', b From 5ae4857efd8ad907dedb6d8817affcae38818296 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Thu, 23 Jun 2011 09:05:51 -0700 Subject: [PATCH 38/98] Add new test directories. --- test/.gitignore | 0 test/pdfs/.gitignore | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/.gitignore create mode 100644 test/pdfs/.gitignore diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore new file mode 100644 index 000000000..e69de29bb From 0d5f92deedb16d3e8b85fc2c6dcf753f572cedd2 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Thu, 23 Jun 2011 09:10:06 -0700 Subject: [PATCH 39/98] Move some files around. --- browser_manifest.json => test/browser_manifest.json | 0 {tests => test/pdfs}/canvas.pdf | Bin {tests => test/pdfs}/pdf.pdf.link | 0 {tests => test/pdfs}/tracemonkey.pdf | Bin test.py => test/test.py | 0 test_manifest.json => test/test_manifest.json | 0 test_slave.html => test/test_slave.html | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename browser_manifest.json => test/browser_manifest.json (100%) rename {tests => test/pdfs}/canvas.pdf (100%) rename {tests => test/pdfs}/pdf.pdf.link (100%) rename {tests => test/pdfs}/tracemonkey.pdf (100%) rename test.py => test/test.py (100%) rename test_manifest.json => test/test_manifest.json (100%) rename test_slave.html => test/test_slave.html (100%) diff --git a/browser_manifest.json b/test/browser_manifest.json similarity index 100% rename from browser_manifest.json rename to test/browser_manifest.json diff --git a/tests/canvas.pdf b/test/pdfs/canvas.pdf similarity index 100% rename from tests/canvas.pdf rename to test/pdfs/canvas.pdf diff --git a/tests/pdf.pdf.link b/test/pdfs/pdf.pdf.link similarity index 100% rename from tests/pdf.pdf.link rename to test/pdfs/pdf.pdf.link diff --git a/tests/tracemonkey.pdf b/test/pdfs/tracemonkey.pdf similarity index 100% rename from tests/tracemonkey.pdf rename to test/pdfs/tracemonkey.pdf diff --git a/test.py b/test/test.py similarity index 100% rename from test.py rename to test/test.py diff --git a/test_manifest.json b/test/test_manifest.json similarity index 100% rename from test_manifest.json rename to test/test_manifest.json diff --git a/test_slave.html b/test/test_slave.html similarity index 100% rename from test_slave.html rename to test/test_slave.html From fd5412ac1270ade070e6e002216577ba3c99a0d7 Mon Sep 17 00:00:00 2001 From: sbarman Date: Thu, 23 Jun 2011 09:41:59 -0700 Subject: [PATCH 40/98] fix for uncompressed flatestream blocks --- pdf.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pdf.js b/pdf.js index d940c4f8b..326c31234 100644 --- a/pdf.js +++ b/pdf.js @@ -479,17 +479,17 @@ var FlateStream = (function() { 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 + var bytes = this.bytes; + var bytesPos = this.bytesPos; + var b; + if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad block header in flate stream"); var blockLen = b; @@ -502,18 +502,24 @@ var FlateStream = (function() { if (typeof (b = bytes[bytesPos++]) == "undefined") error("Bad block header in flate stream"); check |= (b << 8); - if (check != (~this.blockLen & 0xffff)) + if (check != (~blockLen & 0xffff)) error("Bad uncompressed block length in flate stream"); + + this.codeBuf = 0; + this.codeSize = 0; + var bufferLength = this.bufferLength; var buffer = this.ensureBuffer(bufferLength + blockLen); - this.bufferLength = bufferLength + blockLen; - for (var n = bufferLength; n < blockLen; ++n) { + var end = bufferLength + blockLen; + this.bufferLength = end; + for (var n = bufferLength; n < end; ++n) { if (typeof (b = bytes[bytesPos++]) == "undefined") { this.eof = true; break; } buffer[n] = b; } + this.bytesPos = bytesPos; return; } From 0a8e1110a33cbe3c7f4be3ad259647a4fc69485f Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Thu, 23 Jun 2011 09:48:34 -0700 Subject: [PATCH 41/98] Modify paths of web resources to work with test resources more buried. --- test/browser_manifest.json | 2 +- test/test.py | 18 +++++++++--------- test/test_manifest.json | 8 ++++---- test/test_slave.html | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/test/browser_manifest.json b/test/browser_manifest.json index 79115d1a4..f11c97c11 100644 --- a/test/browser_manifest.json +++ b/test/browser_manifest.json @@ -1,6 +1,6 @@ [ { - "name":"Firefox 5", + "name":"firefox5", "path":"/Applications/Firefox.app", "type":"firefox" } diff --git a/test/test.py b/test/test.py index 75810f43e..acdd0c552 100644 --- a/test/test.py +++ b/test/test.py @@ -5,6 +5,9 @@ from urlparse import urlparse USAGE_EXAMPLE = "%prog" +# The local web server uses the git repo as the document root. +DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),"..")) + ANAL = True DEFAULT_MANIFEST_FILE = 'test_manifest.json' DEFAULT_BROWSER_MANIFEST_FILE = 'browser_manifest.json' @@ -73,15 +76,14 @@ class PDFTestHandler(BaseHTTPRequestHandler): def do_GET(self): url = urlparse(self.path) + print "GET",url # Ignore query string path, _ = url.path, url.query - cwd = os.getcwd() - path = os.path.abspath(os.path.realpath(cwd + os.sep + path)) - cwd = os.path.abspath(cwd) - prefix = os.path.commonprefix(( path, cwd )) + path = os.path.abspath(os.path.realpath(DOC_ROOT + os.sep + path)) + prefix = os.path.commonprefix(( path, DOC_ROOT )) _, ext = os.path.splitext(path) - if not (prefix == cwd + if not (prefix == DOC_ROOT and os.path.isfile(path) and ext in MIMEs): self.send_error(404) @@ -146,7 +148,7 @@ def makeBrowserCommands(browserManifestFile): def setUp(options): # Only serve files from a pdf.js clone - assert not ANAL or os.path.isfile('pdf.js') and os.path.isdir('.git') + assert not ANAL or os.path.isfile('../pdf.js') and os.path.isdir('../.git') State.masterMode = options.masterMode if options.masterMode and os.path.isdir(TMPDIR): @@ -191,13 +193,11 @@ def setUp(options): State.remaining = len(manifestList) - - for b in testBrowsers: print 'Launching', b.name qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) subprocess.Popen(( os.path.abspath(os.path.realpath(b.path)), - 'http://localhost:8080/test_slave.html?'+ qs)) + 'http://localhost:8080/test/test_slave.html?'+ qs)) def check(task, results, browser): diff --git a/test/test_manifest.json b/test/test_manifest.json index 036b7aafc..e4a7ada81 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -1,21 +1,21 @@ [ { "id": "tracemonkey-eq", - "file": "tests/tracemonkey.pdf", + "file": "pdfs/tracemonkey.pdf", "rounds": 1, "type": "eq" }, { "id": "tracemonkey-fbf", - "file": "tests/tracemonkey.pdf", + "file": "pdfs/tracemonkey.pdf", "rounds": 2, "type": "fbf" }, { "id": "html5-canvas-cheat-sheet-load", - "file": "tests/canvas.pdf", + "file": "pdfs/canvas.pdf", "rounds": 1, "type": "load" }, { "id": "pdfspec-load", - "file": "tests/pdf.pdf", + "file": "pdfs/pdf.pdf", "link": true, "rounds": 1, "type": "load" diff --git a/test/test_slave.html b/test/test_slave.html index 06b911810..80e374709 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -1,9 +1,9 @@ pdf.js test slave - - - + + + + + + + +
+ + + + + + -- + +
+ +
+ + + +
+ + + diff --git a/worker.js b/worker.js new file mode 100644 index 000000000..fdc762afd --- /dev/null +++ b/worker.js @@ -0,0 +1,113 @@ +"use strict"; + +function log() { + var args = Array.prototype.slice.call(arguments); + postMessage("log"); + postMessage(JSON.stringify(args)) +} + +var console = { + log: log +} + +importScripts("canvas_proxy.js"); +importScripts("pdf.js"); +importScripts("fonts.js"); +importScripts("glyphlist.js") + +// var array = new Uint8Array(2); +// array[0] = 1; +// array[1] = 300; +// postMessage(array); + +var timer = null; +function tic() { + timer = Date.now(); +} + +function toc(msg) { + log("Took ", (Date.now() - timer)); + timer = null; +} + + +var canvas = new CanvasProxy(1224, 1584); +// canvas.moveTo(0, 10); +// canvas.lineTo(0, 20); +// canvas.lineTo(500, 500); +// canvas.flush(); +// canvas.stroke(); +// canvas.flush(); +log("test"); + +onmessage = function(event) { + var data = event.data; + var pdfDocument = new PDFDoc(new Stream(data)); + var numPages = pdfDocument.numPages; + + tic(); + // Let's try to render the first page... + var page = pdfDocument.getPage(1); + + // 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 = []; + + var gfx = new CanvasGraphics(canvas); + page.compile(gfx, fonts); + toc("compiled page"); + + // + var fontsReady = true; + // Inspect fonts and translate the missing one + var count = fonts.length; + for (var i = 0; i < count; i++) { + var font = fonts[i]; + if (Fonts[font.name]) { + fontsReady = fontsReady && !Fonts[font.name].loading; + continue; + } + + new Font(font.name, font.file, font.properties); + fontsReady = false; + } + + function delayLoadFont() { + for (var i = 0; i < count; i++) { + if (Fonts[font.name].loading) + return; + } + clearInterval(pageInterval); + page.display(gfx); + + canvas.flush(); + }; + + if (fontsReady) { + delayLoadFont(); + } else { + pageInterval = setInterval(delayLoadFont, 10); + } + postMessage(page.code.src); +} + +// function open(url) { +// var req = new XMLHttpRequest(); +// req.open("GET", url); +// // req.responseType = "arraybuffer"; +// req.expected = 0;//(document.URL.indexOf("file:") == 0) ? 0 : 200; +// req.onreadystatechange = function() { +// postMessage("loaded"); +// if (req.readyState == 4 && req.status == req.expected) { +// var data = req.mozResponseArrayBuffer || req.mozResponse || +// req.responseArrayBuffer || req.response; +// pdfDocument = new PDFDoc(new Stream(data)); +// numPages = pdfDocument.numPages; +// // document.getElementById("numPages").innerHTML = numPages.toString(); +// // goToPage(pageNum); +// } +// }; +// req.send(null); +// } +// +// open("compressed.tracemonkey-pldi-09.pdf") \ No newline at end of file From a8dcb0dcd68635a41875617d33b700d4a9467e1e Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 22 Jun 2011 01:28:17 +0200 Subject: [PATCH 52/98] Most working, but once you add the font-css file to the web page, there is no font drawn at all --- canvas_proxy.js | 12 +++- fonts.js | 146 +++++++++++++++++++++++++-------------------- pdf.js | 34 +++++++++-- viewer_worker.html | 95 +++++++++++++++++++++++++---- worker.js | 56 +++++++++-------- 5 files changed, 235 insertions(+), 108 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index 1b100beae..433166aac 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -42,11 +42,17 @@ function CanvasProxy(width, height) { "stroke", "clip", "measureText", - "isPointInPath" + "isPointInPath", + + "$setCurrentX", + "$addCurrentX", + "$saveCurrentX", + "$restoreCurrentX", + "$showText" ]; function buildFuncCall(name) { return function() { - console.log("funcCall", name) + // console.log("funcCall", name) stack.push([name, Array.prototype.slice.call(arguments)]); } } @@ -103,6 +109,8 @@ function CanvasProxy(width, height) { } CanvasProxy.prototype.flush = function() { + // postMessage("log"); + // postMessage(JSON.stringify([this.$stack.length])); postMessage("canvas_proxy_stack"); postMessage(JSON.stringify(this.$stack)); this.$stack.length = 0; diff --git a/fonts.js b/fonts.js index d5943b7a3..8c0abbcec 100644 --- a/fonts.js +++ b/fonts.js @@ -759,91 +759,109 @@ var Font = (function () { var data = this.font; var fontName = this.name; + var isWorker = (typeof window == "undefined"); /** Hack begin */ + if (!isWorker) { - // 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 - var canvas = document.createElement("canvas"); - var style = "border: 1px solid black; position:absolute; top: " + - (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px"; - canvas.setAttribute("style", style); - canvas.setAttribute("width", 340); - canvas.setAttribute("heigth", 100); - document.body.appendChild(canvas); + // 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 + var canvas = document.createElement("canvas"); + var style = "border: 1px solid black; position:absolute; top: " + + (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px"; + canvas.setAttribute("style", style); + canvas.setAttribute("width", 340); + canvas.setAttribute("heigth", 100); + document.body.appendChild(canvas); - // Get the font size canvas think it will be for 'spaces' - var ctx = canvas.getContext("2d"); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - var testString = " "; + // Get the font size canvas think it will be for 'spaces' + var ctx = canvas.getContext("2d"); + ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + var testString = " "; - // When debugging use the characters provided by the charsets to visually - // see what's happening instead of 'spaces' - var debug = false; - if (debug) { - var name = document.createElement("font"); - name.setAttribute("style", "position: absolute; left: 20px; top: " + - (100 * fontCount + 60) + "px"); - name.innerHTML = fontName; - document.body.appendChild(name); + // When debugging use the characters provided by the charsets to visually + // see what's happening instead of 'spaces' + var debug = false; + if (debug) { + var name = document.createElement("font"); + name.setAttribute("style", "position: absolute; left: 20px; top: " + + (100 * fontCount + 60) + "px"); + name.innerHTML = fontName; + document.body.appendChild(name); - // Retrieve font charset - var charset = Fonts[fontName].properties.charset || []; + // Retrieve font charset + var charset = Fonts[fontName].properties.charset || []; - // if the charset is too small make it repeat a few times - var count = 30; - while (count-- && charset.length <= 30) - charset = charset.concat(charset.slice()); + // if the charset is too small make it repeat a few times + var count = 30; + while (count-- && charset.length <= 30) + charset = charset.concat(charset.slice()); - for (var i = 0; i < charset.length; i++) { - var unicode = GlyphsUnicode[charset[i]]; - if (!unicode) - continue; - testString += String.fromCharCode(unicode); - } + for (var i = 0; i < charset.length; i++) { + var unicode = GlyphsUnicode[charset[i]]; + if (!unicode) + continue; + testString += String.fromCharCode(unicode); + } - ctx.fillText(testString, 20, 20); - } + ctx.fillText(testString, 20, 20); + } - // Periodicaly check for the width of the testString, it will be - // different once the real font has loaded - var textWidth = ctx.measureText(testString).width; + // Periodicaly check for the width of the testString, it will be + // different once the real font has loaded + var textWidth = ctx.measureText(testString).width; - var interval = window.setInterval(function canvasInterval(self) { - this.start = this.start || Date.now(); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + var interval = window.setInterval(function canvasInterval(self) { + this.start = this.start || Date.now(); + ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - // For some reasons the font has not loaded, so mark it loaded for the - // page to proceed but cry - if ((Date.now() - this.start) >= kMaxWaitForFontFace) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - warn("Is " + fontName + " for charset: " + charset + " loaded?"); - this.start = 0; - } else if (textWidth != ctx.measureText(testString).width) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - this.start = 0; - } + // For some reasons the font has not loaded, so mark it loaded for the + // page to proceed but cry + if ((Date.now() - this.start) >= kMaxWaitForFontFace) { + window.clearInterval(interval); + Fonts[fontName].loading = false; + warn("Is " + fontName + " for charset: " + charset + " loaded?"); + this.start = 0; + } else if (textWidth != ctx.measureText(testString).width) { + window.clearInterval(interval); + Fonts[fontName].loading = false; + this.start = 0; + } - if (debug) - ctx.fillText(testString, 20, 50); - }, 30, this); + if (debug) + ctx.fillText(testString, 20, 50); + }, 30, this); + } /** Hack end */ - + // // Get the base64 encoding of the binary font data var str = ""; var length = data.length; for (var i = 0; i < length; ++i) str += String.fromCharCode(data[i]); - var base64 = window.btoa(str); + if (isWorker) { + postMessage("font"); + postMessage(JSON.stringify({ + str: str, + mimetype: this.mimetype, + fontName: fontName, + })); - // Add the @font-face rule to the document - var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; - var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; - var styleSheet = document.styleSheets[0]; - styleSheet.insertRule(rule, styleSheet.length); + setTimeout(function() { + Fonts[fontName].loading = false; + }, kMaxWaitForFontFace); + } else { + var base64 = window.btoa(str); + + // Add the @font-face rule to the document + var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.length); + console.log("added font", fontName); + console.log(rule); + } } }; diff --git a/pdf.js b/pdf.js index 09d1c874e..80e9c1930 100644 --- a/pdf.js +++ b/pdf.js @@ -2674,12 +2674,18 @@ var CanvasGraphics = (function() { }, save: function() { this.ctx.save(); + if (this.ctx.$saveCurrentX) { + this.ctx.$saveCurrentX(); + } this.stateStack.push(this.current); this.current = new CanvasExtraState(); }, restore: function() { var prev = this.stateStack.pop(); if (prev) { + if (this.ctx.$restoreCurrentX) { + this.ctx.$restoreCurrentX(); + } this.current = prev; this.ctx.restore(); } @@ -2760,6 +2766,9 @@ var CanvasGraphics = (function() { // Text beginText: function() { this.current.textMatrix = IDENTITY_MATRIX; + if (this.ctx.$setCurrentX) { + this.ctx.$setCurrentX(0) + } this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; }, @@ -2814,6 +2823,9 @@ var CanvasGraphics = (function() { moveText: function (x, y) { this.current.x = this.current.lineX += x; this.current.y = this.current.lineY += y; + if (this.ctx.$setCurrentX) { + this.ctx.$setCurrentX(this.current.x) + } }, setLeadingMoveText: function(x, y) { this.setLeading(-y); @@ -2821,6 +2833,10 @@ var CanvasGraphics = (function() { }, setTextMatrix: function(a, b, c, d, e, f) { this.current.textMatrix = [ a, b, c, d, e, f ]; + + if (this.ctx.$setCurrentX) { + this.$setCurrentX(0) + } this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; }, @@ -2831,11 +2847,15 @@ var CanvasGraphics = (function() { this.ctx.save(); this.ctx.transform.apply(this.ctx, this.current.textMatrix); this.ctx.scale(1, -1); - this.ctx.translate(0, -2 * this.current.y); - text = Fonts.charsToUnicode(text); - this.ctx.fillText(text, this.current.x, this.current.y); - this.current.x += this.ctx.measureText(text).width; + if (this.ctx.$showText) { + this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text)); + } else { + console.log(text, this.current.x); + text = Fonts.charsToUnicode(text); + this.ctx.fillText(text, 0, 0); + this.current.x += this.ctx.measureText(text).width; + } this.ctx.restore(); }, @@ -2843,7 +2863,11 @@ var CanvasGraphics = (function() { for (var i = 0; i < arr.length; ++i) { var e = arr[i]; if (IsNum(e)) { - this.current.x -= e * 0.001 * this.current.fontSize; + if (this.ctx.$addCurrentX) { + this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize) + } else { + this.current.x -= e * 0.001 * this.current.fontSize; + } } else if (IsString(e)) { this.showText(e); } else { diff --git a/viewer_worker.html b/viewer_worker.html index f9e1f0b32..dde249e55 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -11,11 +11,63 @@ var myWorker = new Worker('worker.js'); const WAIT = 0; const CANVAS_PROXY_STACK = 1; const LOG = 2; +const FONT = 3; + +var currentX = 0; +var currentXStack = []; +var special = { + "$setCurrentX": function(value) { + currentX = value; + }, + + "$addCurrentX": function(value) { + currentX += value; + }, + + "$saveCurrentX": function() { + currentXStack.push(currentX); + }, + + "$restoreCurrentX": function() { + currentX = currentXStack.pop(); + }, + + "$showText": function(y, text) { + console.log(text, currentX, y, this.measureText(text).width); + + this.translate(currentX, -1 * y); + this.fillText(text, 0, 0); + currentX += this.measureText(text).width; + } +} + +function renderProxyCanvas(stack) { + // for (var i = 0; i < stack.length; i++) { + for (var i = 0; i < 1000; i++) { + var opp = stack[i]; + if (opp[0] == "$") { + // console.log("set property", opp[1], opp[2]); + if (opp[1] == "font") { + ctx[opp[1]] = opp[2]; + // console.log("font", opp[2]); + } else { + ctx[opp[1]] = opp[2]; + } + + } else if (opp[0] in special) { + // console.log("sepcial", opp[0], opp[1]) + special[opp[0]].apply(ctx, opp[1]); + } else { + // console.log("execute", opp[0], opp[1]); + ctx[opp[0]].apply(ctx, opp[1]); + } + } +} var onMessageState = WAIT; myWorker.onmessage = function(event) { var data = event.data; - console.log("onMessageRaw", data); + // console.log("onMessageRaw", data); switch (onMessageState) { case WAIT: if (typeof data != "string") { @@ -28,11 +80,31 @@ myWorker.onmessage = function(event) { case "canvas_proxy_stack": onMessageState = CANVAS_PROXY_STACK; return; + case "font": + onMessageState = FONT; + return; default: throw "unkown state: " + data } break; + case FONT: + data = JSON.parse(data); + var base64 = window.btoa(data.str); + + // Add the @font-face rule to the document + var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + + // ONCE you uncomment this, there is no font painted at all :( + // styleSheet.insertRule(rule, styleSheet.length); + + console.log("added font", data.fontName); + // console.log(rule); + onMessageState = WAIT; + break; + case LOG: console.log.apply(console, JSON.parse(data)); onMessageState = WAIT; @@ -40,17 +112,14 @@ myWorker.onmessage = function(event) { case CANVAS_PROXY_STACK: var stack = JSON.parse(data); - for (var i = 0; i < stack.length; i++) { - var opp = stack[i]; - if (opp[0] == "$") { - console.log("set property", opp[1], opp[2]); - ctx[opp[1]] = opp[2]; - } else { - console.log("execute", opp[0], opp[1]); - ctx[opp[0]].apply(ctx, opp[1]); - } - } + console.log("canvas stack", stack.length) + // console.log(stack.length); onMessageState = WAIT; + // return; + + setTimeout(function() { + renderProxyCanvas(stack); + }, 2000); break; } } @@ -75,6 +144,10 @@ function open(url) { window.onload = function() { var ctx = window.ctx = document.getElementById("canvas").getContext("2d"); + ctx.save(); + ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); // for (var name in ctx) { // if (!(ctx[name] instanceof Function)) { // console.log('"' + name + '": "' + ctx[name] + '",'); diff --git a/worker.js b/worker.js index fdc762afd..9ee9409bd 100644 --- a/worker.js +++ b/worker.js @@ -40,6 +40,7 @@ var canvas = new CanvasProxy(1224, 1584); // canvas.flush(); log("test"); +var pageInterval; onmessage = function(event) { var data = event.data; var pdfDocument = new PDFDoc(new Stream(data)); @@ -59,36 +60,39 @@ onmessage = function(event) { // var fontsReady = true; - // Inspect fonts and translate the missing one - var count = fonts.length; - for (var i = 0; i < count; i++) { - var font = fonts[i]; - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; - } + // Inspect fonts and translate the missing one + var count = fonts.length; + for (var i = 0; i < count; i++) { + var font = fonts[i]; + if (Fonts[font.name]) { + fontsReady = fontsReady && !Fonts[font.name].loading; + continue; + } - new Font(font.name, font.file, font.properties); - fontsReady = false; - } + new Font(font.name, font.file, font.properties); + fontsReady = false; + } - function delayLoadFont() { - for (var i = 0; i < count; i++) { - if (Fonts[font.name].loading) - return; - } - clearInterval(pageInterval); - page.display(gfx); + // function delayLoadFont() { + // for (var i = 0; i < count; i++) { + // if (Fonts[font.name].loading) + // return; + // } + // clearInterval(pageInterval); + // page.display(gfx); + // + // log("flush"); + // canvas.flush(); + // }; - canvas.flush(); - }; + // if (fontsReady) { + // delayLoadFont(); + // } else { + // pageInterval = setInterval(delayLoadFont, 10); + // } - if (fontsReady) { - delayLoadFont(); - } else { - pageInterval = setInterval(delayLoadFont, 10); - } - postMessage(page.code.src); + page.display(gfx); + canvas.flush(); } // function open(url) { From d9424a7135820fd902d50cadfd2150c6b6a1bdd7 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 22 Jun 2011 09:15:55 +0200 Subject: [PATCH 53/98] Make fonts getting loaded by a very nasty hack --- fonts.js | 6 +----- pdf.js | 6 +++--- viewer_worker.html | 43 ++++++++++++++++++++++++++++--------------- worker.js | 2 +- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/fonts.js b/fonts.js index 8c0abbcec..c7f54edf9 100644 --- a/fonts.js +++ b/fonts.js @@ -844,13 +844,9 @@ var Font = (function () { postMessage("font"); postMessage(JSON.stringify({ str: str, - mimetype: this.mimetype, fontName: fontName, + mimetype: this.mimetype })); - - setTimeout(function() { - Fonts[fontName].loading = false; - }, kMaxWaitForFontFace); } else { var base64 = window.btoa(str); diff --git a/pdf.js b/pdf.js index 80e9c1930..64b99a33e 100644 --- a/pdf.js +++ b/pdf.js @@ -2835,7 +2835,7 @@ var CanvasGraphics = (function() { this.current.textMatrix = [ a, b, c, d, e, f ]; if (this.ctx.$setCurrentX) { - this.$setCurrentX(0) + this.ctx.$setCurrentX(0) } this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; @@ -2851,9 +2851,9 @@ var CanvasGraphics = (function() { if (this.ctx.$showText) { this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text)); } else { - console.log(text, this.current.x); text = Fonts.charsToUnicode(text); - this.ctx.fillText(text, 0, 0); + this.ctx.translate(this.current.x, -1 * this.current.y); + this.ctx.fillText(Fonts.charsToUnicode(text), 0, 0); this.current.x += this.ctx.measureText(text).width; } diff --git a/viewer_worker.html b/viewer_worker.html index dde249e55..930fb6cd5 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -8,10 +8,6 @@ var myWorker = new Worker('worker.js'); // array[0] = 1; // array[1] = 300; // -const WAIT = 0; -const CANVAS_PROXY_STACK = 1; -const LOG = 2; -const FONT = 3; var currentX = 0; var currentXStack = []; @@ -33,7 +29,7 @@ var special = { }, "$showText": function(y, text) { - console.log(text, currentX, y, this.measureText(text).width); + // console.log(text, currentX, y, this.measureText(text).width); this.translate(currentX, -1 * y); this.fillText(text, 0, 0); @@ -41,14 +37,16 @@ var special = { } } +var gStack; function renderProxyCanvas(stack) { - // for (var i = 0; i < stack.length; i++) { - for (var i = 0; i < 1000; i++) { + for (var i = 0; i < stack.length; i++) { + // for (var i = 0; i < 1000; i++) { var opp = stack[i]; if (opp[0] == "$") { // console.log("set property", opp[1], opp[2]); if (opp[1] == "font") { ctx[opp[1]] = opp[2]; + // ctx.font = "10px 'Verdana Bold Italic'"; // console.log("font", opp[2]); } else { ctx[opp[1]] = opp[2]; @@ -64,7 +62,15 @@ function renderProxyCanvas(stack) { } } +const WAIT = 0; +const CANVAS_PROXY_STACK = 1; +const LOG = 2; +const FONT = 3; + var onMessageState = WAIT; +var fontStr = null; +var first = true; +var intervals = []; myWorker.onmessage = function(event) { var data = event.data; // console.log("onMessageRaw", data); @@ -96,12 +102,15 @@ myWorker.onmessage = function(event) { var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.length); - // ONCE you uncomment this, there is no font painted at all :( - // styleSheet.insertRule(rule, styleSheet.length); + // *HACK*: this makes the font get loaded on the page. WTF? We + // really have to set the fonts a few time... + var interval = setInterval(function() { + ctx.font = "bold italic 20px " + data.fontName + ", Symbol, Arial"; + }, 10); + intervals.push(interval); - console.log("added font", data.fontName); - // console.log(rule); onMessageState = WAIT; break; @@ -112,14 +121,18 @@ myWorker.onmessage = function(event) { case CANVAS_PROXY_STACK: var stack = JSON.parse(data); + gStack = stack; console.log("canvas stack", stack.length) - // console.log(stack.length); - onMessageState = WAIT; - // return; + // Shedule a timeout. Hoping the fonts are loaded after 100ms. setTimeout(function() { + // Remove all setIntervals to make the font load. + intervals.forEach(function(inter) { + clearInterval(inter); + }); renderProxyCanvas(stack); - }, 2000); + }, 100); + onMessageState = WAIT; break; } } diff --git a/worker.js b/worker.js index 9ee9409bd..6d34a9d62 100644 --- a/worker.js +++ b/worker.js @@ -48,7 +48,7 @@ onmessage = function(event) { tic(); // Let's try to render the first page... - var page = pdfDocument.getPage(1); + var page = pdfDocument.getPage(8); // page.compile will collect all fonts for us, once we have loaded them // we can trigger the actual page rendering with page.display From b151516416cf2bc84013cb39eeb1e361701e9892 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 22 Jun 2011 09:46:11 +0200 Subject: [PATCH 54/98] Introduce ImageCanvas to handle canvas rendering in WebWorker --- canvas_proxy.js | 7 ++++--- fonts.js | 2 -- pdf.js | 41 +++++++++++++++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index 433166aac..ccc95c74a 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -14,9 +14,9 @@ function CanvasProxy(width, height) { "arc", "fillText", "strokeText", - "drawImage", - "getImageData", - "putImageData", + // "drawImage", + // "getImageData", + // "putImageData", "createImageData", "drawWindow", "save", @@ -50,6 +50,7 @@ function CanvasProxy(width, height) { "$restoreCurrentX", "$showText" ]; + function buildFuncCall(name) { return function() { // console.log("funcCall", name) diff --git a/fonts.js b/fonts.js index c7f54edf9..9c9201b72 100644 --- a/fonts.js +++ b/fonts.js @@ -855,8 +855,6 @@ var Font = (function () { var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.length); - console.log("added font", fontName); - console.log(rule); } } }; diff --git a/pdf.js b/pdf.js index 64b99a33e..273550084 100644 --- a/pdf.js +++ b/pdf.js @@ -2269,14 +2269,32 @@ var Encodings = { } }; +function ImageCanvas(width, height) { + var tmpCanvas = this.canvas = document.createElement("canvas"); + tmpCanvas.width = width; + tmpCanvas.height = height; + + this.ctx = tmpCanvas.getContext("2d"); + this.imgData = this.ctx.getImageData(0, 0, width, height); +} + +ImageCanvas.prototype.putImageData = function(imgData) { + this.ctx.putImageData(imgData, 0, 0); +} + +ImageCanvas.prototype.getCanvas = function() { + return this.canvas; +} + var CanvasGraphics = (function() { - function constructor(canvasCtx) { + function constructor(canvasCtx, imageCanvas) { this.ctx = canvasCtx; this.current = new CanvasExtraState(); this.stateStack = [ ]; this.pendingClip = null; this.res = null; this.xobjs = null; + this.ImageCanvas = imageCanvas || ImageCanvas; } constructor.prototype = { @@ -3009,6 +3027,7 @@ var CanvasGraphics = (function() { var tmpCanvas = document.createElement("canvas"); tmpCanvas.width = Math.ceil(botRight[0] - topLeft[0]); tmpCanvas.height = Math.ceil(botRight[1] - topLeft[1]); + console.log("tilingFill", tmpCanvas.width, tmpCanvas.height); // set the new canvas element context as the graphics context var tmpCtx = tmpCanvas.getContext("2d"); @@ -3249,6 +3268,7 @@ var CanvasGraphics = (function() { ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, 0, -h, w, h); this.restore(); + console.log("drawImage"); return; } @@ -3328,11 +3348,15 @@ var CanvasGraphics = (function() { // handle matte object } - var tmpCanvas = document.createElement("canvas"); - tmpCanvas.width = w; - tmpCanvas.height = h; - var tmpCtx = tmpCanvas.getContext("2d"); - var imgData = tmpCtx.getImageData(0, 0, w, h); + var tmpCanvas = new this.ImageCanvas(w, h); + // var tmpCanvas = document.createElement("canvas"); + // tmpCanvas.width = w; + // tmpCanvas.height = h; + // + // var tmpCtx = tmpCanvas.getContext("2d"); + // var imgData = tmpCtx.getImageData(0, 0, w, h); + // var pixels = imgData.data; + var imgData = tmpCanvas.imgData; var pixels = imgData.data; if (bitsPerComponent != 8) @@ -3399,8 +3423,9 @@ var CanvasGraphics = (function() { TODO("Images with "+ numComps + " components per pixel"); } } - tmpCtx.putImageData(imgData, 0, 0); - ctx.drawImage(tmpCanvas, 0, -h); + console.log("paintImageXObject", w, h); + tmpCanvas.putImageData(imgData, 0, 0); + ctx.drawImage(tmpCanvas.getCanvas(), 0, -h); this.restore(); }, From d99dc718c5fd5e22a88c1c98c8cc6f02c51cfd63 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 22 Jun 2011 10:40:51 +0200 Subject: [PATCH 55/98] Get working for not real images --- canvas_proxy.js | 29 +++++++++++++++++++++++++++++ viewer_worker.html | 15 ++++++++++----- worker.js | 37 +++---------------------------------- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index ccc95c74a..610cdcdba 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -1,3 +1,24 @@ +var ImageCanvasProxyCounter = 0; +function ImageCanvasProxy(width, height) { + this.id = ImageCanvasProxyCounter++; + this.width = width; + this.height = height; + + // Using `Uint8ClampedArray` seems to be the type of ImageData - at least + // Firebug tells me so. + this.imgData = { + data: Uint8ClampedArray(width * height * 4) + }; +} + +ImageCanvasProxy.prototype.putImageData = function(imgData) { + // this.ctx.putImageData(imgData, 0, 0); +} + +ImageCanvasProxy.prototype.getCanvas = function() { + return this; +} + function CanvasProxy(width, height) { var stack = this.$stack = []; @@ -51,6 +72,14 @@ function CanvasProxy(width, height) { "$showText" ]; + this.drawImage = function(canvas, x, y) { + if (canvas instanceof ImageCanvasProxy) { + stack.push(["$drawCanvas", [canvas.imgData, x, y, canvas.width, canvas.height]]); + } else { + throw "unkown type to drawImage"; + } + } + function buildFuncCall(name) { return function() { // console.log("funcCall", name) diff --git a/viewer_worker.html b/viewer_worker.html index 930fb6cd5..07623c50c 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -4,11 +4,6 @@ @@ -184,7 +214,7 @@ window.onload = function() { -- Can we use JSONP to overcome the same-origin restrictions? --> - -- diff --git a/worker.js b/worker.js index 33b34f350..dcb87a811 100644 --- a/worker.js +++ b/worker.js @@ -26,7 +26,7 @@ function tic() { } function toc(msg) { - log("Took ", (Date.now() - timer)); + log(msg + ": " + (Date.now() - timer) + "ms"); timer = null; } @@ -41,46 +41,41 @@ var canvas = new CanvasProxy(1224, 1584); log("test"); var pageInterval; - +var pdfDocument = null; onmessage = function(event) { var data = event.data; - var pdfDocument = new PDFDoc(new Stream(data)); - var numPages = pdfDocument.numPages; + if (!pdfDocument) { + pdfDocument = new PDFDoc(new Stream(data)); + postMessage("pdf_num_page"); + postMessage(pdfDocument.numPages) + return; + } else { + tic(); - tic(); - // Let's try to render the first page... - var page = pdfDocument.getPage(2); + // Let's try to render the first page... + var page = pdfDocument.getPage(parseInt(data)); - // 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 will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + var fonts = []; + var gfx = new CanvasGraphics(canvas, ImageCanvasProxy); + page.compile(gfx, fonts); - var gfx = new CanvasGraphics(canvas, ImageCanvasProxy); - page.compile(gfx, fonts); - toc("compiled page"); + // Inspect fonts and translate the missing one. + var count = fonts.length; + for (var i = 0; i < count; i++) { + var font = fonts[i]; + if (Fonts[font.name]) { + fontsReady = fontsReady && !Fonts[font.name].loading; + continue; + } + // This "builds" the font and sents it over to the main thread. + new Font(font.name, font.file, font.properties); + } + toc("compiled page"); - page.display(gfx); - canvas.flush(); + page.display(gfx); + canvas.flush(); + } } - -// function open(url) { -// var req = new XMLHttpRequest(); -// req.open("GET", url); -// // req.responseType = "arraybuffer"; -// req.expected = 0;//(document.URL.indexOf("file:") == 0) ? 0 : 200; -// req.onreadystatechange = function() { -// postMessage("loaded"); -// if (req.readyState == 4 && req.status == req.expected) { -// var data = req.mozResponseArrayBuffer || req.mozResponse || -// req.responseArrayBuffer || req.response; -// pdfDocument = new PDFDoc(new Stream(data)); -// numPages = pdfDocument.numPages; -// // document.getElementById("numPages").innerHTML = numPages.toString(); -// // goToPage(pageNum); -// } -// }; -// req.send(null); -// } -// -// open("compressed.tracemonkey-pldi-09.pdf") \ No newline at end of file From ddd8aeffb9483b8ccc1f236052e55714a93b69bb Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 22 Jun 2011 13:19:25 +0200 Subject: [PATCH 57/98] Fix font loading issue by using a hidden DOM font node --- viewer_worker.html | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/viewer_worker.html b/viewer_worker.html index bba694f21..ced71679e 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -118,12 +118,10 @@ myWorker.onmessage = function(event) { var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.length); - // *HACK*: this makes the font get loaded on the page. WTF? We - // really have to set the fonts a few time... - var interval = setInterval(function() { - ctx.font = "bold italic 20px " + data.fontName + ", Symbol, Arial"; - }, 10); - intervals.push(interval); + // 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. + document.getElementById("fonts").innerHTML += "
j
"; console.log("setup font", data.fontName); onMessageState = WAIT; @@ -139,16 +137,13 @@ myWorker.onmessage = function(event) { gStack = stack; console.log("canvas stack size", stack.length) - // Shedule a timeout. Hoping the fonts are loaded after 100ms. + // There might be fonts that need to get loaded. Shedule the + // rendering at the end of the event queue ensures this. setTimeout(function() { - // Remove all setIntervals to make the font load. - intervals.forEach(function(inter) { - clearInterval(inter); - }); tic(); renderProxyCanvas(stack); toc("canvas rendering") - }, 100); + }, 0); onMessageState = WAIT; break; } @@ -208,6 +203,7 @@ window.onload = function() { +
- - - Previous + + -- diff --git a/worker.js b/worker.js index e59e37155..09e2b8145 100644 --- a/worker.js +++ b/worker.js @@ -1,15 +1,26 @@ "use strict"; +var timer = null; +function tic() { + timer = Date.now(); +} + +function toc(msg) { + log(msg + ": " + (Date.now() - timer) + "ms"); + timer = null; +} + function log() { - var args = Array.prototype.slice.call(arguments); - postMessage("log"); - postMessage(JSON.stringify(args)) + var args = Array.prototype.slice.call(arguments); + postMessage("log"); + postMessage(JSON.stringify(args)) } var console = { - log: log + log: log } +// importScripts("canvas_proxy.js"); importScripts("pdf.js"); importScripts("fonts.js"); @@ -18,55 +29,50 @@ importScripts("glyphlist.js") // Use the JpegStreamProxy proxy. JpegStream = JpegStreamProxy; -var timer = null; -function tic() { - timer = Date.now(); -} - -function toc(msg) { - log(msg + ": " + (Date.now() - timer) + "ms"); - timer = null; -} - // Create the WebWorkerProxyCanvas. var canvas = new CanvasProxy(1224, 1584); -var pageInterval; +// Listen for messages from the main thread. var pdfDocument = null; onmessage = function(event) { - var data = event.data; - if (!pdfDocument) { - pdfDocument = new PDFDoc(new Stream(data)); - postMessage("pdf_num_page"); - postMessage(pdfDocument.numPages) - return; - } else { - tic(); + var data = event.data; + // If there is no pdfDocument yet, then the sent data is the PDFDocument. + if (!pdfDocument) { + pdfDocument = new PDFDoc(new Stream(data)); + postMessage("pdf_num_page"); + postMessage(pdfDocument.numPages) + return; + } + // User requested to render a certain page. + else { + tic(); - // Let's try to render the first page... - var page = pdfDocument.getPage(parseInt(data)); + // Let's try to render the first page... + var page = pdfDocument.getPage(parseInt(data)); - // 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 = []; - var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy); - page.compile(gfx, fonts); + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + var fonts = []; + var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy); + page.compile(gfx, fonts); - // Inspect fonts and translate the missing one. - var count = fonts.length; - for (var i = 0; i < count; i++) { - var font = fonts[i]; - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; - } + // Inspect fonts and translate the missing one. + var count = fonts.length; + for (var i = 0; i < count; i++) { + var font = fonts[i]; + if (Fonts[font.name]) { + fontsReady = fontsReady && !Fonts[font.name].loading; + continue; + } - // This "builds" the font and sents it over to the main thread. - new Font(font.name, font.file, font.properties); - } - toc("compiled page"); - - page.display(gfx); - canvas.flush(); + // This "builds" the font and sents it over to the main thread. + new Font(font.name, font.file, font.properties); } + toc("compiled page"); + + tic() + page.display(gfx); + canvas.flush(); + toc("displayed page"); + } } diff --git a/worker_client.js b/worker_client.js new file mode 100644 index 000000000..316ef1fc0 --- /dev/null +++ b/worker_client.js @@ -0,0 +1,294 @@ +"use strict"; + +function WorkerPDFDoc(canvas) { + var timer = null + function tic() { + timer = Date.now(); + } + + function toc(msg) { + console.log(msg + ": " + (Date.now() - timer) + "ms"); + } + + this.ctx = canvas.getContext("2d"); + this.canvas = canvas; + this.worker = new Worker('worker.js'); + + this.numPage = 1; + this.numPages = null; + + var imagesList = {}; + var canvasList = { + 0: canvas + }; + var patternList = {}; + var gradient; + + var currentX = 0; + var currentXStack = []; + + var ctxSpecial = { + "$setCurrentX": function(value) { + currentX = value; + }, + + "$addCurrentX": function(value) { + currentX += value; + }, + + "$saveCurrentX": function() { + currentXStack.push(currentX); + }, + + "$restoreCurrentX": function() { + currentX = currentXStack.pop(); + }, + + "$showText": function(y, text, uniText) { + this.translate(currentX, -1 * y); + this.fillText(uniText, 0, 0); + currentX += this.measureText(text).width; + }, + + "$putImageData": function(imageData, x, y) { + var imgData = this.getImageData(0, 0, imageData.width, imageData.height); + + // Store the .data property to avaid property lookups. + var imageRealData = imageData.data; + var imgRealData = imgData.data; + + // Copy over the imageData. + var len = imageRealData.length; + while (len--) + imgRealData[len] = imageRealData[len] + + this.putImageData(imgData, x, y); + }, + + "$drawImage": function(id, x, y, sx, sy, swidth, sheight) { + var image = imagesList[id]; + if (!image) { + throw "Image not found"; + } + this.drawImage(image, x, y, image.width, image.height, + sx, sy, swidth, sheight); + }, + + "$drawCanvas": function(id, x, y, sx, sy, swidth, sheight) { + var canvas = canvasList[id]; + if (!canvas) { + throw "Canvas not found"; + } + if (sheight != null) { + this.drawImage(canvas, x, y, canvas.width, canvas.height, + sx, sy, swidth, sheight); + } else { + this.drawImage(canvas, x, y, canvas.width, canvas.height); + } + }, + + "$createLinearGradient": function(x0, y0, x1, y1) { + gradient = this.createLinearGradient(x0, y0, x1, y1); + }, + + "$createPatternFromCanvas": function(patternId, canvasId, kind) { + var canvas = canvasList[canvasId]; + if (!canvas) { + throw "Canvas not found"; + } + patternList[patternId] = this.createPattern(canvas, kind); + }, + + "$addColorStop": function(i, rgba) { + gradient.addColorStop(i, rgba); + }, + + "$fillStyleGradient": function() { + this.fillStyle = gradient; + }, + + "$fillStylePattern": function(id) { + var pattern = patternList[id]; + if (!pattern) { + throw "Pattern not found"; + } + this.fillStyle = pattern; + }, + + "$strokeStyleGradient": function() { + this.strokeStyle = gradient; + }, + + "$strokeStylePattern": function(id) { + var pattern = patternList[id]; + if (!pattern) { + throw "Pattern not found"; + } + this.strokeStyle = pattern; + } + } + + function renderProxyCanvas(canvas, stack) { + var ctx = canvas.getContext("2d"); + for (var i = 0; i < stack.length; i++) { + var opp = stack[i]; + if (opp[0] == "$") { + ctx[opp[1]] = opp[2]; + } else if (opp[0] in ctxSpecial) { + ctxSpecial[opp[0]].apply(ctx, opp[1]); + } else { + ctx[opp[0]].apply(ctx, opp[1]); + } + } + } + + /** + * onMessage state machine. + */ + const WAIT = 0; + const CANVAS_PROXY_STACK = 1; + const LOG = 2; + const FONT = 3; + const PDF_NUM_PAGE = 4; + const JPEG_STREAM = 5; + + var onMessageState = WAIT; + this.worker.onmessage = function(event) { + var data = event.data; + // console.log("onMessageRaw", data); + switch (onMessageState) { + case WAIT: + if (typeof data != "string") { + throw "expecting to get an string"; + } + switch (data) { + case "pdf_num_page": + onMessageState = PDF_NUM_PAGE; + return; + + case "log": + onMessageState = LOG; + return; + + case "canvas_proxy_stack": + onMessageState = CANVAS_PROXY_STACK; + return; + + case "font": + onMessageState = FONT; + return; + + case "jpeg_stream": + onMessageState = JPEG_STREAM; + return; + + default: + throw "unkown state: " + data + } + break; + + case JPEG_STREAM: + var img = new Image(); + img.src = "data:image/jpeg;base64," + window.btoa(data.str); + imagesList[data.id] = img; + console.log("got image", data.id) + break; + + case PDF_NUM_PAGE: + this.numPages = parseInt(data); + if (this.loadCallback) { + this.loadCallback(); + } + onMessageState = WAIT; + break; + + case FONT: + data = JSON.parse(data); + var base64 = window.btoa(data.str); + + // Add the @font-face rule to the document + var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.length); + + // 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. + document.getElementById("fonts").innerHTML += "
j
"; + + onMessageState = WAIT; + break; + + case LOG: + console.log.apply(console, JSON.parse(data)); + onMessageState = WAIT; + break; + + case CANVAS_PROXY_STACK: + var id = data.id; + var stack = data.stack; + + // Check if there is already a canvas with the given id. If not, + // create a new canvas. + if (!canvasList[id]) { + var newCanvas = document.createElement("canvas"); + newCanvas.width = data.width; + newCanvas.height = data.height; + canvasList[id] = newCanvas; + } + + // There might be fonts that need to get loaded. Shedule the + // rendering at the end of the event queue ensures this. + setTimeout(function() { + if (id == 0) tic(); + renderProxyCanvas(canvasList[id], stack); + if (id == 0) toc("canvas rendering") + }, 0); + onMessageState = WAIT; + break; + } + }.bind(this); +} + + WorkerPDFDoc.prototype.open = function(url, callback) { + var req = new XMLHttpRequest(); + req.open("GET", url); + req.mozResponseType = req.responseType = "arraybuffer"; + req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200; + req.onreadystatechange = function() { + if (req.readyState == 4 && req.status == req.expected) { + var data = req.mozResponseArrayBuffer || req.mozResponse || + req.responseArrayBuffer || req.response; + + this.loadCallback = callback; + this.worker.postMessage(data); + this.showPage(this.numPage); + } + }.bind(this); + req.send(null); +} + +WorkerPDFDoc.prototype.showPage = function(numPage) { + var ctx = this.ctx; + ctx.save(); + ctx.fillStyle = "rgb(255, 255, 255)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + this.numPage = parseInt(numPage); + this.worker.postMessage(numPage); + if (this.onChangePage) { + this.onChangePage(numPage); + } +} + +WorkerPDFDoc.prototype.nextPage = function() { + if (this.numPage == this.numPages) return; + this.showPage(++this.numPage); +} + +WorkerPDFDoc.prototype.prevPage = function() { + if (this.numPage == 1) return; + this.showPage(--this.numPage); +} From 229edf24d4586f39bb25b65a947aceb06b8a72ad Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 23 Jun 2011 13:09:36 +0200 Subject: [PATCH 66/98] First pass on review: worker.js -> pdf_worker.js, Font.bind cleanup + other stuff --- canvas_proxy.js | 73 ++++++++++++++++-------------- fonts.js | 92 +++++++------------------------------- pdf.js | 8 ++-- worker.js => pdf_worker.js | 3 ++ viewer_worker.html | 1 + worker_client.js | 23 ++++++---- 6 files changed, 77 insertions(+), 123 deletions(-) rename worker.js => pdf_worker.js (92%) diff --git a/canvas_proxy.js b/canvas_proxy.js index 83b57682f..0b7681bfe 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -1,3 +1,7 @@ +/* -*- 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 JpegStreamProxyCounter = 0; // WebWorker Proxy for JpegStream. @@ -29,16 +33,16 @@ var JpegStreamProxy = (function() { // Really simple GradientProxy. There is currently only one active gradient at // the time, meaning you can't create a gradient, create a second one and then // use the first one again. As this isn't used in pdf.js right now, it's okay. -function GradientProxy(stack, x0, y0, x1, y1) { - stack.push(["$createLinearGradient", [x0, y0, x1, y1]]); +function GradientProxy(cmdQueue, x0, y0, x1, y1) { + cmdQueue.push(["$createLinearGradient", [x0, y0, x1, y1]]); this.addColorStop = function(i, rgba) { - stack.push(["$addColorStop", [i, rgba]]); + cmdQueue.push(["$addColorStop", [i, rgba]]); } } // Really simple PatternProxy. var patternProxyCounter = 0; -function PatternProxy(stack, object, kind) { +function PatternProxy(cmdQueue, object, kind) { this.id = patternProxyCounter++; if (!(object instanceof CanvasProxy) ) { @@ -49,7 +53,7 @@ function PatternProxy(stack, object, kind) { // TODO: Make some kind of dependency management, such that the object // gets flushed only if needed. object.flush(); - stack.push(["$createPatternFromCanvas", [this.id, object.id, kind]]); + cmdQueue.push(["$createPatternFromCanvas", [this.id, object.id, kind]]); } var canvasProxyCounter = 0; @@ -57,7 +61,7 @@ function CanvasProxy(width, height) { this.id = canvasProxyCounter++; // The `stack` holds the rendering calls and gets flushed to the main thead. - var stack = this.$stack = []; + var cmdQueue = this.cmdQueue = []; // Dummy context that gets exposed. var ctx = {}; @@ -119,7 +123,7 @@ function CanvasProxy(width, height) { function buildFuncCall(name) { return function() { // console.log("funcCall", name) - stack.push([name, Array.prototype.slice.call(arguments)]); + cmdQueue.push([name, Array.prototype.slice.call(arguments)]); } } var name; @@ -131,11 +135,11 @@ function CanvasProxy(width, height) { // Some function calls that need more work. ctx.createPattern = function(object, kind) { - return new PatternProxy(stack, object, kind); + return new PatternProxy(cmdQueue, object, kind); } ctx.createLinearGradient = function(x0, y0, x1, y1) { - return new GradientProxy(stack, x0, y0, x1, y1); + return new GradientProxy(cmdQueue, x0, y0, x1, y1); } ctx.getImageData = function(x, y, w, h) { @@ -147,16 +151,16 @@ function CanvasProxy(width, height) { } ctx.putImageData = function(data, x, y, width, height) { - stack.push(["$putImageData", [data, x, y, width, height]]); + cmdQueue.push(["$putImageData", [data, x, y, width, height]]); } ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) { if (image instanceof CanvasProxy) { // Send the image/CanvasProxy to the main thread. image.flush(); - stack.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]); + cmdQueue.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]); } else if(image instanceof JpegStreamProxy) { - stack.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]]) + cmdQueue.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]]) } else { throw "unkown type to drawImage"; } @@ -192,11 +196,26 @@ function CanvasProxy(width, height) { function buildSetter(name) { return function(value) { - stack.push(["$", name, value]); + cmdQueue.push(["$", name, value]); return ctx["$" + name] = value; } } + // Setting the value to `stroke|fillStyle` needs special handling, as it + // might gets an gradient/pattern. + function buildSetterStyle(name) { + return function(value) { + if (value instanceof GradientProxy) { + cmdQueue.push(["$" + name + "Gradient"]); + } else if (value instanceof PatternProxy) { + cmdQueue.push(["$" + name + "Pattern", [value.id]]); + } else { + cmdQueue.push(["$", name, value]); + return ctx["$" + name] = value; + } + } + } + for (var name in ctxProp) { ctx["$" + name] = ctxProp[name]; ctx.__defineGetter__(name, buildGetter(name)); @@ -204,18 +223,6 @@ function CanvasProxy(width, height) { // Special treatment for `fillStyle` and `strokeStyle`: The passed style // might be a gradient. Need to check for that. if (name == "fillStyle" || name == "strokeStyle") { - function buildSetterStyle(name) { - return function(value) { - if (value instanceof GradientProxy) { - stack.push(["$" + name + "Gradient"]); - } else if (value instanceof PatternProxy) { - stack.push(["$" + name + "Pattern", [value.id]]); - } else { - stack.push(["$", name, value]); - return ctx["$" + name] = value; - } - } - } ctx.__defineSetter__(name, buildSetterStyle(name)); } else { ctx.__defineSetter__(name, buildSetter(name)); @@ -224,16 +231,16 @@ function CanvasProxy(width, height) { } /** -* Sends the current stack of the CanvasProxy over to the main thread and -* resets the stack. +* Sends the current cmdQueue of the CanvasProxy over to the main thread and +* resets the cmdQueue. */ CanvasProxy.prototype.flush = function() { - postMessage("canvas_proxy_stack"); + postMessage("canvas_proxy_cmd_queue"); postMessage({ - id: this.id, - stack: this.$stack, - width: this.width, - height: this.height + id: this.id, + cmdQueue: this.cmdQueue, + width: this.width, + height: this.height }); - this.$stack.length = 0; + this.cmdQueue.length = 0; } diff --git a/fonts.js b/fonts.js index 9c9201b72..a3604c6b9 100644 --- a/fonts.js +++ b/fonts.js @@ -759,88 +759,15 @@ var Font = (function () { var data = this.font; var fontName = this.name; - var isWorker = (typeof window == "undefined"); - /** Hack begin */ - if (!isWorker) { - - // 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 - var canvas = document.createElement("canvas"); - var style = "border: 1px solid black; position:absolute; top: " + - (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px"; - canvas.setAttribute("style", style); - canvas.setAttribute("width", 340); - canvas.setAttribute("heigth", 100); - document.body.appendChild(canvas); - - // Get the font size canvas think it will be for 'spaces' - var ctx = canvas.getContext("2d"); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - var testString = " "; - - // When debugging use the characters provided by the charsets to visually - // see what's happening instead of 'spaces' - var debug = false; - if (debug) { - var name = document.createElement("font"); - name.setAttribute("style", "position: absolute; left: 20px; top: " + - (100 * fontCount + 60) + "px"); - name.innerHTML = fontName; - document.body.appendChild(name); - - // Retrieve font charset - var charset = Fonts[fontName].properties.charset || []; - - // if the charset is too small make it repeat a few times - var count = 30; - while (count-- && charset.length <= 30) - charset = charset.concat(charset.slice()); - - for (var i = 0; i < charset.length; i++) { - var unicode = GlyphsUnicode[charset[i]]; - if (!unicode) - continue; - testString += String.fromCharCode(unicode); - } - - ctx.fillText(testString, 20, 20); - } - - // Periodicaly check for the width of the testString, it will be - // different once the real font has loaded - var textWidth = ctx.measureText(testString).width; - - var interval = window.setInterval(function canvasInterval(self) { - this.start = this.start || Date.now(); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - - // For some reasons the font has not loaded, so mark it loaded for the - // page to proceed but cry - if ((Date.now() - this.start) >= kMaxWaitForFontFace) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - warn("Is " + fontName + " for charset: " + charset + " loaded?"); - this.start = 0; - } else if (textWidth != ctx.measureText(testString).width) { - window.clearInterval(interval); - Fonts[fontName].loading = false; - this.start = 0; - } - - if (debug) - ctx.fillText(testString, 20, 50); - }, 30, this); - } - - /** Hack end */ - // // Get the base64 encoding of the binary font data var str = ""; var length = data.length; for (var i = 0; i < length; ++i) str += String.fromCharCode(data[i]); - if (isWorker) { + // Insert the font-face css on the page. In a web worker, this needs to + // be forwareded on the main thread. + if (typeof window == "undefined") { postMessage("font"); postMessage(JSON.stringify({ str: str, @@ -855,6 +782,19 @@ var Font = (function () { var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.length); + + var div = document.createElement("div"); + div.innerHTML += "
j
"; + document.body.appendChild(div); + + Fonts[fontName].loading = true; + window.setTimeout(function() { + Fonts[fontName].loading = false; + // Timeout of just `0`, `10` doesn't work here, but for me all values + // above work. Setting value to 50ms. + }, 50); } } }; diff --git a/pdf.js b/pdf.js index 1223a2bb6..847067946 100644 --- a/pdf.js +++ b/pdf.js @@ -2645,9 +2645,7 @@ var CanvasGraphics = (function() { } var fn = Function("objpool", src); - var ret = function (gfx) { fn.call(gfx, objpool); }; - ret.src = src; - return ret; + return function (gfx) { fn.call(gfx, objpool); }; }, endDrawing: function() { @@ -3015,8 +3013,8 @@ var CanvasGraphics = (function() { 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 + Math.ceil(botRight[0] - topLeft[0]), // width + Math.ceil(botRight[1] - topLeft[1]) // height ); // set the new canvas element context as the graphics context diff --git a/worker.js b/pdf_worker.js similarity index 92% rename from worker.js rename to pdf_worker.js index 09e2b8145..91245aedb 100644 --- a/worker.js +++ b/pdf_worker.js @@ -1,3 +1,6 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + "use strict"; var timer = null; diff --git a/viewer_worker.html b/viewer_worker.html index a9f08388f..a5ffc6a6e 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -14,6 +14,7 @@ window.onload = function() { pdfDoc.onChangePage = function(numPage) { document.getElementById("pageNumber").value = numPage; } + // pdfDoc.open("canvas.pdf", function() { pdfDoc.open("compressed.tracemonkey-pldi-09.pdf", function() { document.getElementById("numPages").innerHTML = "/" + pdfDoc.numPages; }) diff --git a/worker_client.js b/worker_client.js index 316ef1fc0..f69f4f682 100644 --- a/worker_client.js +++ b/worker_client.js @@ -1,3 +1,6 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + "use strict"; function WorkerPDFDoc(canvas) { @@ -128,10 +131,11 @@ function WorkerPDFDoc(canvas) { } } - function renderProxyCanvas(canvas, stack) { + function renderProxyCanvas(canvas, cmdQueue) { var ctx = canvas.getContext("2d"); - for (var i = 0; i < stack.length; i++) { - var opp = stack[i]; + var cmdQueueLength = cmdQueue.length; + for (var i = 0; i < cmdQueueLength; i++) { + var opp = cmdQueue[i]; if (opp[0] == "$") { ctx[opp[1]] = opp[2]; } else if (opp[0] in ctxSpecial) { @@ -146,7 +150,7 @@ function WorkerPDFDoc(canvas) { * onMessage state machine. */ const WAIT = 0; - const CANVAS_PROXY_STACK = 1; + const CANVAS_PROXY_CMD_QUEUE = 1; const LOG = 2; const FONT = 3; const PDF_NUM_PAGE = 4; @@ -170,8 +174,8 @@ function WorkerPDFDoc(canvas) { onMessageState = LOG; return; - case "canvas_proxy_stack": - onMessageState = CANVAS_PROXY_STACK; + case "canvas_proxy_cmd_queue": + onMessageState = CANVAS_PROXY_CMD_QUEUE; return; case "font": @@ -215,6 +219,7 @@ function WorkerPDFDoc(canvas) { // 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"); document.getElementById("fonts").innerHTML += "
j
"; onMessageState = WAIT; @@ -225,9 +230,9 @@ function WorkerPDFDoc(canvas) { onMessageState = WAIT; break; - case CANVAS_PROXY_STACK: + case CANVAS_PROXY_CMD_QUEUE: var id = data.id; - var stack = data.stack; + var cmdQueue = data.cmdQueue; // Check if there is already a canvas with the given id. If not, // create a new canvas. @@ -242,7 +247,7 @@ function WorkerPDFDoc(canvas) { // rendering at the end of the event queue ensures this. setTimeout(function() { if (id == 0) tic(); - renderProxyCanvas(canvasList[id], stack); + renderProxyCanvas(canvasList[id], cmdQueue); if (id == 0) toc("canvas rendering") }, 0); onMessageState = WAIT; From 78129970c6b70992e7d8316a1978f0be79a86604 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 23 Jun 2011 13:25:59 +0200 Subject: [PATCH 67/98] Change postMessage to send only one object that holds the action and data. --- canvas_proxy.js | 17 +++--- fonts.js | 10 ++-- pdf_worker.js | 12 +++-- worker_client.js | 132 +++++++++++++++++------------------------------ 4 files changed, 70 insertions(+), 101 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index 0b7681bfe..e2795bd00 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -11,10 +11,9 @@ var JpegStreamProxy = (function() { this.dict = dict; // Tell the main thread to create an image. - postMessage("jpeg_stream"); postMessage({ - id: this.id, - str: bytesToString(bytes) + action: jpeg_stream, + data: bytesToString(bytes) }); } @@ -235,12 +234,14 @@ function CanvasProxy(width, height) { * resets the cmdQueue. */ CanvasProxy.prototype.flush = function() { - postMessage("canvas_proxy_cmd_queue"); postMessage({ - id: this.id, - cmdQueue: this.cmdQueue, - width: this.width, - height: this.height + action: "canvas_proxy_cmd_queue", + data: { + id: this.id, + cmdQueue: this.cmdQueue, + width: this.width, + height: this.height + } }); this.cmdQueue.length = 0; } diff --git a/fonts.js b/fonts.js index a3604c6b9..7f4958caf 100644 --- a/fonts.js +++ b/fonts.js @@ -768,12 +768,14 @@ var Font = (function () { // Insert the font-face css on the page. In a web worker, this needs to // be forwareded on the main thread. if (typeof window == "undefined") { - postMessage("font"); - postMessage(JSON.stringify({ - str: str, + postMessage({ + action: "font", + data: { + raw: str, fontName: fontName, mimetype: this.mimetype - })); + } + }); } else { var base64 = window.btoa(str); diff --git a/pdf_worker.js b/pdf_worker.js index 91245aedb..13a1f3f28 100644 --- a/pdf_worker.js +++ b/pdf_worker.js @@ -15,8 +15,10 @@ function toc(msg) { function log() { var args = Array.prototype.slice.call(arguments); - postMessage("log"); - postMessage(JSON.stringify(args)) + postMessage({ + action: "log", + args: args + }); } var console = { @@ -42,8 +44,10 @@ onmessage = function(event) { // If there is no pdfDocument yet, then the sent data is the PDFDocument. if (!pdfDocument) { pdfDocument = new PDFDoc(new Stream(data)); - postMessage("pdf_num_page"); - postMessage(pdfDocument.numPages) + postMessage({ + action: "pdf_num_pages", + data: pdfDocument.numPages + }); return; } // User requested to render a certain page. diff --git a/worker_client.js b/worker_client.js index f69f4f682..4af0d9764 100644 --- a/worker_client.js +++ b/worker_client.js @@ -15,7 +15,7 @@ function WorkerPDFDoc(canvas) { this.ctx = canvas.getContext("2d"); this.canvas = canvas; - this.worker = new Worker('worker.js'); + this.worker = new Worker('pdf_worker.js'); this.numPage = 1; this.numPages = null; @@ -147,90 +147,44 @@ function WorkerPDFDoc(canvas) { } /** - * onMessage state machine. + * Functions to handle data sent by the WebWorker. */ - const WAIT = 0; - const CANVAS_PROXY_CMD_QUEUE = 1; - const LOG = 2; - const FONT = 3; - const PDF_NUM_PAGE = 4; - const JPEG_STREAM = 5; + var actionHandler = { + "log": function(data) { + console.log.apply(console, data); + }, + + "pdf_num_pages": function(data) { + this.numPages = parseInt(data); + if (this.loadCallback) { + this.loadCallback(); + } + }, + + "font": function(data) { + var base64 = window.btoa(data.raw); - var onMessageState = WAIT; - this.worker.onmessage = function(event) { - var data = event.data; - // console.log("onMessageRaw", data); - switch (onMessageState) { - case WAIT: - if (typeof data != "string") { - throw "expecting to get an string"; - } - switch (data) { - case "pdf_num_page": - onMessageState = PDF_NUM_PAGE; - return; + // Add the @font-face rule to the document + var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; + var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; + var styleSheet = document.styleSheets[0]; + styleSheet.insertRule(rule, styleSheet.length); - case "log": - onMessageState = LOG; - return; + // 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"); + document.getElementById("fonts").innerHTML += "
j
"; + }, - case "canvas_proxy_cmd_queue": - onMessageState = CANVAS_PROXY_CMD_QUEUE; - return; - - case "font": - onMessageState = FONT; - return; - - case "jpeg_stream": - onMessageState = JPEG_STREAM; - return; - - default: - throw "unkown state: " + data - } - break; - - case JPEG_STREAM: - var img = new Image(); - img.src = "data:image/jpeg;base64," + window.btoa(data.str); - imagesList[data.id] = img; - console.log("got image", data.id) - break; - - case PDF_NUM_PAGE: - this.numPages = parseInt(data); - if (this.loadCallback) { - this.loadCallback(); - } - onMessageState = WAIT; - break; - - case FONT: - data = JSON.parse(data); - var base64 = window.btoa(data.str); - - // Add the @font-face rule to the document - var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; - var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; - var styleSheet = document.styleSheets[0]; - styleSheet.insertRule(rule, styleSheet.length); - - // 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"); - document.getElementById("fonts").innerHTML += "
j
"; - - onMessageState = WAIT; - break; - - case LOG: - console.log.apply(console, JSON.parse(data)); - onMessageState = WAIT; - break; - - case CANVAS_PROXY_CMD_QUEUE: + "jpeg_stream": function(data) { + var img = new Image(); + img.src = "data:image/jpeg;base64," + window.btoa(data); + imagesList[data.id] = img; + console.log("got image", data.id) + }, + + "canvas_proxy_cmd_queue": function(data) { var id = data.id; var cmdQueue = data.cmdQueue; @@ -250,13 +204,21 @@ function WorkerPDFDoc(canvas) { renderProxyCanvas(canvasList[id], cmdQueue); if (id == 0) toc("canvas rendering") }, 0); - onMessageState = WAIT; - break; } - }.bind(this); + } + + // List to the WebWorker for data and call actionHandler on it. + this.worker.onmessage = function(event) { + var data = event.data; + if (data.action in actionHandler) { + actionHandler[data.action].call(this, data.data); + } else { + throw "Unkown action from worker: " + data.action; + } + } } - WorkerPDFDoc.prototype.open = function(url, callback) { +WorkerPDFDoc.prototype.open = function(url, callback) { var req = new XMLHttpRequest(); req.open("GET", url); req.mozResponseType = req.responseType = "arraybuffer"; From 37ee56a705d88b360e67b1cb3d2e064bd6614fb6 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 23 Jun 2011 14:36:45 +0200 Subject: [PATCH 68/98] Fix sending image data to main thread --- canvas_proxy.js | 7 +++++-- viewer.js | 2 +- worker_client.js | 5 ++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/canvas_proxy.js b/canvas_proxy.js index e2795bd00..d6f5a0a25 100644 --- a/canvas_proxy.js +++ b/canvas_proxy.js @@ -12,8 +12,11 @@ var JpegStreamProxy = (function() { // Tell the main thread to create an image. postMessage({ - action: jpeg_stream, - data: bytesToString(bytes) + action: "jpeg_stream", + data: { + id: this.id, + raw: bytesToString(bytes) + } }); } diff --git a/viewer.js b/viewer.js index 41aaf354c..d0aeb0b2d 100644 --- a/viewer.js +++ b/viewer.js @@ -10,7 +10,7 @@ function load(userInput) { pageNum = parseInt(queryParams().page) || 1; var fileName = userInput; if (!userInput) { - fileName = queryParams().file || "compressed.tracemonkey-pldi-09.pdf"; + fileName = "canvas.pdf"; } open(fileName); } diff --git a/worker_client.js b/worker_client.js index 4af0d9764..385103c30 100644 --- a/worker_client.js +++ b/worker_client.js @@ -71,7 +71,7 @@ function WorkerPDFDoc(canvas) { "$drawImage": function(id, x, y, sx, sy, swidth, sheight) { var image = imagesList[id]; if (!image) { - throw "Image not found"; + throw "Image not found: " + id; } this.drawImage(image, x, y, image.width, image.height, sx, sy, swidth, sheight); @@ -179,9 +179,8 @@ function WorkerPDFDoc(canvas) { "jpeg_stream": function(data) { var img = new Image(); - img.src = "data:image/jpeg;base64," + window.btoa(data); + img.src = "data:image/jpeg;base64," + window.btoa(data.raw); imagesList[data.id] = img; - console.log("got image", data.id) }, "canvas_proxy_cmd_queue": function(data) { From c35e1e75522313faae4cfaa63dde10b903c93c07 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 23 Jun 2011 15:24:55 +0200 Subject: [PATCH 69/98] Fix WebWorker logging and add separate timing for `fonts`. --- pdf_worker.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pdf_worker.js b/pdf_worker.js index 13a1f3f28..86dfec2dd 100644 --- a/pdf_worker.js +++ b/pdf_worker.js @@ -17,7 +17,7 @@ function log() { var args = Array.prototype.slice.call(arguments); postMessage({ action: "log", - args: args + data: args }); } @@ -62,7 +62,9 @@ onmessage = function(event) { var fonts = []; var gfx = new CanvasGraphics(canvas.getContext("2d"), CanvasProxy); page.compile(gfx, fonts); + toc("compiled page"); + tic() // Inspect fonts and translate the missing one. var count = fonts.length; for (var i = 0; i < count; i++) { @@ -75,7 +77,7 @@ onmessage = function(event) { // This "builds" the font and sents it over to the main thread. new Font(font.name, font.file, font.properties); } - toc("compiled page"); + toc("fonts"); tic() page.display(gfx); From 171ab51c569182bf41d4f1de74d55cc81a346abf Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 23 Jun 2011 19:43:01 +0200 Subject: [PATCH 70/98] Ensure divs used to make fonts load are not visible --- fonts.js | 6 +++--- viewer.js | 2 +- viewer_worker.html | 2 -- worker_client.js | 5 ++++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/fonts.js b/fonts.js index 7f4958caf..ba40ef8e1 100644 --- a/fonts.js +++ b/fonts.js @@ -786,9 +786,9 @@ var Font = (function () { styleSheet.insertRule(rule, styleSheet.length); var div = document.createElement("div"); - div.innerHTML += "
j
"; + var style = 'font-family:"' + fontName + + '";position: absolute;top:-99999;left:-99999;z-index:-99999'; + div.setAttribute("style", style); document.body.appendChild(div); Fonts[fontName].loading = true; diff --git a/viewer.js b/viewer.js index d0aeb0b2d..41aaf354c 100644 --- a/viewer.js +++ b/viewer.js @@ -10,7 +10,7 @@ function load(userInput) { pageNum = parseInt(queryParams().page) || 1; var fileName = userInput; if (!userInput) { - fileName = "canvas.pdf"; + fileName = queryParams().file || "compressed.tracemonkey-pldi-09.pdf"; } open(fileName); } diff --git a/viewer_worker.html b/viewer_worker.html index a5ffc6a6e..d13935f13 100644 --- a/viewer_worker.html +++ b/viewer_worker.html @@ -22,9 +22,7 @@ window.onload = function() { - -
-
- - diff --git a/multi-page-viewer.js b/multi-page-viewer.js deleted file mode 100644 index baad7809e..000000000 --- a/multi-page-viewer.js +++ /dev/null @@ -1,466 +0,0 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ - -"use strict"; - -var PDFViewer = { - queryParams: {}, - - element: null, - - previousPageButton: null, - nextPageButton: null, - pageNumberInput: null, - scaleSelect: null, - fileInput: null, - - willJumpToPage: false, - - pdf: null, - - url: 'compressed.tracemonkey-pldi-09.pdf', - pageNumber: 1, - numberOfPages: 1, - - scale: 1.0, - - pageWidth: function() { - return 816 * PDFViewer.scale; - }, - - pageHeight: function() { - return 1056 * PDFViewer.scale; - }, - - lastPagesDrawn: [], - - visiblePages: function() { - var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. - var windowTop = window.pageYOffset; - var windowBottom = window.pageYOffset + window.innerHeight; - var pageStartIndex = Math.floor(windowTop / pageHeight); - var pageStopIndex = Math.ceil(windowBottom / pageHeight); - - var pages = []; - - for (var i = pageStartIndex; i <= pageStopIndex; i++) { - pages.push(i + 1); - } - - return pages; - }, - - createPage: function(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'; - - PDFViewer.element.appendChild(anchor); - PDFViewer.element.appendChild(div); - }, - - removePage: function(num) { - var div = document.getElementById('pageContainer' + num); - - if (div) { - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - } - }, - - drawPage: function(num) { - if (!PDFViewer.pdf) { - return; - } - - var div = document.getElementById('pageContainer' + num); - var canvas = document.createElement('canvas'); - - if (div && !div.hasChildNodes()) { - div.appendChild(canvas); - - var page = PDFViewer.pdf.getPage(num); - - canvas.id = 'page' + num; - canvas.mozOpaque = true; - - // Canvas dimensions must be specified in CSS pixels. CSS pixels - // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. - canvas.width = PDFViewer.pageWidth(); - canvas.height = PDFViewer.pageHeight(); - - var ctx = canvas.getContext('2d'); - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var gfx = new CanvasGraphics(ctx); - var fonts = []; - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - page.compile(gfx, fonts); - - var areFontsReady = true; - - // Inspect fonts and translate the missing one - var fontCount = fonts.length; - - for (var i = 0; i < fontCount; i++) { - var font = fonts[i]; - - if (Fonts[font.name]) { - areFontsReady = areFontsReady && !Fonts[font.name].loading; - continue; - } - - new Font(font.name, font.file, font.properties); - - areFontsReady = false; - } - - var pageInterval; - - var delayLoadFont = function() { - for (var i = 0; i < fontCount; i++) { - if (Fonts[font.name].loading) { - return; - } - } - - clearInterval(pageInterval); - - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - - PDFViewer.drawPage(num); - } - - if (!areFontsReady) { - pageInterval = setInterval(delayLoadFont, 10); - return; - } - - page.display(gfx); - } - }, - - changeScale: function(num) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.scale = num / 100; - - var i; - - if (PDFViewer.pdf) { - for (i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - } - } - - for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { - var option = PDFViewer.scaleSelect.childNodes[i]; - - if (option.value == num) { - if (!option.selected) { - option.selected = 'selected'; - } - } else { - if (option.selected) { - option.removeAttribute('selected'); - } - } - } - - PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; - }, - - goToPage: function(num) { - if (1 <= num && num <= PDFViewer.numberOfPages) { - PDFViewer.pageNumber = num; - PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; - PDFViewer.willJumpToPage = true; - - document.location.hash = PDFViewer.pageNumber; - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } - }, - - goToPreviousPage: function() { - if (PDFViewer.pageNumber > 1) { - PDFViewer.goToPage(--PDFViewer.pageNumber); - } - }, - - goToNextPage: function() { - if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { - PDFViewer.goToPage(++PDFViewer.pageNumber); - } - }, - - openURL: function(url) { - PDFViewer.url = url; - document.title = url; - - var req = new XMLHttpRequest(); - req.open('GET', url); - req.mozResponseType = req.responseType = 'arraybuffer'; - req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; - - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status === req.expected) { - var data = req.mozResponseArrayBuffer || - req.mozResponse || - req.responseArrayBuffer || - req.response; - - 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' : ''; - } -}; - -window.onload = function() { - - // Parse the URL query parameters into a cached object. - PDFViewer.queryParams = function() { - var qs = window.location.search.substring(1); - var kvs = qs.split('&'); - var params = {}; - for (var i = 0; i < kvs.length; ++i) { - var kv = kvs[i].split('='); - params[unescape(kv[0])] = unescape(kv[1]); - } - - return params; - }(); - - PDFViewer.element = document.getElementById('viewer'); - - PDFViewer.pageNumberInput = document.getElementById('pageNumber'); - PDFViewer.pageNumberInput.onkeydown = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // Up arrow key. - if (charCode === 38) { - PDFViewer.goToNextPage(); - this.select(); - } - - // Down arrow key. - else if (charCode === 40) { - PDFViewer.goToPreviousPage(); - this.select(); - } - - // All other non-numeric keys (excluding Left arrow, Right arrow, - // Backspace, and Delete keys). - else if ((charCode < 48 || charCode > 57) && - charCode !== 8 && // Backspace - charCode !== 46 && // Delete - charCode !== 37 && // Left arrow - charCode !== 39 // Right arrow - ) { - return false; - } - - return true; - }; - PDFViewer.pageNumberInput.onkeyup = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // All numeric keys, Backspace, and Delete. - if ((charCode >= 48 && charCode <= 57) || - charCode === 8 || // Backspace - charCode === 46 // Delete - ) { - PDFViewer.goToPage(this.value); - } - - this.focus(); - }; - - PDFViewer.previousPageButton = document.getElementById('previousPageButton'); - PDFViewer.previousPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToPreviousPage(); - } - }; - PDFViewer.previousPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.previousPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.previousPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.nextPageButton = document.getElementById('nextPageButton'); - PDFViewer.nextPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToNextPage(); - } - }; - PDFViewer.nextPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.nextPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.nextPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.scaleSelect = document.getElementById('scaleSelect'); - 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.openURL(PDFViewer.queryParams.file || PDFViewer.url); - - window.onscroll = function(evt) { - var lastPagesDrawn = PDFViewer.lastPagesDrawn; - var visiblePages = PDFViewer.visiblePages(); - - var pagesToDraw = []; - var pagesToKeep = []; - var pagesToRemove = []; - - var i; - - // Determine which visible pages were not previously drawn. - for (i = 0; i < visiblePages.length; i++) { - if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { - pagesToDraw.push(visiblePages[i]); - PDFViewer.drawPage(visiblePages[i]); - } else { - pagesToKeep.push(visiblePages[i]); - } - } - - // Determine which previously drawn pages are no longer visible. - for (i = 0; i < lastPagesDrawn.length; i++) { - if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { - pagesToRemove.push(lastPagesDrawn[i]); - PDFViewer.removePage(lastPagesDrawn[i]); - } - } - - PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); - - // Update the page number input with the current page number. - if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { - PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } else { - PDFViewer.willJumpToPage = false; - } - }; -}; diff --git a/multi_page_viewer.css b/multi_page_viewer.css new file mode 100644 index 000000000..fce7d7b32 --- /dev/null +++ b/multi_page_viewer.css @@ -0,0 +1,197 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ + +body { + background-color: #929292; + font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; + margin: 0px; + padding: 0px; +} + +canvas { + box-shadow: 0px 4px 10px #000; + -moz-box-shadow: 0px 4px 10px #000; + -webkit-box-shadow: 0px 4px 10px #000; +} + +span { + font-size: 0.8em; +} + +.control { + display: inline-block; + float: left; + margin: 0px 20px 0px 0px; + padding: 0px 4px 0px 0px; +} + +.control > input { + float: left; + border: 1px solid #4d4d4d; + height: 20px; + padding: 0px; + margin: 0px 2px 0px 0px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); +} + +.control > select { + float: left; + border: 1px solid #4d4d4d; + height: 22px; + padding: 2px 0px 0px; + margin: 0px 0px 1px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); +} + +.control > span { + cursor: default; + float: left; + height: 18px; + margin: 5px 2px 0px; + padding: 0px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; +} + +.control .label { + clear: both; + float: left; + font-size: 0.65em; + margin: 2px 0px 0px; + position: relative; + text-align: center; + width: 100%; +} + +.page { + width: 816px; + height: 1056px; + margin: 10px auto; +} + +#controls { + background-color: #eee; + border-bottom: 1px solid #666; + padding: 4px 0px 0px 8px; + position: fixed; + left: 0px; + top: 0px; + height: 40px; + width: 100%; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; +} + +#controls input { + user-select: text; + -moz-user-select: text; + -webkit-user-select: text; +} + +#previousPageButton { + background: url('images/buttons.png') no-repeat 0px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; +} + +#previousPageButton.down { + background: url('images/buttons.png') no-repeat 0px -46px; +} + +#previousPageButton.disabled { + background: url('images/buttons.png') no-repeat 0px 0px; +} + +#nextPageButton { + background: url('images/buttons.png') no-repeat -28px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; +} + +#nextPageButton.down { + background: url('images/buttons.png') no-repeat -28px -46px; +} + +#nextPageButton.disabled { + 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 new file mode 100644 index 000000000..47234686d --- /dev/null +++ b/multi_page_viewer.html @@ -0,0 +1,51 @@ + + + +pdf.js Multi-Page Viewer + + + + + + + + +
+ + + + Previous/Next + + + + / + -- + Page Number + + + + Zoom + + + + + Open File + +
+ +
+ + diff --git a/multi_page_viewer.js b/multi_page_viewer.js new file mode 100644 index 000000000..ddb541175 --- /dev/null +++ b/multi_page_viewer.js @@ -0,0 +1,458 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ + +"use strict"; + +var PDFViewer = { + queryParams: {}, + + element: null, + + previousPageButton: null, + nextPageButton: null, + pageNumberInput: null, + scaleSelect: null, + fileInput: null, + + willJumpToPage: false, + + pdf: null, + + url: 'compressed.tracemonkey-pldi-09.pdf', + pageNumber: 1, + numberOfPages: 1, + + scale: 1.0, + + pageWidth: function() { + return 816 * PDFViewer.scale; + }, + + pageHeight: function() { + return 1056 * PDFViewer.scale; + }, + + lastPagesDrawn: [], + + visiblePages: function() { + var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. + var windowTop = window.pageYOffset; + var windowBottom = window.pageYOffset + window.innerHeight; + var pageStartIndex = Math.floor(windowTop / pageHeight); + var pageStopIndex = Math.ceil(windowBottom / pageHeight); + + var pages = []; + + for (var i = pageStartIndex; i <= pageStopIndex; i++) { + pages.push(i + 1); + } + + return pages; + }, + + createPage: function(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'; + + PDFViewer.element.appendChild(anchor); + PDFViewer.element.appendChild(div); + }, + + removePage: function(num) { + var div = document.getElementById('pageContainer' + num); + + if (div) { + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + } + }, + + drawPage: function(num) { + if (!PDFViewer.pdf) { + return; + } + + var div = document.getElementById('pageContainer' + num); + var canvas = document.createElement('canvas'); + + if (div && !div.hasChildNodes()) { + div.appendChild(canvas); + + var page = PDFViewer.pdf.getPage(num); + + canvas.id = 'page' + num; + canvas.mozOpaque = true; + + // Canvas dimensions must be specified in CSS pixels. CSS pixels + // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. + canvas.width = PDFViewer.pageWidth(); + canvas.height = PDFViewer.pageHeight(); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + var gfx = new CanvasGraphics(ctx); + var fonts = []; + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + page.compile(gfx, fonts); + + var areFontsReady = true; + + // Inspect fonts and translate the missing one + var fontCount = fonts.length; + + for (var i = 0; i < fontCount; i++) { + var font = fonts[i]; + + if (Fonts[font.name]) { + areFontsReady = areFontsReady && !Fonts[font.name].loading; + continue; + } + + new Font(font.name, font.file, font.properties); + + areFontsReady = false; + } + + var pageInterval; + + var delayLoadFont = function() { + for (var i = 0; i < fontCount; i++) { + if (Fonts[font.name].loading) { + return; + } + } + + clearInterval(pageInterval); + + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + + PDFViewer.drawPage(num); + } + + if (!areFontsReady) { + pageInterval = setInterval(delayLoadFont, 10); + return; + } + + page.display(gfx); + } + }, + + changeScale: function(num) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } + + PDFViewer.scale = num / 100; + + var i; + + if (PDFViewer.pdf) { + for (i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + } + } + + for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { + var option = PDFViewer.scaleSelect.childNodes[i]; + + if (option.value == num) { + if (!option.selected) { + option.selected = 'selected'; + } + } else { + if (option.selected) { + option.removeAttribute('selected'); + } + } + } + + PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; + }, + + goToPage: function(num) { + if (1 <= num && num <= PDFViewer.numberOfPages) { + PDFViewer.pageNumber = num; + PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; + PDFViewer.willJumpToPage = true; + + document.location.hash = PDFViewer.pageNumber; + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + } + }, + + goToPreviousPage: function() { + if (PDFViewer.pageNumber > 1) { + PDFViewer.goToPage(--PDFViewer.pageNumber); + } + }, + + goToNextPage: function() { + if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { + PDFViewer.goToPage(++PDFViewer.pageNumber); + } + }, + + openURL: function(url) { + PDFViewer.url = url; + document.title = url; + + var req = new XMLHttpRequest(); + req.open('GET', url); + req.mozResponseType = req.responseType = 'arraybuffer'; + req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; + + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status === req.expected) { + var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; + + 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' : ''; + } +}; + +window.onload = function() { + + // Parse the URL query parameters into a cached object. + PDFViewer.queryParams = function() { + var qs = window.location.search.substring(1); + var kvs = qs.split('&'); + var params = {}; + + for (var i = 0; i < kvs.length; ++i) { + var kv = kvs[i].split('='); + params[unescape(kv[0])] = unescape(kv[1]); + } + + return params; + }(); + + PDFViewer.element = document.getElementById('viewer'); + + PDFViewer.pageNumberInput = document.getElementById('pageNumber'); + PDFViewer.pageNumberInput.onkeydown = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // Up arrow key. + if (charCode === 38) { + PDFViewer.goToNextPage(); + this.select(); + } + + // Down arrow key. + else if (charCode === 40) { + PDFViewer.goToPreviousPage(); + this.select(); + } + + // All other non-numeric keys (excluding Left arrow, Right arrow, + // Backspace, and Delete keys). + else if ((charCode < 48 || charCode > 57) && + charCode !== 8 && // Backspace + charCode !== 46 && // Delete + charCode !== 37 && // Left arrow + charCode !== 39 // Right arrow + ) { + return false; + } + + return true; + }; + PDFViewer.pageNumberInput.onkeyup = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // All numeric keys, Backspace, and Delete. + if ((charCode >= 48 && charCode <= 57) || + charCode === 8 || // Backspace + charCode === 46 // Delete + ) { + PDFViewer.goToPage(this.value); + } + + this.focus(); + }; + + PDFViewer.previousPageButton = document.getElementById('previousPageButton'); + PDFViewer.previousPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToPreviousPage(); + } + }; + PDFViewer.previousPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.previousPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.previousPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.nextPageButton = document.getElementById('nextPageButton'); + PDFViewer.nextPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToNextPage(); + } + }; + PDFViewer.nextPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.nextPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.nextPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.scaleSelect = document.getElementById('scaleSelect'); + 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.openURL(PDFViewer.queryParams.file || PDFViewer.url); + + window.onscroll = function(evt) { + var lastPagesDrawn = PDFViewer.lastPagesDrawn; + var visiblePages = PDFViewer.visiblePages(); + + var pagesToDraw = []; + var pagesToKeep = []; + var pagesToRemove = []; + + var i; + + // Determine which visible pages were not previously drawn. + for (i = 0; i < visiblePages.length; i++) { + if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { + pagesToDraw.push(visiblePages[i]); + PDFViewer.drawPage(visiblePages[i]); + } else { + pagesToKeep.push(visiblePages[i]); + } + } + + // Determine which previously drawn pages are no longer visible. + for (i = 0; i < lastPagesDrawn.length; i++) { + if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { + pagesToRemove.push(lastPagesDrawn[i]); + PDFViewer.removePage(lastPagesDrawn[i]); + } + } + + PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); + + // Update the page number input with the current page number. + if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { + PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + } else { + PDFViewer.willJumpToPage = false; + } + }; +}; From d704bfacf418db5a891bea054218dd1021e54fc0 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 02:58:17 +0200 Subject: [PATCH 82/98] Fix Windows 'hmtx' bust table --- fonts.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/fonts.js b/fonts.js index 1169e21c6..6f5adbeef 100644 --- a/fonts.js +++ b/fonts.js @@ -710,11 +710,15 @@ var Font = (function () { createTableEntry(otf, offsets, "hhea", hhea); /** HMTX */ - hmtx = "\x01\xF4\x00\x00"; + /* For some reasons, probably related to how the backend handle fonts, + * Linux seems to ignore this file and prefer the data from the CFF itself + * while Windows use this data. So be careful if you hack on Linux and + * have to touch the 'hmtx' table + */ + hmtx = "\x01\xF4\x00\x00"; // Fake .notdef + var width = 0, lsb = 0; for (var i = 0; i < charstrings.length; i++) { - var charstring = charstrings[i].charstring; - var width = charstring[1]; - var lsb = charstring[0]; + width = charstrings[i].charstring[0]; hmtx += string16(width) + string16(lsb); } hmtx = stringToArray(hmtx); @@ -1314,7 +1318,7 @@ CFF.prototype = { "hvcurveto": 31, }, - flattenCharstring: function flattenCharstring(glyph, charstring, subrs) { + flattenCharstring: function flattenCharstring(charstring, subrs) { var i = 0; while (true) { var obj = charstring[i]; @@ -1326,7 +1330,7 @@ CFF.prototype = { case "callsubr": var subr = subrs[charstring[i - 1]]; if (subr.length > 1) { - subr = this.flattenCharstring(glyph, subr, subrs); + subr = this.flattenCharstring(subr, subrs); subr.pop(); charstring.splice(i - 1, 2, subr); } else { @@ -1420,11 +1424,11 @@ CFF.prototype = { wrap: function wrap(name, charstrings, subrs, properties) { // Starts the conversion of the Type1 charstrings to Type2 - var glyphs = charstrings.slice(); - var glyphsCount = glyphs.length; - for (var i = 0; i < glyphs.length; i++) { - var charstring = glyphs[i]; - glyphs[i] = this.flattenCharstring(charstring.glyph, charstring.charstring, subrs); + var glyphs = []; + var glyphsCount = charstrings.length; + for (var i = 0; i < glyphsCount; i++) { + var charstring = charstrings[i].charstring; + glyphs.push(this.flattenCharstring(charstring.slice(), subrs)); } // Create a CFF font data From 40006194eaa6283c22ce87ef358f5b9600e7ce85 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 03:01:41 +0200 Subject: [PATCH 83/98] Don't read the lsb instead of the width --- fonts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fonts.js b/fonts.js index 6f5adbeef..a995c55eb 100644 --- a/fonts.js +++ b/fonts.js @@ -718,7 +718,7 @@ var Font = (function () { hmtx = "\x01\xF4\x00\x00"; // Fake .notdef var width = 0, lsb = 0; for (var i = 0; i < charstrings.length; i++) { - width = charstrings[i].charstring[0]; + width = charstrings[i].charstring[1]; hmtx += string16(width) + string16(lsb); } hmtx = stringToArray(hmtx); From 0b48feb2080e2fa28b7708cfe123f834a7e1277e Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo Date: Thu, 23 Jun 2011 21:11:50 -0400 Subject: [PATCH 84/98] Revert ae2e637044e5abe5d4c119de133e65e0aef4a9b5^..HEAD --- multi-page-viewer.css | 197 ++++++++++ multi-page-viewer.html | 51 +++ multi-page-viewer.js | 466 ++++++++++++++++++++++ multi_page_viewer.css | 234 ++++++------ multi_page_viewer.html | 76 ++-- multi_page_viewer.js | 850 +++++++++++++++++++++-------------------- pdf.js | 7 +- 7 files changed, 1300 insertions(+), 581 deletions(-) create mode 100644 multi-page-viewer.css create mode 100644 multi-page-viewer.html create mode 100644 multi-page-viewer.js diff --git a/multi-page-viewer.css b/multi-page-viewer.css new file mode 100644 index 000000000..7f4701022 --- /dev/null +++ b/multi-page-viewer.css @@ -0,0 +1,197 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ + +body { + background-color: #929292; + font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; + margin: 0px; + padding: 0px; +} + +canvas { + box-shadow: 0px 4px 10px #000; + -moz-box-shadow: 0px 4px 10px #000; + -webkit-box-shadow: 0px 4px 10px #000; +} + +span { + font-size: 0.8em; +} + +.control { + display: inline-block; + float: left; + margin: 0px 20px 0px 0px; + padding: 0px 4px 0px 0px; +} + +.control > input { + float: left; + border: 1px solid #4d4d4d; + height: 20px; + padding: 0px; + margin: 0px 2px 0px 0px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); +} + +.control > select { + float: left; + border: 1px solid #4d4d4d; + height: 22px; + padding: 2px 0px 0px; + margin: 0px 0px 1px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); +} + +.control > span { + cursor: default; + float: left; + height: 18px; + margin: 5px 2px 0px; + padding: 0px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; +} + +.control .label { + clear: both; + float: left; + font-size: 0.65em; + margin: 2px 0px 0px; + position: relative; + text-align: center; + width: 100%; +} + +.page { + width: 816px; + height: 1056px; + margin: 10px auto; +} + +#controls { + background-color: #eee; + border-bottom: 1px solid #666; + padding: 4px 0px 0px 8px; + position: fixed; + left: 0px; + top: 0px; + height: 40px; + width: 100%; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; +} + +#controls input { + user-select: text; + -moz-user-select: text; + -webkit-user-select: text; +} + +#previousPageButton { + background: url('images/buttons.png') no-repeat 0px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; +} + +#previousPageButton.down { + background: url('images/buttons.png') no-repeat 0px -46px; +} + +#previousPageButton.disabled { + background: url('images/buttons.png') no-repeat 0px 0px; +} + +#nextPageButton { + background: url('images/buttons.png') no-repeat -28px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; +} + +#nextPageButton.down { + background: url('images/buttons.png') no-repeat -28px -46px; +} + +#nextPageButton.disabled { + 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 new file mode 100644 index 000000000..ffbdfe707 --- /dev/null +++ b/multi-page-viewer.html @@ -0,0 +1,51 @@ + + + +pdf.js Multi-Page Viewer + + + + + + + + +
+ + + + Previous/Next + + + + / + -- + Page Number + + + + Zoom + + + + + Open File + +
+ +
+ + diff --git a/multi-page-viewer.js b/multi-page-viewer.js new file mode 100644 index 000000000..baad7809e --- /dev/null +++ b/multi-page-viewer.js @@ -0,0 +1,466 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ + +"use strict"; + +var PDFViewer = { + queryParams: {}, + + element: null, + + previousPageButton: null, + nextPageButton: null, + pageNumberInput: null, + scaleSelect: null, + fileInput: null, + + willJumpToPage: false, + + pdf: null, + + url: 'compressed.tracemonkey-pldi-09.pdf', + pageNumber: 1, + numberOfPages: 1, + + scale: 1.0, + + pageWidth: function() { + return 816 * PDFViewer.scale; + }, + + pageHeight: function() { + return 1056 * PDFViewer.scale; + }, + + lastPagesDrawn: [], + + visiblePages: function() { + var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. + var windowTop = window.pageYOffset; + var windowBottom = window.pageYOffset + window.innerHeight; + var pageStartIndex = Math.floor(windowTop / pageHeight); + var pageStopIndex = Math.ceil(windowBottom / pageHeight); + + var pages = []; + + for (var i = pageStartIndex; i <= pageStopIndex; i++) { + pages.push(i + 1); + } + + return pages; + }, + + createPage: function(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'; + + PDFViewer.element.appendChild(anchor); + PDFViewer.element.appendChild(div); + }, + + removePage: function(num) { + var div = document.getElementById('pageContainer' + num); + + if (div) { + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + } + }, + + drawPage: function(num) { + if (!PDFViewer.pdf) { + return; + } + + var div = document.getElementById('pageContainer' + num); + var canvas = document.createElement('canvas'); + + if (div && !div.hasChildNodes()) { + div.appendChild(canvas); + + var page = PDFViewer.pdf.getPage(num); + + canvas.id = 'page' + num; + canvas.mozOpaque = true; + + // Canvas dimensions must be specified in CSS pixels. CSS pixels + // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. + canvas.width = PDFViewer.pageWidth(); + canvas.height = PDFViewer.pageHeight(); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + var gfx = new CanvasGraphics(ctx); + var fonts = []; + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + page.compile(gfx, fonts); + + var areFontsReady = true; + + // Inspect fonts and translate the missing one + var fontCount = fonts.length; + + for (var i = 0; i < fontCount; i++) { + var font = fonts[i]; + + if (Fonts[font.name]) { + areFontsReady = areFontsReady && !Fonts[font.name].loading; + continue; + } + + new Font(font.name, font.file, font.properties); + + areFontsReady = false; + } + + var pageInterval; + + var delayLoadFont = function() { + for (var i = 0; i < fontCount; i++) { + if (Fonts[font.name].loading) { + return; + } + } + + clearInterval(pageInterval); + + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + + PDFViewer.drawPage(num); + } + + if (!areFontsReady) { + pageInterval = setInterval(delayLoadFont, 10); + return; + } + + page.display(gfx); + } + }, + + changeScale: function(num) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } + + PDFViewer.scale = num / 100; + + var i; + + if (PDFViewer.pdf) { + for (i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + } + } + + for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { + var option = PDFViewer.scaleSelect.childNodes[i]; + + if (option.value == num) { + if (!option.selected) { + option.selected = 'selected'; + } + } else { + if (option.selected) { + option.removeAttribute('selected'); + } + } + } + + PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; + }, + + goToPage: function(num) { + if (1 <= num && num <= PDFViewer.numberOfPages) { + PDFViewer.pageNumber = num; + PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; + PDFViewer.willJumpToPage = true; + + document.location.hash = PDFViewer.pageNumber; + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; + } + }, + + goToPreviousPage: function() { + if (PDFViewer.pageNumber > 1) { + PDFViewer.goToPage(--PDFViewer.pageNumber); + } + }, + + goToNextPage: function() { + if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { + PDFViewer.goToPage(++PDFViewer.pageNumber); + } + }, + + openURL: function(url) { + PDFViewer.url = url; + document.title = url; + + var req = new XMLHttpRequest(); + req.open('GET', url); + req.mozResponseType = req.responseType = 'arraybuffer'; + req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; + + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status === req.expected) { + var data = req.mozResponseArrayBuffer || + req.mozResponse || + req.responseArrayBuffer || + req.response; + + 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' : ''; + } +}; + +window.onload = function() { + + // Parse the URL query parameters into a cached object. + PDFViewer.queryParams = function() { + var qs = window.location.search.substring(1); + var kvs = qs.split('&'); + var params = {}; + for (var i = 0; i < kvs.length; ++i) { + var kv = kvs[i].split('='); + params[unescape(kv[0])] = unescape(kv[1]); + } + + return params; + }(); + + PDFViewer.element = document.getElementById('viewer'); + + PDFViewer.pageNumberInput = document.getElementById('pageNumber'); + PDFViewer.pageNumberInput.onkeydown = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // Up arrow key. + if (charCode === 38) { + PDFViewer.goToNextPage(); + this.select(); + } + + // Down arrow key. + else if (charCode === 40) { + PDFViewer.goToPreviousPage(); + this.select(); + } + + // All other non-numeric keys (excluding Left arrow, Right arrow, + // Backspace, and Delete keys). + else if ((charCode < 48 || charCode > 57) && + charCode !== 8 && // Backspace + charCode !== 46 && // Delete + charCode !== 37 && // Left arrow + charCode !== 39 // Right arrow + ) { + return false; + } + + return true; + }; + PDFViewer.pageNumberInput.onkeyup = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // All numeric keys, Backspace, and Delete. + if ((charCode >= 48 && charCode <= 57) || + charCode === 8 || // Backspace + charCode === 46 // Delete + ) { + PDFViewer.goToPage(this.value); + } + + this.focus(); + }; + + PDFViewer.previousPageButton = document.getElementById('previousPageButton'); + PDFViewer.previousPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToPreviousPage(); + } + }; + PDFViewer.previousPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.previousPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.previousPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.nextPageButton = document.getElementById('nextPageButton'); + PDFViewer.nextPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToNextPage(); + } + }; + PDFViewer.nextPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.nextPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.nextPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.scaleSelect = document.getElementById('scaleSelect'); + 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.openURL(PDFViewer.queryParams.file || PDFViewer.url); + + window.onscroll = function(evt) { + var lastPagesDrawn = PDFViewer.lastPagesDrawn; + var visiblePages = PDFViewer.visiblePages(); + + var pagesToDraw = []; + var pagesToKeep = []; + var pagesToRemove = []; + + var i; + + // Determine which visible pages were not previously drawn. + for (i = 0; i < visiblePages.length; i++) { + if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { + pagesToDraw.push(visiblePages[i]); + PDFViewer.drawPage(visiblePages[i]); + } else { + pagesToKeep.push(visiblePages[i]); + } + } + + // Determine which previously drawn pages are no longer visible. + for (i = 0; i < lastPagesDrawn.length; i++) { + if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { + pagesToRemove.push(lastPagesDrawn[i]); + PDFViewer.removePage(lastPagesDrawn[i]); + } + } + + PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); + + // Update the page number input with the current page number. + if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { + PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; + } else { + PDFViewer.willJumpToPage = false; + } + }; +}; diff --git a/multi_page_viewer.css b/multi_page_viewer.css index fce7d7b32..7f4701022 100644 --- a/multi_page_viewer.css +++ b/multi_page_viewer.css @@ -2,196 +2,196 @@ /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ body { - background-color: #929292; - font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; - margin: 0px; - padding: 0px; + background-color: #929292; + font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; + margin: 0px; + padding: 0px; } canvas { - box-shadow: 0px 4px 10px #000; - -moz-box-shadow: 0px 4px 10px #000; - -webkit-box-shadow: 0px 4px 10px #000; + box-shadow: 0px 4px 10px #000; + -moz-box-shadow: 0px 4px 10px #000; + -webkit-box-shadow: 0px 4px 10px #000; } span { - font-size: 0.8em; + font-size: 0.8em; } .control { - display: inline-block; - float: left; - margin: 0px 20px 0px 0px; - padding: 0px 4px 0px 0px; + display: inline-block; + float: left; + margin: 0px 20px 0px 0px; + padding: 0px 4px 0px 0px; } .control > input { - float: left; - border: 1px solid #4d4d4d; - height: 20px; - padding: 0px; - margin: 0px 2px 0px 0px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + float: left; + border: 1px solid #4d4d4d; + height: 20px; + padding: 0px; + margin: 0px 2px 0px 0px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); } .control > select { - float: left; - border: 1px solid #4d4d4d; - height: 22px; - padding: 2px 0px 0px; - margin: 0px 0px 1px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + float: left; + border: 1px solid #4d4d4d; + height: 22px; + padding: 2px 0px 0px; + margin: 0px 0px 1px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); } .control > span { - cursor: default; - float: left; - height: 18px; - margin: 5px 2px 0px; - padding: 0px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; + cursor: default; + float: left; + height: 18px; + margin: 5px 2px 0px; + padding: 0px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; } .control .label { - clear: both; - float: left; - font-size: 0.65em; - margin: 2px 0px 0px; - position: relative; - text-align: center; - width: 100%; + clear: both; + float: left; + font-size: 0.65em; + margin: 2px 0px 0px; + position: relative; + text-align: center; + width: 100%; } .page { - width: 816px; - height: 1056px; - margin: 10px auto; + width: 816px; + height: 1056px; + margin: 10px auto; } #controls { - background-color: #eee; - border-bottom: 1px solid #666; - padding: 4px 0px 0px 8px; - position: fixed; - left: 0px; - top: 0px; - height: 40px; - width: 100%; - box-shadow: 0px 2px 8px #000; - -moz-box-shadow: 0px 2px 8px #000; - -webkit-box-shadow: 0px 2px 8px #000; + background-color: #eee; + border-bottom: 1px solid #666; + padding: 4px 0px 0px 8px; + position: fixed; + left: 0px; + top: 0px; + height: 40px; + width: 100%; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; } #controls input { - user-select: text; - -moz-user-select: text; - -webkit-user-select: text; + user-select: text; + -moz-user-select: text; + -webkit-user-select: text; } #previousPageButton { - background: url('images/buttons.png') no-repeat 0px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; + background: url('images/buttons.png') no-repeat 0px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; } #previousPageButton.down { - background: url('images/buttons.png') no-repeat 0px -46px; + background: url('images/buttons.png') no-repeat 0px -46px; } #previousPageButton.disabled { - background: url('images/buttons.png') no-repeat 0px 0px; + background: url('images/buttons.png') no-repeat 0px 0px; } #nextPageButton { - background: url('images/buttons.png') no-repeat -28px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; + background: url('images/buttons.png') no-repeat -28px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; } #nextPageButton.down { - background: url('images/buttons.png') no-repeat -28px -46px; + background: url('images/buttons.png') no-repeat -28px -46px; } #nextPageButton.disabled { - background: url('images/buttons.png') no-repeat -28px 0px; + 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; + 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; + background: url('images/buttons.png') no-repeat -56px -46px; } #openFileButton.disabled { - background: url('images/buttons.png') no-repeat -56px 0px; + background: url('images/buttons.png') no-repeat -56px 0px; } #fileInput { - display: none; + display: none; } #pageNumber { - text-align: right; + 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; + 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; + position: absolute; + overflow: hidden; + overflow-y: auto; + top: 40px; + right: 10px; + bottom: 10px; + left: 10px; } #sidebarContentView { - height: auto; - width: 100px; + height: auto; + width: 100px; } #viewer { - margin: 44px 0px 0px; - padding: 8px 0px; + margin: 44px 0px 0px; + padding: 8px 0px; } diff --git a/multi_page_viewer.html b/multi_page_viewer.html index 47234686d..ffbdfe707 100644 --- a/multi_page_viewer.html +++ b/multi_page_viewer.html @@ -3,49 +3,49 @@ pdf.js Multi-Page Viewer - + - + -
- - - - Previous/Next - - - - / - -- - Page Number - - - - Zoom - - - - - Open File - -
- -
+ +
diff --git a/multi_page_viewer.js b/multi_page_viewer.js index ddb541175..baad7809e 100644 --- a/multi_page_viewer.js +++ b/multi_page_viewer.js @@ -4,455 +4,463 @@ "use strict"; var PDFViewer = { - queryParams: {}, - - element: null, - - previousPageButton: null, - nextPageButton: null, - pageNumberInput: null, - scaleSelect: null, - fileInput: null, - - willJumpToPage: false, - - pdf: null, - - url: 'compressed.tracemonkey-pldi-09.pdf', - pageNumber: 1, - numberOfPages: 1, - - scale: 1.0, - - pageWidth: function() { - return 816 * PDFViewer.scale; - }, - - pageHeight: function() { - return 1056 * PDFViewer.scale; - }, - - lastPagesDrawn: [], - - visiblePages: function() { - var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. - var windowTop = window.pageYOffset; - var windowBottom = window.pageYOffset + window.innerHeight; - var pageStartIndex = Math.floor(windowTop / pageHeight); - var pageStopIndex = Math.ceil(windowBottom / pageHeight); + queryParams: {}, + + element: null, + + previousPageButton: null, + nextPageButton: null, + pageNumberInput: null, + scaleSelect: null, + fileInput: null, - var pages = []; + willJumpToPage: false, + + pdf: null, + + url: 'compressed.tracemonkey-pldi-09.pdf', + pageNumber: 1, + numberOfPages: 1, + + scale: 1.0, + + pageWidth: function() { + return 816 * PDFViewer.scale; + }, + + pageHeight: function() { + return 1056 * PDFViewer.scale; + }, - for (var i = pageStartIndex; i <= pageStopIndex; i++) { - pages.push(i + 1); - } + lastPagesDrawn: [], - return pages; - }, - - createPage: function(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'; - - PDFViewer.element.appendChild(anchor); - PDFViewer.element.appendChild(div); - }, - - removePage: function(num) { - var div = document.getElementById('pageContainer' + num); - - if (div) { - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - } - }, - - drawPage: function(num) { - if (!PDFViewer.pdf) { - return; - } - - var div = document.getElementById('pageContainer' + num); - var canvas = document.createElement('canvas'); - - if (div && !div.hasChildNodes()) { - div.appendChild(canvas); - - var page = PDFViewer.pdf.getPage(num); - - canvas.id = 'page' + num; - canvas.mozOpaque = true; - - // Canvas dimensions must be specified in CSS pixels. CSS pixels - // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. - canvas.width = PDFViewer.pageWidth(); - canvas.height = PDFViewer.pageHeight(); - - var ctx = canvas.getContext('2d'); - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var gfx = new CanvasGraphics(ctx); - var fonts = []; - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - page.compile(gfx, fonts); - - var areFontsReady = true; - - // Inspect fonts and translate the missing one - var fontCount = fonts.length; - - for (var i = 0; i < fontCount; i++) { - var font = fonts[i]; - - if (Fonts[font.name]) { - areFontsReady = areFontsReady && !Fonts[font.name].loading; - continue; + visiblePages: function() { + var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. + var windowTop = window.pageYOffset; + var windowBottom = window.pageYOffset + window.innerHeight; + var pageStartIndex = Math.floor(windowTop / pageHeight); + var pageStopIndex = Math.ceil(windowBottom / pageHeight); + + var pages = []; + + for (var i = pageStartIndex; i <= pageStopIndex; i++) { + pages.push(i + 1); } + + return pages; + }, + + createPage: function(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'; - new Font(font.name, font.file, font.properties); + PDFViewer.element.appendChild(anchor); + PDFViewer.element.appendChild(div); + }, + + removePage: function(num) { + var div = document.getElementById('pageContainer' + num); - areFontsReady = false; - } - - var pageInterval; - - var delayLoadFont = function() { - for (var i = 0; i < fontCount; i++) { - if (Fonts[font.name].loading) { + if (div) { + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + } + }, + + drawPage: function(num) { + if (!PDFViewer.pdf) { return; - } } - clearInterval(pageInterval); + var div = document.getElementById('pageContainer' + num); + var canvas = document.createElement('canvas'); - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); + if (div && !div.hasChildNodes()) { + div.appendChild(canvas); + + var page = PDFViewer.pdf.getPage(num); + + canvas.id = 'page' + num; + canvas.mozOpaque = true; + + // Canvas dimensions must be specified in CSS pixels. CSS pixels + // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. + canvas.width = PDFViewer.pageWidth(); + canvas.height = PDFViewer.pageHeight(); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + var gfx = new CanvasGraphics(ctx); + var fonts = []; + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + page.compile(gfx, fonts); + + var areFontsReady = true; + + // Inspect fonts and translate the missing one + var fontCount = fonts.length; + + for (var i = 0; i < fontCount; i++) { + var font = fonts[i]; + + if (Fonts[font.name]) { + areFontsReady = areFontsReady && !Fonts[font.name].loading; + continue; + } + + new Font(font.name, font.file, font.properties); + + areFontsReady = false; + } + + var pageInterval; + + var delayLoadFont = function() { + for (var i = 0; i < fontCount; i++) { + if (Fonts[font.name].loading) { + return; + } + } + + clearInterval(pageInterval); + + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + + PDFViewer.drawPage(num); + } + + if (!areFontsReady) { + pageInterval = setInterval(delayLoadFont, 10); + return; + } + + page.display(gfx); + } + }, + + changeScale: function(num) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } + + PDFViewer.scale = num / 100; + + var i; + + if (PDFViewer.pdf) { + for (i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + } + } + + for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { + var option = PDFViewer.scaleSelect.childNodes[i]; + + if (option.value == num) { + if (!option.selected) { + option.selected = 'selected'; + } + } else { + if (option.selected) { + option.removeAttribute('selected'); + } + } } - PDFViewer.drawPage(num); - } - - if (!areFontsReady) { - pageInterval = setInterval(delayLoadFont, 10); - return; - } - - page.display(gfx); - } - }, - - changeScale: function(num) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.scale = num / 100; - - var i; - - if (PDFViewer.pdf) { - for (i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - } - } - - for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { - var option = PDFViewer.scaleSelect.childNodes[i]; - - if (option.value == num) { - if (!option.selected) { - option.selected = 'selected'; + PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; + }, + + goToPage: function(num) { + if (1 <= num && num <= PDFViewer.numberOfPages) { + PDFViewer.pageNumber = num; + PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; + PDFViewer.willJumpToPage = true; + + document.location.hash = PDFViewer.pageNumber; + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; } - } else { - if (option.selected) { - option.removeAttribute('selected'); + }, + + goToPreviousPage: function() { + if (PDFViewer.pageNumber > 1) { + PDFViewer.goToPage(--PDFViewer.pageNumber); } - } - } + }, + + goToNextPage: function() { + if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { + PDFViewer.goToPage(++PDFViewer.pageNumber); + } + }, + + openURL: function(url) { + PDFViewer.url = url; + document.title = url; - PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; - }, - - goToPage: function(num) { - if (1 <= num && num <= PDFViewer.numberOfPages) { - PDFViewer.pageNumber = num; - PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; - PDFViewer.willJumpToPage = true; - - document.location.hash = PDFViewer.pageNumber; - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; - } - }, - - goToPreviousPage: function() { - if (PDFViewer.pageNumber > 1) { - PDFViewer.goToPage(--PDFViewer.pageNumber); - } - }, - - goToNextPage: function() { - if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { - PDFViewer.goToPage(++PDFViewer.pageNumber); - } - }, - - openURL: function(url) { - PDFViewer.url = url; - document.title = url; + var req = new XMLHttpRequest(); + req.open('GET', url); + req.mozResponseType = req.responseType = 'arraybuffer'; + req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; - var req = new XMLHttpRequest(); - req.open('GET', url); - req.mozResponseType = req.responseType = 'arraybuffer'; - req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status === req.expected) { + var data = req.mozResponseArrayBuffer || + req.mozResponse || + req.responseArrayBuffer || + req.response; + + PDFViewer.readPDF(data); + } + }; - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status === req.expected) { - var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; + req.send(null); + }, + + readPDF: function(data) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } - PDFViewer.readPDF(data); - } - }; - - req.send(null); - }, + PDFViewer.pdf = new PDFDoc(new Stream(data)); + PDFViewer.numberOfPages = PDFViewer.pdf.numPages; + document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString(); - readPDF: function(data) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); + 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' : ''; } - - 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' : ''; - } }; window.onload = function() { - - // Parse the URL query parameters into a cached object. - PDFViewer.queryParams = function() { - var qs = window.location.search.substring(1); - var kvs = qs.split('&'); - var params = {}; - - for (var i = 0; i < kvs.length; ++i) { - var kv = kvs[i].split('='); - params[unescape(kv[0])] = unescape(kv[1]); - } - - return params; - }(); - PDFViewer.element = document.getElementById('viewer'); - - PDFViewer.pageNumberInput = document.getElementById('pageNumber'); - PDFViewer.pageNumberInput.onkeydown = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // Up arrow key. - if (charCode === 38) { - PDFViewer.goToNextPage(); - this.select(); - } - - // Down arrow key. - else if (charCode === 40) { - PDFViewer.goToPreviousPage(); - this.select(); - } - - // All other non-numeric keys (excluding Left arrow, Right arrow, - // Backspace, and Delete keys). - else if ((charCode < 48 || charCode > 57) && - charCode !== 8 && // Backspace - charCode !== 46 && // Delete - charCode !== 37 && // Left arrow - charCode !== 39 // Right arrow - ) { - return false; - } - - return true; - }; - PDFViewer.pageNumberInput.onkeyup = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // All numeric keys, Backspace, and Delete. - if ((charCode >= 48 && charCode <= 57) || - charCode === 8 || // Backspace - charCode === 46 // Delete - ) { - PDFViewer.goToPage(this.value); - } - - this.focus(); - }; - - PDFViewer.previousPageButton = document.getElementById('previousPageButton'); - PDFViewer.previousPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToPreviousPage(); - } - }; - PDFViewer.previousPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.previousPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.previousPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.nextPageButton = document.getElementById('nextPageButton'); - PDFViewer.nextPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToNextPage(); - } - }; - PDFViewer.nextPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.nextPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.nextPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.scaleSelect = document.getElementById('scaleSelect'); - 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(); + // Parse the URL query parameters into a cached object. + PDFViewer.queryParams = function() { + var qs = window.location.search.substring(1); + var kvs = qs.split('&'); + var params = {}; + for (var i = 0; i < kvs.length; ++i) { + var kv = kvs[i].split('='); + params[unescape(kv[0])] = unescape(kv[1]); + } - document.title = file.name; + return params; + }(); + + PDFViewer.element = document.getElementById('viewer'); + + PDFViewer.pageNumberInput = document.getElementById('pageNumber'); + PDFViewer.pageNumberInput.onkeydown = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // Up arrow key. + if (charCode === 38) { + PDFViewer.goToNextPage(); + this.select(); + } - // 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); + // Down arrow key. + else if (charCode === 40) { + PDFViewer.goToPreviousPage(); + this.select(); + } + + // All other non-numeric keys (excluding Left arrow, Right arrow, + // Backspace, and Delete keys). + else if ((charCode < 48 || charCode > 57) && + charCode !== 8 && // Backspace + charCode !== 46 && // Delete + charCode !== 37 && // Left arrow + charCode !== 39 // Right arrow + ) { + return false; + } + + return true; + }; + PDFViewer.pageNumberInput.onkeyup = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // All numeric keys, Backspace, and Delete. + if ((charCode >= 48 && charCode <= 57) || + charCode === 8 || // Backspace + charCode === 46 // Delete + ) { + PDFViewer.goToPage(this.value); + } + + this.focus(); + }; + + PDFViewer.previousPageButton = document.getElementById('previousPageButton'); + PDFViewer.previousPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToPreviousPage(); + } + }; + PDFViewer.previousPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.previousPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.previousPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.nextPageButton = document.getElementById('nextPageButton'); + PDFViewer.nextPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToNextPage(); + } + }; + PDFViewer.nextPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.nextPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.nextPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.scaleSelect = document.getElementById('scaleSelect'); + 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' : ''; }; - // 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.openURL(PDFViewer.queryParams.file || PDFViewer.url); - - window.onscroll = function(evt) { - var lastPagesDrawn = PDFViewer.lastPagesDrawn; - var visiblePages = PDFViewer.visiblePages(); - - var pagesToDraw = []; - var pagesToKeep = []; - var pagesToRemove = []; - - var i; - - // Determine which visible pages were not previously drawn. - for (i = 0; i < visiblePages.length; i++) { - if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { - pagesToDraw.push(visiblePages[i]); - PDFViewer.drawPage(visiblePages[i]); - } else { - pagesToKeep.push(visiblePages[i]); - } - } - - // Determine which previously drawn pages are no longer visible. - for (i = 0; i < lastPagesDrawn.length; i++) { - if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { - pagesToRemove.push(lastPagesDrawn[i]); - PDFViewer.removePage(lastPagesDrawn[i]); - } - } - - PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); - - // Update the page number input with the current page number. - if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { - PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? '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 { - PDFViewer.willJumpToPage = false; + document.getElementById('fileWrapper').style.display = 'none'; } - }; + + PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; + PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; + + PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); + + window.onscroll = function(evt) { + var lastPagesDrawn = PDFViewer.lastPagesDrawn; + var visiblePages = PDFViewer.visiblePages(); + + var pagesToDraw = []; + var pagesToKeep = []; + var pagesToRemove = []; + + var i; + + // Determine which visible pages were not previously drawn. + for (i = 0; i < visiblePages.length; i++) { + if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { + pagesToDraw.push(visiblePages[i]); + PDFViewer.drawPage(visiblePages[i]); + } else { + pagesToKeep.push(visiblePages[i]); + } + } + + // Determine which previously drawn pages are no longer visible. + for (i = 0; i < lastPagesDrawn.length; i++) { + if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { + pagesToRemove.push(lastPagesDrawn[i]); + PDFViewer.removePage(lastPagesDrawn[i]); + } + } + + PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); + + // Update the page number input with the current page number. + if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { + PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? + 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? + 'disabled' : ''; + } else { + PDFViewer.willJumpToPage = false; + } + }; }; diff --git a/pdf.js b/pdf.js index b2b6401fd..ffefc61a1 100644 --- a/pdf.js +++ b/pdf.js @@ -3275,11 +3275,8 @@ var CanvasGraphics = (function() { } } - if (bitsPerComponent !== 8) { - TODO("Support bpc="+ bitsPerComponent); - this.restore(); - return; - } + if (bitsPerComponent !== 8) + error("Unsupported bpc"); var xref = this.xref; var colorSpaces = this.colorSpaces; From d256253d62120a5edc4437ed58c8cc22300afe11 Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo Date: Thu, 23 Jun 2011 21:12:39 -0400 Subject: [PATCH 85/98] Fixed file renaming issues. --- multi_page_viewer.css | 197 ----------------- multi_page_viewer.html | 51 ----- multi_page_viewer.js | 466 ----------------------------------------- 3 files changed, 714 deletions(-) delete mode 100644 multi_page_viewer.css delete mode 100644 multi_page_viewer.html delete mode 100644 multi_page_viewer.js diff --git a/multi_page_viewer.css b/multi_page_viewer.css deleted file mode 100644 index 7f4701022..000000000 --- a/multi_page_viewer.css +++ /dev/null @@ -1,197 +0,0 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ - -body { - background-color: #929292; - font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; - margin: 0px; - padding: 0px; -} - -canvas { - box-shadow: 0px 4px 10px #000; - -moz-box-shadow: 0px 4px 10px #000; - -webkit-box-shadow: 0px 4px 10px #000; -} - -span { - font-size: 0.8em; -} - -.control { - display: inline-block; - float: left; - margin: 0px 20px 0px 0px; - padding: 0px 4px 0px 0px; -} - -.control > input { - float: left; - border: 1px solid #4d4d4d; - height: 20px; - padding: 0px; - margin: 0px 2px 0px 0px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); -} - -.control > select { - float: left; - border: 1px solid #4d4d4d; - height: 22px; - padding: 2px 0px 0px; - margin: 0px 0px 1px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); -} - -.control > span { - cursor: default; - float: left; - height: 18px; - margin: 5px 2px 0px; - padding: 0px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; -} - -.control .label { - clear: both; - float: left; - font-size: 0.65em; - margin: 2px 0px 0px; - position: relative; - text-align: center; - width: 100%; -} - -.page { - width: 816px; - height: 1056px; - margin: 10px auto; -} - -#controls { - background-color: #eee; - border-bottom: 1px solid #666; - padding: 4px 0px 0px 8px; - position: fixed; - left: 0px; - top: 0px; - height: 40px; - width: 100%; - box-shadow: 0px 2px 8px #000; - -moz-box-shadow: 0px 2px 8px #000; - -webkit-box-shadow: 0px 2px 8px #000; -} - -#controls input { - user-select: text; - -moz-user-select: text; - -webkit-user-select: text; -} - -#previousPageButton { - background: url('images/buttons.png') no-repeat 0px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; -} - -#previousPageButton.down { - background: url('images/buttons.png') no-repeat 0px -46px; -} - -#previousPageButton.disabled { - background: url('images/buttons.png') no-repeat 0px 0px; -} - -#nextPageButton { - background: url('images/buttons.png') no-repeat -28px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; -} - -#nextPageButton.down { - background: url('images/buttons.png') no-repeat -28px -46px; -} - -#nextPageButton.disabled { - 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 deleted file mode 100644 index ffbdfe707..000000000 --- a/multi_page_viewer.html +++ /dev/null @@ -1,51 +0,0 @@ - - - -pdf.js Multi-Page Viewer - - - - - - - - -
- - - - Previous/Next - - - - / - -- - Page Number - - - - Zoom - - - - - Open File - -
- -
- - diff --git a/multi_page_viewer.js b/multi_page_viewer.js deleted file mode 100644 index baad7809e..000000000 --- a/multi_page_viewer.js +++ /dev/null @@ -1,466 +0,0 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ - -"use strict"; - -var PDFViewer = { - queryParams: {}, - - element: null, - - previousPageButton: null, - nextPageButton: null, - pageNumberInput: null, - scaleSelect: null, - fileInput: null, - - willJumpToPage: false, - - pdf: null, - - url: 'compressed.tracemonkey-pldi-09.pdf', - pageNumber: 1, - numberOfPages: 1, - - scale: 1.0, - - pageWidth: function() { - return 816 * PDFViewer.scale; - }, - - pageHeight: function() { - return 1056 * PDFViewer.scale; - }, - - lastPagesDrawn: [], - - visiblePages: function() { - var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. - var windowTop = window.pageYOffset; - var windowBottom = window.pageYOffset + window.innerHeight; - var pageStartIndex = Math.floor(windowTop / pageHeight); - var pageStopIndex = Math.ceil(windowBottom / pageHeight); - - var pages = []; - - for (var i = pageStartIndex; i <= pageStopIndex; i++) { - pages.push(i + 1); - } - - return pages; - }, - - createPage: function(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'; - - PDFViewer.element.appendChild(anchor); - PDFViewer.element.appendChild(div); - }, - - removePage: function(num) { - var div = document.getElementById('pageContainer' + num); - - if (div) { - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - } - }, - - drawPage: function(num) { - if (!PDFViewer.pdf) { - return; - } - - var div = document.getElementById('pageContainer' + num); - var canvas = document.createElement('canvas'); - - if (div && !div.hasChildNodes()) { - div.appendChild(canvas); - - var page = PDFViewer.pdf.getPage(num); - - canvas.id = 'page' + num; - canvas.mozOpaque = true; - - // Canvas dimensions must be specified in CSS pixels. CSS pixels - // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. - canvas.width = PDFViewer.pageWidth(); - canvas.height = PDFViewer.pageHeight(); - - var ctx = canvas.getContext('2d'); - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var gfx = new CanvasGraphics(ctx); - var fonts = []; - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - page.compile(gfx, fonts); - - var areFontsReady = true; - - // Inspect fonts and translate the missing one - var fontCount = fonts.length; - - for (var i = 0; i < fontCount; i++) { - var font = fonts[i]; - - if (Fonts[font.name]) { - areFontsReady = areFontsReady && !Fonts[font.name].loading; - continue; - } - - new Font(font.name, font.file, font.properties); - - areFontsReady = false; - } - - var pageInterval; - - var delayLoadFont = function() { - for (var i = 0; i < fontCount; i++) { - if (Fonts[font.name].loading) { - return; - } - } - - clearInterval(pageInterval); - - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - - PDFViewer.drawPage(num); - } - - if (!areFontsReady) { - pageInterval = setInterval(delayLoadFont, 10); - return; - } - - page.display(gfx); - } - }, - - changeScale: function(num) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.scale = num / 100; - - var i; - - if (PDFViewer.pdf) { - for (i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - } - } - - for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { - var option = PDFViewer.scaleSelect.childNodes[i]; - - if (option.value == num) { - if (!option.selected) { - option.selected = 'selected'; - } - } else { - if (option.selected) { - option.removeAttribute('selected'); - } - } - } - - PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; - }, - - goToPage: function(num) { - if (1 <= num && num <= PDFViewer.numberOfPages) { - PDFViewer.pageNumber = num; - PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; - PDFViewer.willJumpToPage = true; - - document.location.hash = PDFViewer.pageNumber; - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } - }, - - goToPreviousPage: function() { - if (PDFViewer.pageNumber > 1) { - PDFViewer.goToPage(--PDFViewer.pageNumber); - } - }, - - goToNextPage: function() { - if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { - PDFViewer.goToPage(++PDFViewer.pageNumber); - } - }, - - openURL: function(url) { - PDFViewer.url = url; - document.title = url; - - var req = new XMLHttpRequest(); - req.open('GET', url); - req.mozResponseType = req.responseType = 'arraybuffer'; - req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; - - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status === req.expected) { - var data = req.mozResponseArrayBuffer || - req.mozResponse || - req.responseArrayBuffer || - req.response; - - 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' : ''; - } -}; - -window.onload = function() { - - // Parse the URL query parameters into a cached object. - PDFViewer.queryParams = function() { - var qs = window.location.search.substring(1); - var kvs = qs.split('&'); - var params = {}; - for (var i = 0; i < kvs.length; ++i) { - var kv = kvs[i].split('='); - params[unescape(kv[0])] = unescape(kv[1]); - } - - return params; - }(); - - PDFViewer.element = document.getElementById('viewer'); - - PDFViewer.pageNumberInput = document.getElementById('pageNumber'); - PDFViewer.pageNumberInput.onkeydown = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // Up arrow key. - if (charCode === 38) { - PDFViewer.goToNextPage(); - this.select(); - } - - // Down arrow key. - else if (charCode === 40) { - PDFViewer.goToPreviousPage(); - this.select(); - } - - // All other non-numeric keys (excluding Left arrow, Right arrow, - // Backspace, and Delete keys). - else if ((charCode < 48 || charCode > 57) && - charCode !== 8 && // Backspace - charCode !== 46 && // Delete - charCode !== 37 && // Left arrow - charCode !== 39 // Right arrow - ) { - return false; - } - - return true; - }; - PDFViewer.pageNumberInput.onkeyup = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // All numeric keys, Backspace, and Delete. - if ((charCode >= 48 && charCode <= 57) || - charCode === 8 || // Backspace - charCode === 46 // Delete - ) { - PDFViewer.goToPage(this.value); - } - - this.focus(); - }; - - PDFViewer.previousPageButton = document.getElementById('previousPageButton'); - PDFViewer.previousPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToPreviousPage(); - } - }; - PDFViewer.previousPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.previousPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.previousPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.nextPageButton = document.getElementById('nextPageButton'); - PDFViewer.nextPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToNextPage(); - } - }; - PDFViewer.nextPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.nextPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.nextPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.scaleSelect = document.getElementById('scaleSelect'); - 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.openURL(PDFViewer.queryParams.file || PDFViewer.url); - - window.onscroll = function(evt) { - var lastPagesDrawn = PDFViewer.lastPagesDrawn; - var visiblePages = PDFViewer.visiblePages(); - - var pagesToDraw = []; - var pagesToKeep = []; - var pagesToRemove = []; - - var i; - - // Determine which visible pages were not previously drawn. - for (i = 0; i < visiblePages.length; i++) { - if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { - pagesToDraw.push(visiblePages[i]); - PDFViewer.drawPage(visiblePages[i]); - } else { - pagesToKeep.push(visiblePages[i]); - } - } - - // Determine which previously drawn pages are no longer visible. - for (i = 0; i < lastPagesDrawn.length; i++) { - if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { - pagesToRemove.push(lastPagesDrawn[i]); - PDFViewer.removePage(lastPagesDrawn[i]); - } - } - - PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); - - // Update the page number input with the current page number. - if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { - PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } else { - PDFViewer.willJumpToPage = false; - } - }; -}; From 78ce1863bb4e91f5b66e9a54432116957249327e Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo Date: Thu, 23 Jun 2011 21:17:31 -0400 Subject: [PATCH 86/98] Fixed file renaming issues. --- multi-page-viewer.css => multi_page_viewer.css | 0 multi-page-viewer.html => multi_page_viewer.html | 0 multi-page-viewer.js => multi_page_viewer.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename multi-page-viewer.css => multi_page_viewer.css (100%) rename multi-page-viewer.html => multi_page_viewer.html (100%) rename multi-page-viewer.js => multi_page_viewer.js (100%) diff --git a/multi-page-viewer.css b/multi_page_viewer.css similarity index 100% rename from multi-page-viewer.css rename to multi_page_viewer.css diff --git a/multi-page-viewer.html b/multi_page_viewer.html similarity index 100% rename from multi-page-viewer.html rename to multi_page_viewer.html diff --git a/multi-page-viewer.js b/multi_page_viewer.js similarity index 100% rename from multi-page-viewer.js rename to multi_page_viewer.js From fa045139ad1e783d85719e3b91be92073fab6bcc Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo Date: Thu, 23 Jun 2011 21:18:07 -0400 Subject: [PATCH 87/98] Fixed vim indentation rules. --- multi_page_viewer.css | 238 +++++------ multi_page_viewer.html | 76 ++-- multi_page_viewer.js | 886 ++++++++++++++++++++--------------------- 3 files changed, 596 insertions(+), 604 deletions(-) diff --git a/multi_page_viewer.css b/multi_page_viewer.css index 7f4701022..b3eaab792 100644 --- a/multi_page_viewer.css +++ b/multi_page_viewer.css @@ -1,197 +1,197 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ body { - background-color: #929292; - font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; - margin: 0px; - padding: 0px; + background-color: #929292; + font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif; + margin: 0px; + padding: 0px; } canvas { - box-shadow: 0px 4px 10px #000; - -moz-box-shadow: 0px 4px 10px #000; - -webkit-box-shadow: 0px 4px 10px #000; + box-shadow: 0px 4px 10px #000; + -moz-box-shadow: 0px 4px 10px #000; + -webkit-box-shadow: 0px 4px 10px #000; } span { - font-size: 0.8em; + font-size: 0.8em; } .control { - display: inline-block; - float: left; - margin: 0px 20px 0px 0px; - padding: 0px 4px 0px 0px; + display: inline-block; + float: left; + margin: 0px 20px 0px 0px; + padding: 0px 4px 0px 0px; } .control > input { - float: left; - border: 1px solid #4d4d4d; - height: 20px; - padding: 0px; - margin: 0px 2px 0px 0px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + float: left; + border: 1px solid #4d4d4d; + height: 20px; + padding: 0px; + margin: 0px 2px 0px 0px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); } .control > select { - float: left; - border: 1px solid #4d4d4d; - height: 22px; - padding: 2px 0px 0px; - margin: 0px 0px 1px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + float: left; + border: 1px solid #4d4d4d; + height: 22px; + padding: 2px 0px 0px; + margin: 0px 0px 1px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); } .control > span { - cursor: default; - float: left; - height: 18px; - margin: 5px 2px 0px; - padding: 0px; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; + cursor: default; + float: left; + height: 18px; + margin: 5px 2px 0px; + padding: 0px; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; } .control .label { - clear: both; - float: left; - font-size: 0.65em; - margin: 2px 0px 0px; - position: relative; - text-align: center; - width: 100%; + clear: both; + float: left; + font-size: 0.65em; + margin: 2px 0px 0px; + position: relative; + text-align: center; + width: 100%; } .page { - width: 816px; - height: 1056px; - margin: 10px auto; + width: 816px; + height: 1056px; + margin: 10px auto; } #controls { - background-color: #eee; - border-bottom: 1px solid #666; - padding: 4px 0px 0px 8px; - position: fixed; - left: 0px; - top: 0px; - height: 40px; - width: 100%; - box-shadow: 0px 2px 8px #000; - -moz-box-shadow: 0px 2px 8px #000; - -webkit-box-shadow: 0px 2px 8px #000; + background-color: #eee; + border-bottom: 1px solid #666; + padding: 4px 0px 0px 8px; + position: fixed; + left: 0px; + top: 0px; + height: 40px; + width: 100%; + box-shadow: 0px 2px 8px #000; + -moz-box-shadow: 0px 2px 8px #000; + -webkit-box-shadow: 0px 2px 8px #000; } #controls input { - user-select: text; - -moz-user-select: text; - -webkit-user-select: text; + user-select: text; + -moz-user-select: text; + -webkit-user-select: text; } #previousPageButton { - background: url('images/buttons.png') no-repeat 0px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; + background: url('images/buttons.png') no-repeat 0px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; } #previousPageButton.down { - background: url('images/buttons.png') no-repeat 0px -46px; + background: url('images/buttons.png') no-repeat 0px -46px; } #previousPageButton.disabled { - background: url('images/buttons.png') no-repeat 0px 0px; + background: url('images/buttons.png') no-repeat 0px 0px; } #nextPageButton { - background: url('images/buttons.png') no-repeat -28px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; - width: 28px; - height: 23px; + background: url('images/buttons.png') no-repeat -28px -23px; + cursor: default; + display: inline-block; + float: left; + margin: 0px; + width: 28px; + height: 23px; } #nextPageButton.down { - background: url('images/buttons.png') no-repeat -28px -46px; + background: url('images/buttons.png') no-repeat -28px -46px; } #nextPageButton.disabled { - background: url('images/buttons.png') no-repeat -28px 0px; + 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; + 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; + background: url('images/buttons.png') no-repeat -56px -46px; } #openFileButton.disabled { - background: url('images/buttons.png') no-repeat -56px 0px; + background: url('images/buttons.png') no-repeat -56px 0px; } #fileInput { - display: none; + display: none; } #pageNumber { - text-align: right; + 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; + 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; + position: absolute; + overflow: hidden; + overflow-y: auto; + top: 40px; + right: 10px; + bottom: 10px; + left: 10px; } #sidebarContentView { - height: auto; - width: 100px; + height: auto; + width: 100px; } #viewer { - margin: 44px 0px 0px; - padding: 8px 0px; + margin: 44px 0px 0px; + padding: 8px 0px; } diff --git a/multi_page_viewer.html b/multi_page_viewer.html index ffbdfe707..47234686d 100644 --- a/multi_page_viewer.html +++ b/multi_page_viewer.html @@ -3,49 +3,49 @@ pdf.js Multi-Page Viewer - + - + -
- - - - Previous/Next - - - - / - -- - Page Number - - - - Zoom - - - - - Open File - +
+ + + + Previous/Next + + + + / + -- + Page Number + + + + Zoom + + + + + Open File + +
+ -
+
--> +
diff --git a/multi_page_viewer.js b/multi_page_viewer.js index baad7809e..3a02ea332 100644 --- a/multi_page_viewer.js +++ b/multi_page_viewer.js @@ -1,466 +1,458 @@ -/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / -/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* -*- 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 PDFViewer = { - queryParams: {}, - - element: null, - - previousPageButton: null, - nextPageButton: null, - pageNumberInput: null, - scaleSelect: null, - fileInput: null, - - willJumpToPage: false, - - pdf: null, - - url: 'compressed.tracemonkey-pldi-09.pdf', - pageNumber: 1, - numberOfPages: 1, - - scale: 1.0, - - pageWidth: function() { - return 816 * PDFViewer.scale; - }, - - pageHeight: function() { - return 1056 * PDFViewer.scale; - }, - - lastPagesDrawn: [], - - visiblePages: function() { - var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. - var windowTop = window.pageYOffset; - var windowBottom = window.pageYOffset + window.innerHeight; - var pageStartIndex = Math.floor(windowTop / pageHeight); - var pageStopIndex = Math.ceil(windowBottom / pageHeight); - - var pages = []; - - for (var i = pageStartIndex; i <= pageStopIndex; i++) { - pages.push(i + 1); - } - - return pages; - }, + queryParams: {}, - createPage: function(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'; - - PDFViewer.element.appendChild(anchor); - PDFViewer.element.appendChild(div); - }, - - removePage: function(num) { - var div = document.getElementById('pageContainer' + num); - - if (div) { - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - } - }, - - drawPage: function(num) { - if (!PDFViewer.pdf) { - return; - } - - var div = document.getElementById('pageContainer' + num); - var canvas = document.createElement('canvas'); - - if (div && !div.hasChildNodes()) { - div.appendChild(canvas); - - var page = PDFViewer.pdf.getPage(num); - - canvas.id = 'page' + num; - canvas.mozOpaque = true; - - // Canvas dimensions must be specified in CSS pixels. CSS pixels - // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. - canvas.width = PDFViewer.pageWidth(); - canvas.height = PDFViewer.pageHeight(); - - var ctx = canvas.getContext('2d'); - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var gfx = new CanvasGraphics(ctx); - var fonts = []; - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - page.compile(gfx, fonts); - - var areFontsReady = true; - - // Inspect fonts and translate the missing one - var fontCount = fonts.length; - - for (var i = 0; i < fontCount; i++) { - var font = fonts[i]; - - if (Fonts[font.name]) { - areFontsReady = areFontsReady && !Fonts[font.name].loading; - continue; - } - - new Font(font.name, font.file, font.properties); - - areFontsReady = false; - } - - var pageInterval; - - var delayLoadFont = function() { - for (var i = 0; i < fontCount; i++) { - if (Fonts[font.name].loading) { - return; - } - } - - clearInterval(pageInterval); - - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - - PDFViewer.drawPage(num); - } - - if (!areFontsReady) { - pageInterval = setInterval(delayLoadFont, 10); - return; - } - - page.display(gfx); - } - }, - - changeScale: function(num) { - while (PDFViewer.element.hasChildNodes()) { - PDFViewer.element.removeChild(PDFViewer.element.firstChild); - } - - PDFViewer.scale = num / 100; - - var i; - - if (PDFViewer.pdf) { - for (i = 1; i <= PDFViewer.numberOfPages; i++) { - PDFViewer.createPage(i); - } - - if (PDFViewer.numberOfPages > 0) { - PDFViewer.drawPage(1); - } - } - - for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { - var option = PDFViewer.scaleSelect.childNodes[i]; - - if (option.value == num) { - if (!option.selected) { - option.selected = 'selected'; - } - } else { - if (option.selected) { - option.removeAttribute('selected'); - } - } - } - - PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; - }, - - goToPage: function(num) { - if (1 <= num && num <= PDFViewer.numberOfPages) { - PDFViewer.pageNumber = num; - PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; - PDFViewer.willJumpToPage = true; - - document.location.hash = PDFViewer.pageNumber; - - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } - }, + element: null, - goToPreviousPage: function() { - if (PDFViewer.pageNumber > 1) { - PDFViewer.goToPage(--PDFViewer.pageNumber); - } - }, + previousPageButton: null, + nextPageButton: null, + pageNumberInput: null, + scaleSelect: null, + fileInput: null, - goToNextPage: function() { - if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { - PDFViewer.goToPage(++PDFViewer.pageNumber); - } - }, + willJumpToPage: false, - openURL: function(url) { - PDFViewer.url = url; - document.title = url; - - var req = new XMLHttpRequest(); - req.open('GET', url); - req.mozResponseType = req.responseType = 'arraybuffer'; - req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; - - req.onreadystatechange = function() { - if (req.readyState === 4 && req.status === req.expected) { - var data = req.mozResponseArrayBuffer || - req.mozResponse || - req.responseArrayBuffer || - req.response; - - 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(); + pdf: null, - 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' : ''; + url: 'compressed.tracemonkey-pldi-09.pdf', + pageNumber: 1, + numberOfPages: 1, + + scale: 1.0, + + pageWidth: function() { + return 816 * PDFViewer.scale; + }, + + pageHeight: function() { + return 1056 * PDFViewer.scale; + }, + + lastPagesDrawn: [], + + visiblePages: function() { + var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins. + var windowTop = window.pageYOffset; + var windowBottom = window.pageYOffset + window.innerHeight; + var pageStartIndex = Math.floor(windowTop / pageHeight); + var pageStopIndex = Math.ceil(windowBottom / pageHeight); + + var pages = []; + + for (var i = pageStartIndex; i <= pageStopIndex; i++) { + pages.push(i + 1); } + + return pages; + }, + + createPage: function(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'; + + PDFViewer.element.appendChild(anchor); + PDFViewer.element.appendChild(div); + }, + + removePage: function(num) { + var div = document.getElementById('pageContainer' + num); + + if (div) { + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + } + }, + + drawPage: function(num) { + if (!PDFViewer.pdf) { + return; + } + + var div = document.getElementById('pageContainer' + num); + var canvas = document.createElement('canvas'); + + if (div && !div.hasChildNodes()) { + div.appendChild(canvas); + + var page = PDFViewer.pdf.getPage(num); + + canvas.id = 'page' + num; + canvas.mozOpaque = true; + + // Canvas dimensions must be specified in CSS pixels. CSS pixels + // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. + canvas.width = PDFViewer.pageWidth(); + canvas.height = PDFViewer.pageHeight(); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + var gfx = new CanvasGraphics(ctx); + var fonts = []; + + // page.compile will collect all fonts for us, once we have loaded them + // we can trigger the actual page rendering with page.display + page.compile(gfx, fonts); + + var areFontsReady = true; + + // Inspect fonts and translate the missing one + var fontCount = fonts.length; + + for (var i = 0; i < fontCount; i++) { + var font = fonts[i]; + + if (Fonts[font.name]) { + areFontsReady = areFontsReady && !Fonts[font.name].loading; + continue; + } + + new Font(font.name, font.file, font.properties); + + areFontsReady = false; + } + + var pageInterval; + + var delayLoadFont = function() { + for (var i = 0; i < fontCount; i++) { + if (Fonts[font.name].loading) { + return; + } + } + + clearInterval(pageInterval); + + while (div.hasChildNodes()) { + div.removeChild(div.firstChild); + } + + PDFViewer.drawPage(num); + } + + if (!areFontsReady) { + pageInterval = setInterval(delayLoadFont, 10); + return; + } + + page.display(gfx); + } + }, + + changeScale: function(num) { + while (PDFViewer.element.hasChildNodes()) { + PDFViewer.element.removeChild(PDFViewer.element.firstChild); + } + + PDFViewer.scale = num / 100; + + var i; + + if (PDFViewer.pdf) { + for (i = 1; i <= PDFViewer.numberOfPages; i++) { + PDFViewer.createPage(i); + } + + if (PDFViewer.numberOfPages > 0) { + PDFViewer.drawPage(1); + } + } + + for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) { + var option = PDFViewer.scaleSelect.childNodes[i]; + + if (option.value == num) { + if (!option.selected) { + option.selected = 'selected'; + } + } else { + if (option.selected) { + option.removeAttribute('selected'); + } + } + } + + PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%'; + }, + + goToPage: function(num) { + if (1 <= num && num <= PDFViewer.numberOfPages) { + PDFViewer.pageNumber = num; + PDFViewer.pageNumberInput.value = PDFViewer.pageNumber; + PDFViewer.willJumpToPage = true; + + document.location.hash = PDFViewer.pageNumber; + + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + } + }, + + goToPreviousPage: function() { + if (PDFViewer.pageNumber > 1) { + PDFViewer.goToPage(--PDFViewer.pageNumber); + } + }, + + goToNextPage: function() { + if (PDFViewer.pageNumber < PDFViewer.numberOfPages) { + PDFViewer.goToPage(++PDFViewer.pageNumber); + } + }, + + openURL: function(url) { + PDFViewer.url = url; + document.title = url; + + var req = new XMLHttpRequest(); + req.open('GET', url); + req.mozResponseType = req.responseType = 'arraybuffer'; + req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200; + + req.onreadystatechange = function() { + if (req.readyState === 4 && req.status === req.expected) { + var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response; + + 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' : ''; + } }; window.onload = function() { - - // Parse the URL query parameters into a cached object. - PDFViewer.queryParams = function() { - var qs = window.location.search.substring(1); - var kvs = qs.split('&'); - var params = {}; - for (var i = 0; i < kvs.length; ++i) { - var kv = kvs[i].split('='); - params[unescape(kv[0])] = unescape(kv[1]); - } - - return params; - }(); - - PDFViewer.element = document.getElementById('viewer'); - - PDFViewer.pageNumberInput = document.getElementById('pageNumber'); - PDFViewer.pageNumberInput.onkeydown = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // Up arrow key. - if (charCode === 38) { - PDFViewer.goToNextPage(); - this.select(); - } - - // Down arrow key. - else if (charCode === 40) { - PDFViewer.goToPreviousPage(); - this.select(); - } - - // All other non-numeric keys (excluding Left arrow, Right arrow, - // Backspace, and Delete keys). - else if ((charCode < 48 || charCode > 57) && - charCode !== 8 && // Backspace - charCode !== 46 && // Delete - charCode !== 37 && // Left arrow - charCode !== 39 // Right arrow - ) { - return false; - } - - return true; - }; - PDFViewer.pageNumberInput.onkeyup = function(evt) { - var charCode = evt.charCode || evt.keyCode; - - // All numeric keys, Backspace, and Delete. - if ((charCode >= 48 && charCode <= 57) || - charCode === 8 || // Backspace - charCode === 46 // Delete - ) { - PDFViewer.goToPage(this.value); - } - - this.focus(); - }; - - PDFViewer.previousPageButton = document.getElementById('previousPageButton'); - PDFViewer.previousPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToPreviousPage(); - } - }; - PDFViewer.previousPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.previousPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.previousPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; + + // Parse the URL query parameters into a cached object. + PDFViewer.queryParams = function() { + var qs = window.location.search.substring(1); + var kvs = qs.split('&'); + var params = {}; - PDFViewer.nextPageButton = document.getElementById('nextPageButton'); - PDFViewer.nextPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToNextPage(); - } - }; - PDFViewer.nextPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } - }; - PDFViewer.nextPageButton.onmouseup = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - PDFViewer.nextPageButton.onmouseout = function(evt) { - this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; - }; - - PDFViewer.scaleSelect = document.getElementById('scaleSelect'); - 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'; + for (var i = 0; i < kvs.length; ++i) { + var kv = kvs[i].split('='); + params[unescape(kv[0])] = unescape(kv[1]); } - - PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; - PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0; - PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url); + return params; + }(); - window.onscroll = function(evt) { - var lastPagesDrawn = PDFViewer.lastPagesDrawn; - var visiblePages = PDFViewer.visiblePages(); - - var pagesToDraw = []; - var pagesToKeep = []; - var pagesToRemove = []; - - var i; - - // Determine which visible pages were not previously drawn. - for (i = 0; i < visiblePages.length; i++) { - if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { - pagesToDraw.push(visiblePages[i]); - PDFViewer.drawPage(visiblePages[i]); - } else { - pagesToKeep.push(visiblePages[i]); - } - } - - // Determine which previously drawn pages are no longer visible. - for (i = 0; i < lastPagesDrawn.length; i++) { - if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { - pagesToRemove.push(lastPagesDrawn[i]); - PDFViewer.removePage(lastPagesDrawn[i]); - } - } - - PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); - - // Update the page number input with the current page number. - if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { - PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; - PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? - 'disabled' : ''; - PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? - 'disabled' : ''; - } else { - PDFViewer.willJumpToPage = false; - } + PDFViewer.element = document.getElementById('viewer'); + + PDFViewer.pageNumberInput = document.getElementById('pageNumber'); + PDFViewer.pageNumberInput.onkeydown = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // Up arrow key. + if (charCode === 38) { + PDFViewer.goToNextPage(); + this.select(); + } + + // Down arrow key. + else if (charCode === 40) { + PDFViewer.goToPreviousPage(); + this.select(); + } + + // All other non-numeric keys (excluding Left arrow, Right arrow, + // Backspace, and Delete keys). + else if ((charCode < 48 || charCode > 57) && + charCode !== 8 && // Backspace + charCode !== 46 && // Delete + charCode !== 37 && // Left arrow + charCode !== 39 // Right arrow + ) { + return false; + } + + return true; + }; + PDFViewer.pageNumberInput.onkeyup = function(evt) { + var charCode = evt.charCode || evt.keyCode; + + // All numeric keys, Backspace, and Delete. + if ((charCode >= 48 && charCode <= 57) || + charCode === 8 || // Backspace + charCode === 46 // Delete + ) { + PDFViewer.goToPage(this.value); + } + + this.focus(); + }; + + PDFViewer.previousPageButton = document.getElementById('previousPageButton'); + PDFViewer.previousPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToPreviousPage(); + } + }; + PDFViewer.previousPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.previousPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.previousPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.nextPageButton = document.getElementById('nextPageButton'); + PDFViewer.nextPageButton.onclick = function(evt) { + if (this.className.indexOf('disabled') === -1) { + PDFViewer.goToNextPage(); + } + }; + PDFViewer.nextPageButton.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.nextPageButton.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.nextPageButton.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + PDFViewer.scaleSelect = document.getElementById('scaleSelect'); + 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.openURL(PDFViewer.queryParams.file || PDFViewer.url); + + window.onscroll = function(evt) { + var lastPagesDrawn = PDFViewer.lastPagesDrawn; + var visiblePages = PDFViewer.visiblePages(); + + var pagesToDraw = []; + var pagesToKeep = []; + var pagesToRemove = []; + + var i; + + // Determine which visible pages were not previously drawn. + for (i = 0; i < visiblePages.length; i++) { + if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) { + pagesToDraw.push(visiblePages[i]); + PDFViewer.drawPage(visiblePages[i]); + } else { + pagesToKeep.push(visiblePages[i]); + } + } + + // Determine which previously drawn pages are no longer visible. + for (i = 0; i < lastPagesDrawn.length; i++) { + if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) { + pagesToRemove.push(lastPagesDrawn[i]); + PDFViewer.removePage(lastPagesDrawn[i]); + } + } + + PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep); + + // Update the page number input with the current page number. + if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { + PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; + PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : ''; + PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + } else { + PDFViewer.willJumpToPage = false; + } + }; }; From 6501c2100f5a3a9ee9d1406de6331c793a1efbde Mon Sep 17 00:00:00 2001 From: Justin D'Arcangelo Date: Thu, 23 Jun 2011 21:37:40 -0400 Subject: [PATCH 88/98] Brought pdf.js back up to the latest revision. --- pdf.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pdf.js b/pdf.js index ffefc61a1..b2b6401fd 100644 --- a/pdf.js +++ b/pdf.js @@ -3275,8 +3275,11 @@ var CanvasGraphics = (function() { } } - if (bitsPerComponent !== 8) - error("Unsupported bpc"); + if (bitsPerComponent !== 8) { + TODO("Support bpc="+ bitsPerComponent); + this.restore(); + return; + } var xref = this.xref; var colorSpaces = this.colorSpaces; From 86f197dabac95eeeff51c60f3373d6ee5565a6ab Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 11:47:22 +0200 Subject: [PATCH 89/98] Start adding a FontLoader class to isolate the font-loaded hack --- fonts.js | 53 ++++++++++++++++++++++++++++++++++------------------- viewer.js | 35 +++++++---------------------------- 2 files changed, 41 insertions(+), 47 deletions(-) diff --git a/fonts.js b/fonts.js index a995c55eb..0a1974571 100644 --- a/fonts.js +++ b/fonts.js @@ -80,6 +80,35 @@ var Fonts = { } }; +var FontsLoader = { + bind: function(fonts) { + var worker = (typeof window == "undefined"); + var ready = true; + + for (var i = 0; i < fonts.length; i++) { + var font = fonts[i]; + if (Fonts[font.name]) { + ready = ready && !Fonts[font.name].loading; + continue; + } else { + ready = false; + } + + var obj = new Font(font.name, font.file, font.properties); + + var str = ""; + var data = Fonts[font.name].data; + var length = data.length; + for (var j = 0; j < length; j++) + str += String.fromCharCode(data[j]); + + worker ? obj.bindWorker(str) : obj.bindDOM(str); + } + return ready; + } +}; + + /** * 'Font' is the class the outside world should use, it encapsulate all the font * decoding logics whatever type it is (assuming the font type is supported). @@ -113,13 +142,14 @@ var Font = (function () { return; } + var data; switch (properties.type) { case "Type1": var cff = new CFF(name, file, properties); this.mimetype = "font/opentype"; // Wrap the CFF data inside an OTF font file - this.font = this.convert(name, cff, properties); + data = this.convert(name, cff, properties); break; case "TrueType": @@ -127,7 +157,7 @@ var Font = (function () { // Repair the TrueType file if it is can be damaged in the point of // view of the sanitizer - this.font = this.checkAndRepair(name, file, properties); + data = this.checkAndRepair(name, file, properties); break; default: @@ -135,28 +165,12 @@ var Font = (function () { break; } - var data = this.font; Fonts[name] = { data: data, properties: properties, loading: true, cache: Object.create(null) - } - - // Convert data to a string. - var dataStr = ""; - var length = data.length; - for (var i = 0; i < length; ++i) - dataStr += String.fromCharCode(data[i]); - - // Attach the font to the document. If this script is runnig in a worker, - // call `bindWorker`, which sends stuff over to the main thread. - if (typeof window != "undefined") { - this.bindDOM(dataStr); - } else { - this.bindWorker(dataStr); - } - + }; }; function stringToArray(str) { @@ -1420,6 +1434,7 @@ CFF.prototype = { i++; } error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")"); + return []; }, wrap: function wrap(name, charstrings, subrs, properties) { diff --git a/viewer.js b/viewer.js index 41aaf354c..2bcff50a6 100644 --- a/viewer.js +++ b/viewer.js @@ -3,7 +3,7 @@ "use strict"; -var pdfDocument, canvas, pageDisplay, pageNum, numPages, pageInterval; +var pdfDocument, canvas, pageDisplay, pageNum, numPages, pageTimeout; function load(userInput) { canvas = document.getElementById("canvas"); canvas.mozOpaque = true; @@ -52,7 +52,7 @@ function gotoPage(num) { } function displayPage(num) { - window.clearInterval(pageInterval); + window.clearTimeout(pageTimeout); document.getElementById("pageNumber").value = num; @@ -75,28 +75,12 @@ function displayPage(num) { page.compile(gfx, fonts); var t2 = Date.now(); - var fontsReady = true; - - // Inspect fonts and translate the missing one - var count = fonts.length; - for (var i = 0; i < count; i++) { - var font = fonts[i]; - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; + function loadFont() { + if (!FontsLoader.bind(fonts)) { + pageTimeout = window.setTimeout(loadFont, 10); + return; } - new Font(font.name, font.file, font.properties); - fontsReady = false; - } - - function delayLoadFont() { - for (var i = 0; i < count; i++) { - if (Fonts[font.name].loading) - return; - } - window.clearInterval(pageInterval); - var t3 = Date.now(); page.display(gfx); @@ -106,12 +90,7 @@ function displayPage(num) { var infoDisplay = document.getElementById("info"); infoDisplay.innerHTML = "Time to load/compile/fonts/render: "+ (t1 - t0) + "/" + (t2 - t1) + "/" + (t3 - t2) + "/" + (t4 - t3) + " ms"; }; - - if (fontsReady) { - delayLoadFont(); - } else { - pageInterval = setInterval(delayLoadFont, 10); - } + loadFont(); } function nextPage() { From 3955ed1b4c6a6ee0d0643c78ab6ee4f0c5d4b440 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 11:58:05 +0200 Subject: [PATCH 90/98] Fix a bunch of warnings from Firebug strict mode --- fonts.js | 27 +++++++++++++++------------ pdf.js | 56 ++++++++++++++++++++++++++++++-------------------------- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/fonts.js b/fonts.js index 0a1974571..7e8aecd6d 100644 --- a/fonts.js +++ b/fonts.js @@ -599,16 +599,16 @@ var Font = (function () { return font.getBytes(); }, - convert: function font_convert(name, font, properties) { + convert: function font_convert(fontName, font, properties) { var otf = new Uint8Array(kMaxFontFileSize); function createNameTable(name) { var names = [ "See original licence", // Copyright - name, // Font family + fontName, // Font family "undefined", // Font subfamily (font weight) "uniqueID", // Unique ID - name, // Full font name + fontName, // Full font name "0.1", // Version "undefined", // Postscript name "undefined", // Trademark @@ -616,7 +616,7 @@ var Font = (function () { "undefined" // Designer ]; - var name = + var nameTable = "\x00\x00" + // format "\x00\x0A" + // Number of names Record "\x00\x7E"; // Storage @@ -633,13 +633,13 @@ var Font = (function () { "\x00\x00" + // name ID string16(str.length) + string16(strOffset); - name += nameRecord; + nameTable += nameRecord; strOffset += str.length; } - name += names.join(""); - return name; + nameTable += names.join(""); + return nameTable; } // Required Tables @@ -885,6 +885,9 @@ var FontsUtils = { bytes.set([value >> 24, value >> 16, value >> 8, value]); return [bytes[0], bytes[1], bytes[2], bytes[3]]; } + + error("This number of bytes " + bytesCount + " is not supported"); + return null; }, bytesToInteger: function fu_bytesToInteger(bytesArray) { @@ -1238,7 +1241,7 @@ var CFF = function(name, file, properties) { }; CFF.prototype = { - createCFFIndexHeader: function(objects, isByte) { + createCFFIndexHeader: function cff_createCFFIndexHeader(objects, isByte) { // First 2 bytes contains the number of objects contained into this index var count = objects.length; @@ -1275,18 +1278,18 @@ CFF.prototype = { return data; }, - encodeNumber: function(value) { + encodeNumber: function cff_encodeNumber(value) { var x = 0; if (value >= -32768 && value <= 32767) { return [ 28, value >> 8, value & 0xFF ]; } else if (value >= (-2147483647-1) && value <= 2147483647) { return [ 0xFF, value >> 24, Value >> 16, value >> 8, value & 0xFF ]; - } else { - error("Value: " + value + " is not allowed"); } + error("Value: " + value + " is not allowed"); + return null; }, - getOrderedCharStrings: function(glyphs) { + getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs) { var charstrings = []; for (var i = 0; i < glyphs.length; i++) { diff --git a/pdf.js b/pdf.js index f61250a0c..c4ebb5f24 100644 --- a/pdf.js +++ b/pdf.js @@ -71,14 +71,14 @@ var Stream = (function() { get length() { return this.end - this.start; }, - getByte: function() { + getByte: function stream_getByte() { if (this.pos >= this.end) - return; + return null; return this.bytes[this.pos++]; }, // returns subarray of original buffer // should only be read - getBytes: function(length) { + getBytes: function stream_getBytes(length) { var bytes = this.bytes; var pos = this.pos; var strEnd = this.end; @@ -93,28 +93,28 @@ var Stream = (function() { this.pos = end; return bytes.subarray(pos, end); }, - lookChar: function() { + lookChar: function stream_lookChar() { if (this.pos >= this.end) - return; + return null; return String.fromCharCode(this.bytes[this.pos]); }, - getChar: function() { + getChar: function stream_getChar() { if (this.pos >= this.end) - return; + return null; return String.fromCharCode(this.bytes[this.pos++]); }, - skip: function(n) { + skip: function stream_skip(n) { if (!n) n = 1; this.pos += n; }, - reset: function() { + reset: function stream_reset() { this.pos = this.start; }, - moveStart: function() { + moveStart: function stream_moveStart() { this.start = this.pos; }, - makeSubStream: function(start, length, dict) { + makeSubStream: function stream_makeSubstream(start, length, dict) { return new Stream(this.bytes.buffer, start, length, dict); } }; @@ -146,7 +146,7 @@ var DecodeStream = (function() { } constructor.prototype = { - ensureBuffer: function(requested) { + ensureBuffer: function decodestream_ensureBuffer(requested) { var buffer = this.buffer; var current = buffer ? buffer.byteLength : 0; if (requested < current) @@ -159,16 +159,16 @@ var DecodeStream = (function() { buffer2[i] = buffer[i]; return this.buffer = buffer2; }, - getByte: function() { + getByte: function decodestream_getByte() { var pos = this.pos; while (this.bufferLength <= pos) { if (this.eof) - return; + return null; this.readBlock(); } return this.buffer[this.pos++]; }, - getBytes: function(length) { + getBytes: function decodestream_getBytes(length) { var pos = this.pos; if (length) { @@ -191,25 +191,25 @@ var DecodeStream = (function() { this.pos = end; return this.buffer.subarray(pos, end) }, - lookChar: function() { + lookChar: function decodestream_lookChar() { var pos = this.pos; while (this.bufferLength <= pos) { if (this.eof) - return; + return null; this.readBlock(); } return String.fromCharCode(this.buffer[this.pos]); }, - getChar: function() { + getChar: function decodestream_getChar() { var pos = this.pos; while (this.bufferLength <= pos) { if (this.eof) - return; + return null; this.readBlock(); } return String.fromCharCode(this.buffer[this.pos++]); }, - skip: function(n) { + skip: function decodestream_skip(n) { if (!n) n = 1; this.pos += n; @@ -635,6 +635,7 @@ var PredictorStream = (function() { var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3; DecodeStream.call(this); + return this; } constructor.prototype = Object.create(DecodeStream.prototype); @@ -905,7 +906,9 @@ var Dict = (function() { constructor.prototype = { get: function(key) { - return this.map[key]; + if (key in this.map) + return this.map[key]; + return null; }, get2: function(key1, key2) { return this.get(key1) || this.get(key2); @@ -1590,7 +1593,7 @@ var XRef = (function() { } constructor.prototype = { - readXRefTable: function(parser) { + readXRefTable: function readXRefTable(parser) { var obj; while (true) { if (IsCmd(obj = parser.getObj(), "trailer")) @@ -1661,7 +1664,7 @@ var XRef = (function() { return dict; }, - readXRefStream: function(stream) { + readXRefStream: function readXRefStream(stream) { var streamParameters = stream.parameters; var length = streamParameters.get("Length"); var byteWidths = streamParameters.get("W"); @@ -1713,7 +1716,7 @@ var XRef = (function() { this.readXRef(prev); return streamParameters; }, - readXRef: function(startXRef) { + readXRef: function readXref(startXRef) { var stream = this.stream; stream.pos = startXRef; var parser = new Parser(new Lexer(stream), true); @@ -1731,6 +1734,7 @@ var XRef = (function() { return this.readXRefStream(obj); } error("Invalid XRef"); + return null; }, getEntry: function(i) { var e = this.entries[i]; @@ -2396,7 +2400,7 @@ var CanvasGraphics = (function() { if (!fd) // XXX deprecated "special treatment" for standard // fonts? What do we need to do here? - return; + return null; var descriptor = xref.fetch(fd); var fontName = descriptor.get("FontName"); @@ -3037,7 +3041,7 @@ var CanvasGraphics = (function() { this.restore(); TODO("Inverse pattern is painted"); - var pattern = this.ctx.createPattern(tmpCanvas, "repeat"); + pattern = this.ctx.createPattern(tmpCanvas, "repeat"); this.ctx.fillStyle = pattern; }, setStrokeGray: function(gray) { From 6446b6d03a6138e5693516d7df1e295e51a25971 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 08:43:26 -0700 Subject: [PATCH 91/98] Run browsers in parallel. No limit on the number of concurrent browsers right now. --- test/test.py | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/test/test.py b/test/test.py index 53f65f78b..7e678bd90 100644 --- a/test/test.py +++ b/test/test.py @@ -1,4 +1,4 @@ -import json, platform, os, shutil, sys, subprocess, tempfile, threading, urllib, urllib2 +import json, platform, os, shutil, sys, subprocess, tempfile, threading, time, urllib, urllib2 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import SocketServer from optparse import OptionParser @@ -138,6 +138,7 @@ class BrowserCommand(): def __init__(self, browserRecord): self.name = browserRecord["name"] self.path = browserRecord["path"] + self.tempDir = None if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")): self._fixupMacPath() @@ -151,19 +152,19 @@ class BrowserCommand(): def setup(self): self.tempDir = tempfile.mkdtemp() self.profileDir = os.path.join(self.tempDir, "profile") - print self.profileDir shutil.copytree(os.path.join(DOC_ROOT, "test", "resources", "firefox"), self.profileDir) def teardown(self): - shutil.rmtree(self.tempDir) + if self.tempDir is not None and os.path.exists(self.tempDir): + shutil.rmtree(self.tempDir) def start(self, url): cmds = [self.path] if platform.system() == "Darwin": cmds.append("-foreground") cmds.extend(["-no-remote", "-profile", self.profileDir, url]) - subprocess.call(cmds) + subprocess.Popen(cmds) def makeBrowserCommands(browserManifestFile): with open(browserManifestFile) as bmf: @@ -223,15 +224,22 @@ def setUp(options): State.remaining = len(testBrowsers) * len(manifestList) - for b in testBrowsers: - try: - b.setup() - print 'Launching', b.name - qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) - b.start('http://localhost:8080/test/test_slave.html?'+ qs) - finally: - b.teardown() + return testBrowsers +def startBrowsers(browsers, options): + for b in browsers: + b.setup() + print 'Launching', b.name + qs = 'browser='+ urllib.quote(b.name) +'&manifestFile='+ urllib.quote(options.manifestFile) + b.start('http://localhost:8080/test/test_slave.html?'+ qs) + +def teardownBrowsers(browsers): + for b in browsers: + try: + b.teardown() + except: + print "Error cleaning up after browser at ", b.path + def check(task, results, browser): failed = False for r in xrange(len(results)): @@ -385,8 +393,14 @@ def main(): httpd_thread.setDaemon(True) httpd_thread.start() - setUp(options) - processResults() + browsers = setUp(options) + try: + startBrowsers(browsers, options) + while not State.done: + time.sleep(1) + processResults() + finally: + teardownBrowsers(browsers) if __name__ == '__main__': main() From a67a4c0677abfd191de0330e18507f834315e7b3 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 09:00:13 -0700 Subject: [PATCH 92/98] Add a line to prevent TestPilot from installing. --- test/resources/firefox/user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/resources/firefox/user.js b/test/resources/firefox/user.js index d4b9d4130..7ca293923 100644 --- a/test/resources/firefox/user.js +++ b/test/resources/firefox/user.js @@ -32,3 +32,4 @@ user_pref("app.update.enabled", false); user_pref("browser.panorama.experienced_first_run", true); // Assume experienced user_pref("dom.w3c_touch_events.enabled", true); user_pref("extensions.checkCompatibility", false); +user_pref("extensions.installDistroAddons", false); // prevent testpilot etc From 2b6b6d5ab23de0a99a54d824b0ab4dc4c7fe5af9 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 09:45:41 -0700 Subject: [PATCH 93/98] Try harder to clean up after the browsers. --- test/test.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/test/test.py b/test/test.py index 7e678bd90..bc30d5f8a 100644 --- a/test/test.py +++ b/test/test.py @@ -139,6 +139,7 @@ class BrowserCommand(): self.name = browserRecord["name"] self.path = browserRecord["path"] self.tempDir = None + self.process = None if platform.system() == "Darwin" and (self.path.endswith(".app") or self.path.endswith(".app/")): self._fixupMacPath() @@ -156,6 +157,17 @@ class BrowserCommand(): self.profileDir) def teardown(self): + # If the browser is still running, wait up to ten seconds for it to quit + if self.process and self.process.poll() is None: + checks = 0 + while self.process.poll() is None and checks < 20: + checks += 1 + time.sleep(.5) + # If it's still not dead, try to kill it + if self.process.poll() is None: + print "Process %s is still running. Killing." % self.name + self.process.kill() + if self.tempDir is not None and os.path.exists(self.tempDir): shutil.rmtree(self.tempDir) @@ -164,7 +176,7 @@ class BrowserCommand(): if platform.system() == "Darwin": cmds.append("-foreground") cmds.extend(["-no-remote", "-profile", self.profileDir, url]) - subprocess.Popen(cmds) + self.process = subprocess.Popen(cmds) def makeBrowserCommands(browserManifestFile): with open(browserManifestFile) as bmf: @@ -239,7 +251,9 @@ def teardownBrowsers(browsers): b.teardown() except: print "Error cleaning up after browser at ", b.path - + print "Temp dir was ", b.tempDir + print "Error:", sys.exc_info()[0] + def check(task, results, browser): failed = False for r in xrange(len(results)): From 3d85caa212cf8b86379d94932abca2618ee5b007 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 21:25:08 +0200 Subject: [PATCH 94/98] Do not add the font-loader canvas to the page dom to save some load time --- fonts.js | 58 +++++++++----------------------------------------------- 1 file changed, 9 insertions(+), 49 deletions(-) diff --git a/fonts.js b/fonts.js index 7e8aecd6d..c817b12e5 100644 --- a/fonts.js +++ b/fonts.js @@ -104,6 +104,7 @@ var FontsLoader = { worker ? obj.bindWorker(str) : obj.bindDOM(str); } + return ready; } }; @@ -766,96 +767,55 @@ var Font = (function () { return fontData; }, - bindWorker: function font_bind_worker(dataStr) { + bindWorker: function font_bindWorker(data) { postMessage({ action: "font", data: { - raw: dataStr, + raw: data, fontName: this.name, mimetype: this.mimetype } }); }, - bindDOM: function font_bind_dom(dataStr) { + bindDOM: function font_bindDom(data) { var fontName = this.name; /** Hack begin */ // Actually there is not event when a font has finished downloading so // the following code are a dirty hack to 'guess' when a font is ready + // This code could go away when bug 471915 has landed var canvas = document.createElement("canvas"); - var style = "border: 1px solid black; position:absolute; top: " + - (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px"; - canvas.setAttribute("style", style); - canvas.setAttribute("width", 340); - canvas.setAttribute("heigth", 100); - document.body.appendChild(canvas); - - // Get the font size canvas think it will be for 'spaces' var ctx = canvas.getContext("2d"); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + ctx.font = "bold italic 20px " + fontName + ", Symbol"; var testString = " "; - // When debugging use the characters provided by the charsets to visually - // see what's happening instead of 'spaces' - var debug = false; - if (debug) { - var name = document.createElement("font"); - name.setAttribute("style", "position: absolute; left: 20px; top: " + - (100 * fontCount + 60) + "px"); - name.innerHTML = fontName; - document.body.appendChild(name); - - // Retrieve font charset - var charset = Fonts[fontName].properties.charset || []; - - // if the charset is too small make it repeat a few times - var count = 30; - while (count-- && charset.length <= 30) - charset = charset.concat(charset.slice()); - - for (var i = 0; i < charset.length; i++) { - var unicode = GlyphsUnicode[charset[i]]; - if (!unicode) - continue; - testString += String.fromCharCode(unicode); - } - - ctx.fillText(testString, 20, 20); - } - // Periodicaly check for the width of the testString, it will be // different once the real font has loaded var textWidth = ctx.measureText(testString).width; var interval = window.setInterval(function canvasInterval(self) { this.start = this.start || Date.now(); - ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; + ctx.font = "bold italic 20px " + fontName + ", Symbol"; // For some reasons the font has not loaded, so mark it loaded for the // page to proceed but cry if ((Date.now() - this.start) >= kMaxWaitForFontFace) { window.clearInterval(interval); Fonts[fontName].loading = false; - warn("Is " + fontName + " for charset: " + charset + " loaded?"); + warn("Is " + fontName + " loaded?"); this.start = 0; } else if (textWidth != ctx.measureText(testString).width) { window.clearInterval(interval); Fonts[fontName].loading = false; this.start = 0; } - - if (debug) - ctx.fillText(testString, 20, 50); }, 30, this); /** Hack end */ - - // Convert the data string and add it to the page. - var base64 = window.btoa(dataStr); // Add the @font-face rule to the document - var url = "url(data:" + this.mimetype + ";base64," + base64 + ");"; + var url = "url(data:" + this.mimetype + ";base64," + window.btoa(data) + ");"; var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}"; var styleSheet = document.styleSheets[0]; styleSheet.insertRule(rule, styleSheet.length); From 83fabc49c2dac7f2dac16bdd26a759a8575f0183 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Fri, 24 Jun 2011 21:46:48 +0200 Subject: [PATCH 95/98] Update the multi_page_viewer.js code to work with the new FontLoader API --- multi_page_viewer.js | 73 ++++++++++++-------------------------------- 1 file changed, 19 insertions(+), 54 deletions(-) diff --git a/multi_page_viewer.js b/multi_page_viewer.js index 3a02ea332..1631433ca 100644 --- a/multi_page_viewer.js +++ b/multi_page_viewer.js @@ -3,6 +3,8 @@ "use strict"; +var pageTimeout; + var PDFViewer = { queryParams: {}, @@ -75,81 +77,45 @@ var PDFViewer = { }, drawPage: function(num) { - if (!PDFViewer.pdf) { + if (!PDFViewer.pdf) return; - } - + var div = document.getElementById('pageContainer' + num); var canvas = document.createElement('canvas'); - + if (div && !div.hasChildNodes()) { - div.appendChild(canvas); - var page = PDFViewer.pdf.getPage(num); - + canvas.id = 'page' + num; canvas.mozOpaque = true; - + // Canvas dimensions must be specified in CSS pixels. CSS pixels // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi. canvas.width = PDFViewer.pageWidth(); canvas.height = PDFViewer.pageHeight(); - + div.appendChild(canvas); + var ctx = canvas.getContext('2d'); ctx.save(); ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); - + var gfx = new CanvasGraphics(ctx); - var fonts = []; - + // page.compile will collect all fonts for us, once we have loaded them // we can trigger the actual page rendering with page.display + var fonts = []; page.compile(gfx, fonts); - - var areFontsReady = true; - - // Inspect fonts and translate the missing one - var fontCount = fonts.length; - - for (var i = 0; i < fontCount; i++) { - var font = fonts[i]; - - if (Fonts[font.name]) { - areFontsReady = areFontsReady && !Fonts[font.name].loading; - continue; + + var loadFont = function() { + if (!FontsLoader.bind(fonts)) { + pageTimeout = window.setTimeout(loadFont, 10); + return; } - - new Font(font.name, font.file, font.properties); - - areFontsReady = false; + page.display(gfx); } - - var pageInterval; - - var delayLoadFont = function() { - for (var i = 0; i < fontCount; i++) { - if (Fonts[font.name].loading) { - return; - } - } - - clearInterval(pageInterval); - - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - - PDFViewer.drawPage(num); - } - - if (!areFontsReady) { - pageInterval = setInterval(delayLoadFont, 10); - return; - } - - page.display(gfx); + loadFont(); } }, @@ -258,7 +224,6 @@ var PDFViewer = { }; window.onload = function() { - // Parse the URL query parameters into a cached object. PDFViewer.queryParams = function() { var qs = window.location.search.substring(1); From f317eae084fa8e77e808105454432aa26966d597 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Fri, 24 Jun 2011 17:12:06 -0400 Subject: [PATCH 96/98] nits --- fonts.js | 6 +++--- multi_page_viewer.js | 2 +- viewer.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fonts.js b/fonts.js index c817b12e5..ac06b76ae 100644 --- a/fonts.js +++ b/fonts.js @@ -80,7 +80,7 @@ var Fonts = { } }; -var FontsLoader = { +var FontLoader = { bind: function(fonts) { var worker = (typeof window == "undefined"); var ready = true; @@ -90,10 +90,10 @@ var FontsLoader = { if (Fonts[font.name]) { ready = ready && !Fonts[font.name].loading; continue; - } else { - ready = false; } + ready = false; + var obj = new Font(font.name, font.file, font.properties); var str = ""; diff --git a/multi_page_viewer.js b/multi_page_viewer.js index 1631433ca..f262734d3 100644 --- a/multi_page_viewer.js +++ b/multi_page_viewer.js @@ -109,7 +109,7 @@ var PDFViewer = { page.compile(gfx, fonts); var loadFont = function() { - if (!FontsLoader.bind(fonts)) { + if (!FontLoader.bind(fonts)) { pageTimeout = window.setTimeout(loadFont, 10); return; } diff --git a/viewer.js b/viewer.js index 2bcff50a6..c7be739bc 100644 --- a/viewer.js +++ b/viewer.js @@ -76,7 +76,7 @@ function displayPage(num) { var t2 = Date.now(); function loadFont() { - if (!FontsLoader.bind(fonts)) { + if (!FontLoader.bind(fonts)) { pageTimeout = window.setTimeout(loadFont, 10); return; } From 9748d2ad4978c9fa69f24aea4c994415d51fb2c5 Mon Sep 17 00:00:00 2001 From: Rob Sayre Date: Fri, 24 Jun 2011 16:14:33 -0700 Subject: [PATCH 97/98] Concurrent browsers sending snapshots asynchronously. --- test/test.py | 19 +++++++++++++---- test/test_slave.html | 50 +++++++++++++++++++++++++++++--------------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/test/test.py b/test/test.py index bc30d5f8a..a89aa0a7b 100644 --- a/test/test.py +++ b/test/test.py @@ -69,9 +69,10 @@ class State: eqLog = None class Result: - def __init__(self, snapshot, failure): + def __init__(self, snapshot, failure, page): self.snapshot = snapshot self.failure = failure + self.page = page class TestServer(SocketServer.TCPServer): allow_reuse_address = True @@ -122,10 +123,20 @@ class PDFTestHandler(BaseHTTPRequestHandler): result = json.loads(self.rfile.read(numBytes)) browser, id, failure, round, page, snapshot = result['browser'], result['id'], result['failure'], result['round'], result['page'], result['snapshot'] taskResults = State.taskResults[browser][id] - taskResults[round].append(Result(snapshot, failure)) - assert len(taskResults[round]) == page + taskResults[round].append(Result(snapshot, failure, page)) - if result['taskDone']: + def isTaskDone(): + numPages = result["numPages"] + rounds = State.manifest[id]["rounds"] + for round in range(0,rounds): + if len(taskResults[round]) < numPages: + return False + return True + + if isTaskDone(): + # sort the results since they sometimes come in out of order + for results in taskResults: + results.sort(key=lambda result: result.page) check(State.manifest[id], taskResults, browser) # Please oh please GC this ... del State.taskResults[browser][id] diff --git a/test/test_slave.html b/test/test_slave.html index 1053025e1..03982bbb5 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -139,31 +139,41 @@ function snapshotCurrentPage(gfx) { } } - currentTask.taskDone = (currentTask.pageNum == pdfDoc.numPages - && (1 + currentTask.round) == currentTask.rounds); sendTaskResult(canvas.toDataURL("image/png")); log("done"+ (failure ? " (failed!)" : "") +"\n"); - ++currentTask.pageNum, nextPage(); -} - -function done() { - log("Done!\n"); + // Set up the next request + backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0; setTimeout(function() { - document.body.innerHTML = "Tests are finished.

CLOSE ME!

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

CLOSE ME!

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

+  

+  

Inflight requests:

From b90869e97f644a7f9fb59ff177eddd87847ca4c2 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Fri, 24 Jun 2011 17:11:39 -0700 Subject: [PATCH 98/98] update test_slave for the new font-loading API --- test/test_slave.html | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/test/test_slave.html b/test/test_slave.html index 03982bbb5..08a3804f3 100644 --- a/test/test_slave.html +++ b/test/test_slave.html @@ -88,43 +88,29 @@ function nextPage() { clear(ctx); var fonts = []; - var fontsReady = true; var gfx = new CanvasGraphics(ctx); try { currentPage = pdfDoc.getPage(currentTask.pageNum); currentPage.compile(gfx, fonts); - - // Inspect fonts and translate the missing ones - var count = fonts.length; - for (var i = 0; i < count; ++i) { - var font = fonts[i]; - if (Fonts[font.name]) { - fontsReady = fontsReady && !Fonts[font.name].loading; - continue; - } - new Font(font.name, font.file, font.properties); - fontsReady = false; - } } catch(e) { failure = 'compile: '+ e.toString(); } - var checkFontsLoadedIntervalTimer = null; + var fontLoaderTimer = null; function checkFontsLoaded() { - for (var i = 0; i < count; i++) { - if (Fonts[font.name].loading) { - return; - } + if (!FontLoader.bind(fonts)) { + fontLoaderTimer = window.setTimeout(checkFontsLoaded, 10); + return; } - window.clearInterval(checkFontsLoadedIntervalTimer); - snapshotCurrentPage(gfx); } - if (failure || fontsReady) { + if (failure) { + // Skip font loading if there was a failure, since the fonts might + // be in an inconsistent state. snapshotCurrentPage(gfx); } else { - checkFontsLoadedIntervalTimer = setInterval(checkFontsLoaded, 10); + checkFontsLoaded(); } }