diff --git a/images/buttons.png b/images/buttons.png
index 682212660..3357b47d6 100644
Binary files a/images/buttons.png and b/images/buttons.png differ
diff --git a/images/combobox.png b/images/combobox.png
deleted file mode 100644
index f1527d6a2..000000000
Binary files a/images/combobox.png and /dev/null differ
diff --git a/images/source/ComboBox.psd.zip b/images/source/ComboBox.psd.zip
deleted file mode 100644
index 232604c75..000000000
Binary files a/images/source/ComboBox.psd.zip and /dev/null differ
diff --git a/images/source/FileButton.psd.zip b/images/source/FileButton.psd.zip
new file mode 100644
index 000000000..1f2b51cee
Binary files /dev/null and b/images/source/FileButton.psd.zip differ
diff --git a/multi-page-viewer.css b/multi-page-viewer.css
index c96567465..7f4701022 100644
--- a/multi-page-viewer.css
+++ b/multi-page-viewer.css
@@ -27,7 +27,30 @@ span {
.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 {
@@ -61,7 +84,7 @@ span {
background-color: #eee;
border-bottom: 1px solid #666;
padding: 4px 0px 0px 8px;
- position:fixed;
+ position: fixed;
left: 0px;
top: 0px;
height: 40px;
@@ -79,7 +102,7 @@ span {
#previousPageButton {
background: url('images/buttons.png') no-repeat 0px -23px;
- cursor: pointer;
+ cursor: default;
display: inline-block;
float: left;
margin: 0px;
@@ -97,7 +120,7 @@ span {
#nextPageButton {
background: url('images/buttons.png') no-repeat -28px -23px;
- cursor: pointer;
+ cursor: default;
display: inline-block;
float: left;
margin: 0px;
@@ -113,71 +136,61 @@ span {
background: url('images/buttons.png') no-repeat -28px 0px;
}
-#scaleComboBoxInput {
- background: url('images/combobox.png') no-repeat 0px -23px;
+#openFileButton {
+ background: url('images/buttons.png') no-repeat -56px -23px;
+ cursor: default;
display: inline-block;
float: left;
- margin: 0px;
- width: 35px;
+ margin: 0px 0px 0px 3px;
+ width: 29px;
height: 23px;
}
-#scaleComboBoxInput input {
- background: none;
- border: 0px;
- margin: 3px 2px 0px;
- width: 31px;
+#openFileButton.down {
+ background: url('images/buttons.png') no-repeat -56px -46px;
}
-#scaleComboBoxButton {
- background: url('images/combobox.png') no-repeat -41px -23px;
- cursor: pointer;
- display: inline-block;
- float: left;
- margin: 0px;
- width: 21px;
- height: 23px;
+#openFileButton.disabled {
+ background: url('images/buttons.png') no-repeat -56px 0px;
}
-#scaleComboBoxButton.down {
- background: url('images/combobox.png') no-repeat -41px -46px;
-}
-
-#scaleComboBoxButton.disabled {
- background: url('images/combobox.png') no-repeat -41px 0px;
-}
-
-#scaleComboBoxList {
- background-color: #fff;
- border: 1px solid #666;
- clear: both;
- position: relative;
+#fileInput {
display: none;
- top: -20px;
- width: 48px;
}
-#scaleComboBoxList > ul {
- list-style: none;
- padding: 0px;
- margin: 0px;
-}
-
-#scaleComboBoxList > ul > li {
- display: inline-block;
- cursor: pointer;
- width: 100%;
-}
-
-#scaleComboBoxList > ul > li:hover {
- background-color: #09f;
- color: #fff;
-}
-
-#pageNumber, #scale {
+#pageNumber {
text-align: right;
}
+#sidebar {
+ background-color: rgba(0, 0, 0, 0.8);
+ position: fixed;
+ width: 150px;
+ top: 62px;
+ bottom: 18px;
+ border-top-right-radius: 8px;
+ border-bottom-right-radius: 8px;
+ -moz-border-radius-topright: 8px;
+ -moz-border-radius-bottomright: 8px;
+ -webkit-border-top-right-radius: 8px;
+ -webkit-border-bottom-right-radius: 8px;
+}
+
+#sidebarScrollView {
+ position: absolute;
+ overflow: hidden;
+ overflow-y: auto;
+ top: 40px;
+ right: 10px;
+ bottom: 10px;
+ left: 10px;
+}
+
+#sidebarContentView {
+ height: auto;
+ width: 100px;
+}
+
#viewer {
margin: 44px 0px 0px;
padding: 8px 0px;
diff --git a/multi-page-viewer.html b/multi-page-viewer.html
index 692cfb1c4..ffbdfe707 100644
--- a/multi-page-viewer.html
+++ b/multi-page-viewer.html
@@ -2,7 +2,8 @@
pdf.js Multi-Page Viewer
-
+
+
@@ -11,7 +12,8 @@
+
diff --git a/multi-page-viewer.js b/multi-page-viewer.js
index 6cb46a08a..baad7809e 100644
--- a/multi-page-viewer.js
+++ b/multi-page-viewer.js
@@ -11,7 +11,8 @@ var PDFViewer = {
previousPageButton: null,
nextPageButton: null,
pageNumberInput: null,
- scaleInput: null,
+ scaleSelect: null,
+ fileInput: null,
willJumpToPage: false,
@@ -66,92 +67,103 @@ var PDFViewer = {
removePage: function(num) {
var div = document.getElementById('pageContainer' + num);
- if (div && div.hasChildNodes()) {
- while (div.childNodes.length > 0) {
+ if (div) {
+ while (div.hasChildNodes()) {
div.removeChild(div.firstChild);
}
}
},
drawPage: function(num) {
- if (PDFViewer.pdf) {
- var page = PDFViewer.pdf.getPage(num);
- var div = document.getElementById('pageContainer' + num);
-
- if (div && !div.hasChildNodes()) {
- var canvas = document.createElement('canvas');
- canvas.id = 'page' + num;
- canvas.mozOpaque = true;
-
- // Canvas dimensions must be specified in CSS pixels. CSS pixels
- // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
- canvas.width = PDFViewer.pageWidth();
- canvas.height = PDFViewer.pageHeight();
-
- 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 = [];
+ if (!PDFViewer.pdf) {
+ return;
+ }
- // 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 fontsReady = 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]) {
- fontsReady = fontsReady && !Fonts[font.name].loading;
- continue;
- }
+ var div = document.getElementById('pageContainer' + num);
+ var canvas = document.createElement('canvas');
+
+ if (div && !div.hasChildNodes()) {
+ div.appendChild(canvas);
+
+ var page = PDFViewer.pdf.getPage(num);
- new Font(font.name, font.file, font.properties);
-
- fontsReady = false;
+ 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;
}
- var pageInterval;
- var delayLoadFont = function() {
- for (var i = 0; i < fontCount; i++) {
- if (Fonts[font.name].loading) {
- return;
- }
- }
-
- clearInterval(pageInterval);
-
- PDFViewer.drawPage(num);
- }
-
- if (!fontsReady) {
- pageInterval = setInterval(delayLoadFont, 10);
- return;
- }
-
- page.display(gfx);
- div.appendChild(canvas);
+ 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.childNodes.length > 0) {
+ while (PDFViewer.element.hasChildNodes()) {
PDFViewer.element.removeChild(PDFViewer.element.firstChild);
}
PDFViewer.scale = num / 100;
+ var i;
+
if (PDFViewer.pdf) {
- for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
+ for (i = 1; i <= PDFViewer.numberOfPages; i++) {
PDFViewer.createPage(i);
}
@@ -160,7 +172,21 @@ var PDFViewer = {
}
}
- PDFViewer.scaleInput.value = Math.floor(PDFViewer.scale * 100) + '%';
+ 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) {
@@ -190,7 +216,7 @@ var PDFViewer = {
}
},
- open: function(url) {
+ openURL: function(url) {
PDFViewer.url = url;
document.title = url;
@@ -206,21 +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.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' : '';
}
};
@@ -320,41 +360,67 @@ window.onload = function() {
this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
};
- PDFViewer.scaleInput = document.getElementById('scale');
- PDFViewer.scaleInput.buttonElement = document.getElementById('scaleComboBoxButton');
- PDFViewer.scaleInput.buttonElement.listElement = document.getElementById('scaleComboBoxList');
- PDFViewer.scaleInput.onchange = function(evt) {
+ PDFViewer.scaleSelect = document.getElementById('scaleSelect');
+ PDFViewer.scaleSelect.onchange = function(evt) {
PDFViewer.changeScale(parseInt(this.value));
};
-
- PDFViewer.scaleInput.buttonElement.onclick = function(evt) {
- this.listElement.style.display = (this.listElement.style.display === 'block') ? 'none' : 'block';
- };
- PDFViewer.scaleInput.buttonElement.onmousedown = function(evt) {
- if (this.className.indexOf('disabled') === -1) {
- this.className = 'down';
- }
- };
- PDFViewer.scaleInput.buttonElement.onmouseup = function(evt) {
- this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
- };
- PDFViewer.scaleInput.buttonElement.onmouseout = function(evt) {
- this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
- };
- var listItems = PDFViewer.scaleInput.buttonElement.listElement.getElementsByTagName('LI');
-
- for (var i = 0; i < listItems.length; i++) {
- var listItem = listItems[i];
- listItem.onclick = function(evt) {
- PDFViewer.changeScale(parseInt(this.innerHTML));
- PDFViewer.scaleInput.buttonElement.listElement.style.display = 'none';
+ 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.scaleInput.value) / 100 || 1.0;
- PDFViewer.open(PDFViewer.queryParams.file || PDFViewer.url);
+ PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
+
+ PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
window.onscroll = function(evt) {
var lastPagesDrawn = PDFViewer.lastPagesDrawn;
diff --git a/pdf.js b/pdf.js
index 23dd5bee9..96042d6c9 100644
--- a/pdf.js
+++ b/pdf.js
@@ -61,49 +61,50 @@ 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;
}
+ // required methods for a stream. if a particular stream does not
+ // implement these, an error should be thrown
constructor.prototype = {
get length() {
return this.end - this.start;
},
getByte: function() {
- var bytes = this.bytes;
if (this.pos >= this.end)
- return -1;
- return bytes[this.pos++];
+ return;
+ return this.bytes[this.pos++];
},
// returns subarray of original buffer
// should only be read
getBytes: function(length) {
var bytes = this.bytes;
var pos = this.pos;
+ var strEnd = this.end;
+
+ if (!length)
+ return bytes.subarray(pos, strEnd);
var end = pos + length;
- var strEnd = this.end;
- if (!end || end > strEnd)
+ if (end > strEnd)
end = strEnd;
this.pos = end;
return bytes.subarray(pos, end);
},
lookChar: function() {
- var bytes = this.bytes;
if (this.pos >= this.end)
return;
- return String.fromCharCode(bytes[this.pos]);
+ return String.fromCharCode(this.bytes[this.pos]);
},
getChar: function() {
- var ch = this.lookChar();
- if (!ch)
- return ch;
- this.pos++;
- return ch;
+ if (this.pos >= this.end)
+ return;
+ return String.fromCharCode(this.bytes[this.pos++]);
},
skip: function(n) {
- if (!n && !IsNum(n))
+ if (!n)
n = 1;
this.pos += n;
},
@@ -135,6 +136,91 @@ 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;
+
+ 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 {
+ 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;
+ }
+ };
+
+ return constructor;
+})();
+
+
+
var FlateStream = (function() {
const codeLenCodeMap = Uint32Array([
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
@@ -259,269 +345,393 @@ var FlateStream = (function() {
this.bytes = bytes;
this.bytesPos = bytesPos;
- this.eof = false;
+
this.codeSize = 0;
this.codeBuf = 0;
-
- this.pos = 0;
- this.bufferLength = 0;
+
+ DecodeStream.call(this);
}
- constructor.prototype = {
- getBits: function(bits) {
- var codeSize = this.codeSize;
- var codeBuf = this.codeBuf;
- var bytes = this.bytes;
- var bytesPos = this.bytesPos;
+ constructor.prototype = Object.create(DecodeStream.prototype);
- var b;
- while (codeSize < bits) {
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad encoding in flate stream");
- codeBuf |= b << codeSize;
- codeSize += 8;
- }
- b = codeBuf & ((1 << bits) - 1);
- this.codeBuf = codeBuf >> bits;
- this.codeSize = codeSize -= bits;
- this.bytesPos = bytesPos;
- return b;
- },
- getCode: function(table) {
- var codes = table[0];
- var maxLen = table[1];
- var codeSize = this.codeSize;
- var codeBuf = this.codeBuf;
- var bytes = this.bytes;
- var bytesPos = this.bytesPos;
+ constructor.prototype.getBits = function(bits) {
+ var codeSize = this.codeSize;
+ var codeBuf = this.codeBuf;
+ var bytes = this.bytes;
+ var bytesPos = this.bytesPos;
- while (codeSize < maxLen) {
- var b;
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad encoding in flate stream");
- codeBuf |= (b << codeSize);
- codeSize += 8;
- }
- var code = codes[codeBuf & ((1 << maxLen) - 1)];
- var codeLen = code >> 16;
- var codeVal = code & 0xffff;
- if (codeSize == 0|| codeSize < codeLen || codeLen == 0)
+ var b;
+ while (codeSize < bits) {
+ if (typeof (b = bytes[bytesPos++]) == "undefined")
error("Bad encoding in flate stream");
- this.codeBuf = (codeBuf >> codeLen);
- this.codeSize = (codeSize - codeLen);
- this.bytesPos = bytesPos;
- return codeVal;
- },
- ensureBuffer: function(requested) {
- var buffer = this.buffer;
- var current = buffer ? buffer.byteLength : 0;
- if (requested < current)
- return buffer;
- var size = 512;
- while (size < requested)
- size <<= 1;
- var buffer2 = Uint8Array(size);
- for (var i = 0; i < current; ++i)
- buffer2[i] = buffer[i];
- return this.buffer = buffer2;
- },
- getByte: function() {
- var pos = this.pos;
- while (this.bufferLength <= pos) {
- if (this.eof)
- return;
- this.readBlock();
- }
- return this.buffer[this.pos++];
- },
- getBytes: function(length) {
- var pos = this.pos;
-
- while (!this.eof && this.bufferLength < pos + length)
- this.readBlock();
-
- var end = pos + length;
- var bufEnd = this.bufferLength;
-
- if (end > bufEnd)
- end = bufEnd;
-
- this.pos = end;
- return this.buffer.subarray(pos, end)
- },
- lookChar: function() {
- var pos = this.pos;
- while (this.bufferLength <= pos) {
- if (this.eof)
- return;
- this.readBlock();
- }
- return String.fromCharCode(this.buffer[pos]);
- },
- getChar: function() {
- var ch = this.lookChar();
- // shouldnt matter what the position is if we get past the eof
- // so no need to check if ch is undefined
- this.pos++;
- return ch;
- },
- skip: function(n) {
- if (!n)
- n = 1;
- this.pos += n;
- },
- generateHuffmanTable: function(lengths) {
- var n = lengths.length;
-
- // find max code length
- var maxLen = 0;
- for (var i = 0; i < n; ++i) {
- if (lengths[i] > maxLen)
- maxLen = lengths[i];
- }
-
- // build the table
- var size = 1 << maxLen;
- var codes = Uint32Array(size);
- for (var len = 1, code = 0, skip = 2;
- len <= maxLen;
- ++len, code <<= 1, skip <<= 1) {
- for (var val = 0; val < n; ++val) {
- if (lengths[val] == len) {
- // bit-reverse the code
- var code2 = 0;
- var t = code;
- for (var i = 0; i < len; ++i) {
- code2 = (code2 << 1) | (t & 1);
- t >>= 1;
- }
-
- // fill the table entries
- for (var i = code2; i < size; i += skip)
- codes[i] = (len << 16) | val;
-
- ++code;
- }
- }
- }
-
- return [codes, maxLen];
- },
- readBlock: function() {
- function repeat(stream, array, len, offset, what) {
- var repeat = stream.getBits(len) + offset;
- while (repeat-- > 0)
- array[i++] = what;
- }
-
- var bytes = this.bytes;
- var bytesPos = this.bytesPos;
-
- // read block header
- var hdr = this.getBits(3);
- if (hdr & 1)
- this.eof = true;
- hdr >>= 1;
+ codeBuf |= b << codeSize;
+ codeSize += 8;
+ }
+ b = codeBuf & ((1 << bits) - 1);
+ this.codeBuf = codeBuf >> bits;
+ this.codeSize = codeSize -= bits;
+ this.bytesPos = bytesPos;
+ return b;
+ };
+ constructor.prototype.getCode = function(table) {
+ var codes = table[0];
+ var maxLen = table[1];
+ var codeSize = this.codeSize;
+ var codeBuf = this.codeBuf;
+ var bytes = this.bytes;
+ var bytesPos = this.bytesPos;
+ while (codeSize < maxLen) {
var b;
- if (hdr == 0) { // uncompressed block
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad block header in flate stream");
- var blockLen = b;
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad block header in flate stream");
- blockLen |= (b << 8);
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad block header in flate stream");
- var check = b;
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad block header in flate stream");
- check |= (b << 8);
- if (check != (~this.blockLen & 0xffff))
- error("Bad uncompressed block length in flate stream");
- var bufferLength = this.bufferLength;
- var buffer = this.ensureBuffer(bufferLength + blockLen);
- this.bufferLength = bufferLength + blockLen;
- for (var n = bufferLength; n < blockLen; ++n) {
- if (typeof (b = bytes[bytesPos++]) == "undefined") {
- this.eof = true;
- break;
+ if (typeof (b = bytes[bytesPos++]) == "undefined")
+ error("Bad encoding in flate stream");
+ codeBuf |= (b << codeSize);
+ codeSize += 8;
+ }
+ var code = codes[codeBuf & ((1 << maxLen) - 1)];
+ var codeLen = code >> 16;
+ var codeVal = code & 0xffff;
+ if (codeSize == 0|| codeSize < codeLen || codeLen == 0)
+ error("Bad encoding in flate stream");
+ this.codeBuf = (codeBuf >> codeLen);
+ this.codeSize = (codeSize - codeLen);
+ this.bytesPos = bytesPos;
+ return codeVal;
+ };
+ constructor.prototype.generateHuffmanTable = function(lengths) {
+ var n = lengths.length;
+
+ // find max code length
+ var maxLen = 0;
+ for (var i = 0; i < n; ++i) {
+ if (lengths[i] > maxLen)
+ maxLen = lengths[i];
+ }
+
+ // build the table
+ var size = 1 << maxLen;
+ var codes = Uint32Array(size);
+ for (var len = 1, code = 0, skip = 2;
+ len <= maxLen;
+ ++len, code <<= 1, skip <<= 1) {
+ for (var val = 0; val < n; ++val) {
+ if (lengths[val] == len) {
+ // bit-reverse the code
+ var code2 = 0;
+ var t = code;
+ for (var i = 0; i < len; ++i) {
+ code2 = (code2 << 1) | (t & 1);
+ t >>= 1;
}
- buffer[n] = b;
- }
- 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;
+ // fill the table entries
+ for (var i = code2; i < size; i += skip)
+ codes[i] = (len << 16) | val;
- // 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;
}
}
}
+
+ 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 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;
+ }
+ 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];
+ }
+ };
+
+ return constructor;
+})();
+
+var PredictorStream = (function() {
+ function constructor(stream, params) {
+ var predictor = this.predictor = params.get("Predictor") || 1;
+
+ if (predictor <= 1)
+ return stream; // no prediction
+ if (predictor !== 2 && (predictor < 10 || predictor > 15))
+ error("Unsupported predictor");
+
+ if (predictor === 2)
+ this.readBlock = this.readBlockTiff;
+ else
+ this.readBlock = this.readBlockPng;
+
+ this.stream = stream;
+ this.dict = stream.dict;
+ if (params.has("EarlyChange")) {
+ error("EarlyChange predictor parameter is not supported");
+ }
+ var colors = this.colors = params.get("Colors") || 1;
+ var bits = this.bits = params.get("BitsPerComponent") || 8;
+ var columns = this.columns = params.get("Columns") || 1;
+
+ var pixBytes = this.pixBytes = (colors * bits + 7) >> 3;
+ // add an extra pixByte to represent the pixel left of column 0
+ var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3;
+
+ DecodeStream.call(this);
+ }
+
+ constructor.prototype = Object.create(DecodeStream.prototype);
+
+ constructor.prototype.readBlockTiff = function() {
+ var buffer = this.buffer;
+ var pos = this.pos;
+
+ var rowBytes = this.rowBytes;
+ var pixBytes = this.pixBytes;
+
+ var bufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(bufferLength + rowBytes);
+ var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes);
+
+ var bits = this.bits;
+ var colors = this.colors;
+
+ var rawBytes = this.stream.getBytes(rowBytes);
+
+ if (bits === 1) {
+ var inbuf = 0;
+ for (var i = 0; i < rowBytes; ++i) {
+ var c = rawBytes[i];
+ inBuf = (inBuf << 8) | c;
+ // bitwise addition is exclusive or
+ // first shift inBuf and then add
+ currentRow[i] = (c ^ (inBuf >> colors)) & 0xFF;
+ // truncate inBuf (assumes colors < 16)
+ inBuf &= 0xFFFF;
+ }
+ } else if (bits === 8) {
+ for (var i = 0; i < colors; ++i)
+ currentRow[i] = rawBytes[i];
+ for (; i < rowBytes; ++i)
+ currentRow[i] = currentRow[i - colors] + rawBytes[i];
+ } else {
+ var compArray = new Uint8Array(colors + 1);
+ var bitMask = (1 << bits) - 1;
+ var inbuf = 0, outbut = 0;
+ var inbits = 0, outbits = 0;
+ var j = 0, k = 0;
+ var columns = this.columns;
+ for (var i = 0; i < columns; ++i) {
+ for (var kk = 0; kk < colors; ++kk) {
+ if (inbits < bits) {
+ inbuf = (inbuf << 8) | (rawBytes[j++] & 0xFF);
+ inbits += 8;
+ }
+ compArray[kk] = (compArray[kk] +
+ (inbuf >> (inbits - bits))) & bitMask;
+ inbits -= bits;
+ outbuf = (outbuf << bits) | compArray[kk];
+ outbits += bits;
+ if (outbits >= 8) {
+ currentRow[k++] = (outbuf >> (outbits - 8)) & 0xFF;
+ outbits -= 8;
+ }
+ }
+ }
+ if (outbits > 0) {
+ currentRow[k++] = (outbuf << (8 - outbits)) +
+ (inbuf & ((1 << (8 - outbits)) - 1))
+ }
+ }
+ this.bufferLength += rowBytes;
+ };
+ constructor.prototype.readBlockPng = function() {
+ var buffer = this.buffer;
+ var pos = this.pos;
+
+ var rowBytes = this.rowBytes;
+ var pixBytes = this.pixBytes;
+
+ var predictor = this.stream.getByte();
+ var rawBytes = this.stream.getBytes(rowBytes);
+
+ var bufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(bufferLength + pixBytes);
+
+ var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes);
+ var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
+ if (prevRow.length == 0)
+ prevRow = currentRow;
+
+ switch (predictor) {
+ case 0:
+ break;
+ case 1:
+ for (var i = 0; i < pixBytes; ++i)
+ currentRow[i] = rawBytes[i];
+ for (; i < rowBytes; ++i)
+ currentRow[i] = (currentRow[i - pixBytes] + rawBytes[i]) & 0xFF;
+ break;
+ case 2:
+ for (var i = 0; i < rowBytes; ++i)
+ currentRow[i] = (prevRow[i] + rawBytes[i]) & 0xFF;
+ break;
+ case 3:
+ for (var i = 0; i < pixBytes; ++i)
+ currentRow[i] = (prevRow[i] >> 1) + rawBytes[i];
+ for (; i < rowBytes; ++i)
+ currentRow[i] = (((prevRow[i] + currentRow[i - pixBytes])
+ >> 1) + rawBytes[i]) & 0xFF;
+ break;
+ case 4:
+ // we need to save the up left pixels values. the simplest way
+ // is to create a new buffer
+ for (var i = 0; i < pixBytes; ++i)
+ currentRow[i] = rawBytes[i];
+ for (; i < rowBytes; ++i) {
+ var up = prevRow[i];
+ var upLeft = lastRow[i - pixBytes];
+ var left = currentRow[i - pixBytes];
+ var p = left + up - upLeft;
+
+ var pa = p - left;
+ if (pa < 0)
+ pa = -pa;
+ var pb = p - up;
+ if (pb < 0)
+ pb = -pb;
+ var pc = p - upLeft;
+ if (pc < 0)
+ pc = -pc;
+
+ var c = rawBytes[i];
+ if (pa <= pb && pa <= pc)
+ currentRow[i] = left + c;
+ else if (pb <= pc)
+ currentRow[i] = up + c;
+ else
+ currentRow[i] = upLeft + c;
+ break;
+ }
+ default:
+ error("Unsupported predictor");
+ break;
+ }
+ this.bufferLength += rowBytes;
};
return constructor;
@@ -543,101 +753,14 @@ var JpegStream = (function() {
constructor.prototype = {
getImage: function() {
return this.domImage;
+ },
+ getChar: function() {
+ error("internal error: getChar is not valid on JpegStream");
}
};
return constructor;
})();
-
-var PredictorStream = (function() {
- function constructor(stream, params) {
- this.stream = stream;
- this.dict = stream.dict;
- this.predictor = params.get("Predictor") || 1;
- if (this.predictor <= 1) {
- return stream; // no prediction
- }
- if (params.has("EarlyChange")) {
- error("EarlyChange predictor parameter is not supported");
- }
- this.colors = params.get("Colors") || 1;
- this.bitsPerComponent = params.get("BitsPerComponent") || 8;
- this.columns = params.get("Columns") || 1;
- if (this.colors !== 1 || this.bitsPerComponent !== 8) {
- error("Multi-color and multi-byte predictors are not supported");
- }
- if (this.predictor < 10 || this.predictor > 15) {
- error("Unsupported predictor");
- }
- this.currentRow = new Uint8Array(this.columns);
- this.pos = 0;
- this.bufferLength = 0;
- }
-
- constructor.prototype = {
- readRow : function() {
- var lastRow = this.currentRow;
- var predictor = this.stream.getByte();
- var currentRow = this.stream.getBytes(this.columns), i;
- switch (predictor) {
- default:
- error("Unsupported predictor");
- break;
- case 0:
- break;
- case 2:
- for (i = 0; i < currentRow.length; ++i) {
- currentRow[i] = (lastRow[i] + currentRow[i]) & 0xFF;
- }
- break;
- }
- this.pos = 0;
- this.bufferLength = currentRow.length;
- this.currentRow = currentRow;
- },
- 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");
@@ -648,6 +771,74 @@ var DecryptStream = (function() {
return constructor;
})();
+var Ascii85Stream = (function() {
+ function constructor(str) {
+ this.str = str;
+ this.dict = str.dict;
+ this.input = new Uint8Array(5);
+
+ DecodeStream.call(this);
+ }
+
+ 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) {
+ var buffer = this.ensureBuffer(bufferLength + 4);
+ for (var i = 0; i < 4; ++i)
+ buffer[bufferLength + i] = 0;
+ this.bufferLength += 4;
+ } else {
+ var input = this.input;
+ 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;
+ }
+ var buffer = this.ensureBuffer(bufferLength + i - 1);
+ 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;
+ }
+ }
+ };
+
+ return constructor;
+})();
+
var Name = (function() {
function constructor(name) {
this.name = name;
@@ -682,6 +873,9 @@ var Dict = (function() {
get2: function(key1, key2) {
return this.get(key1) || this.get(key2);
},
+ get3: function(key1, key2, key3) {
+ return this.get(key1) || this.get(key2) || this.get(key3);
+ },
has: function(key) {
return key in this.map;
},
@@ -689,11 +883,10 @@ var Dict = (function() {
this.map[key] = value;
},
forEach: function(aCallback) {
- for (var key in this.map)
- aCallback(key, this.map[key]);
+ for (var key in this.map)
+ aCallback(key, this.map[key]);
}
};
-
return constructor;
})();
@@ -1005,7 +1198,7 @@ var Lexer = (function() {
break;
}
}
-
+
// start reading token
switch (ch) {
case '0': case '1': case '2': case '3': case '4':
@@ -1179,7 +1372,7 @@ var Parser = (function() {
}
return str;
}
-
+
// simple object
var obj = this.buf1;
this.shift();
@@ -1192,7 +1385,7 @@ var Parser = (function() {
// get stream start position
lexer.skipToNextLine();
var pos = stream.pos;
-
+
// get length
var length = dict.get("Length");
var xref = this.xref;
@@ -1230,7 +1423,8 @@ var Parser = (function() {
if (IsArray(filter)) {
var filterArray = filter;
var paramsArray = params;
- for (filter in filterArray) {
+ for (var i = 0, ii = filterArray.length; i < ii; ++i) {
+ filter = filterArray[i];
if (!IsName(filter))
error("Bad filter name");
else {
@@ -1252,6 +1446,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");
}
@@ -1261,7 +1457,7 @@ var Parser = (function() {
return constructor;
})();
-
+
var Linearization = (function() {
function constructor(stream) {
this.parser = new Parser(new Lexer(stream), false);
@@ -1443,26 +1639,29 @@ var XRef = (function() {
for (i = 0; i < n; ++i) {
var type = 0, offset = 0, generation = 0;
for (j = 0; j < typeFieldWidth; ++j)
- type = (type << 8) | stream.getByte();
+ type = (type << 8) | stream.getByte();
+ // if type field is absent, its default value = 1
+ if (typeFieldWidth == 0)
+ type = 1;
for (j = 0; j < offsetFieldWidth; ++j)
- offset = (offset << 8) | stream.getByte();
+ offset = (offset << 8) | stream.getByte();
for (j = 0; j < generationFieldWidth; ++j)
- generation = (generation << 8) | stream.getByte();
- var entry = new Ref(offset, generation);
- if (typeFieldWidth > 0) {
- switch (type) {
- case 0:
- entry.free = true;
- break;
- case 1:
- entry.uncompressed = true;
- break;
- case 2:
- break;
- default:
- error("Invalid XRef entry type");
- break;
- }
+ generation = (generation << 8) | stream.getByte();
+ var entry = {}
+ entry.offset = offset;
+ entry.gen = generation;
+ switch (type) {
+ case 0:
+ entry.free = true;
+ break;
+ case 1:
+ entry.uncompressed = true;
+ break;
+ case 2:
+ break;
+ default:
+ error("Invalid XRef entry type");
+ break;
}
if (!this.entries[first + i])
this.entries[first + i] = entry;
@@ -1592,14 +1791,28 @@ var Page = (function() {
}
constructor.prototype = {
+ getPageProp: function(key) {
+ return this.pageDict.get(key);
+ },
+ inheritPageProp: function(key) {
+ var dict = this.pageDict;
+ var obj = dict.get(key);
+ while (!obj) {
+ dict = this.xref.fetchIfRef(dict.get("Parent"));
+ if (!dict)
+ break;
+ obj = dict.get(key);
+ }
+ return obj;
+ },
get content() {
- return shadow(this, "content", this.pageDict.get("Contents"));
+ return shadow(this, "content", this.getPageProp("Contents"));
},
get resources() {
- return shadow(this, "resources", this.pageDict.get("Resources"));
+ return shadow(this, "resources", this.inheritPageProp("Resources"));
},
get mediaBox() {
- var obj = this.pageDict.get("MediaBox");
+ var obj = this.inheritPageProp("MediaBox");
return shadow(this, "mediaBox", ((IsArray(obj) && obj.length == 4)
? obj
: null));
@@ -1688,7 +1901,7 @@ var Catalog = (function() {
pageCache.push(new Page(this.xref, pageCache.length, obj));
} else { // must be a child page dictionary
assertWellFormed(IsDict(obj),
- "page dictionary kid reference points to wrong type of object");
+ "page dictionary kid reference points to wrong type of object");
this.traverseKids(obj);
}
}
@@ -2049,6 +2262,7 @@ var CanvasGraphics = (function() {
S: "stroke",
s: "closeStroke",
f: "fill",
+ F: "fill",
"f*": "eoFill",
B: "fillStroke",
"B*": "eoFillStroke",
@@ -2129,13 +2343,18 @@ 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");
fontName = fontName.name.replace("+", "_");
- var fontFile = descriptor.get2("FontFile", "FontFile2");
+ var fontFile = descriptor.get3("FontFile", "FontFile2", "FontFile3");
if (!fontFile)
error("FontFile not found for font: " + fontName);
fontFile = xref.fetchIfRef(fontFile);
@@ -2162,10 +2381,10 @@ var CanvasGraphics = (function() {
// Get the font charset if any
var charset = descriptor.get("CharSet");
- if (charset)
+ if (charset) {
assertWellFormed(IsString(charset), "invalid charset");
-
- charset = charset.split("/");
+ charset = charset.split("/");
+ }
} else if (IsName(encoding)) {
var encoding = Encodings[encoding.name];
if (!encoding)
@@ -2507,7 +2726,7 @@ var CanvasGraphics = (function() {
setWordSpacing: function(spacing) {
TODO("word spacing");
},
- setHSpacing: function(scale) {
+ setHScale: function(scale) {
TODO("horizontal text scale");
},
setLeading: function(leading) {
@@ -2779,7 +2998,6 @@ var CanvasGraphics = (function() {
shadingFill: function(entryRef) {
var xref = this.xref;
var res = this.res;
-
var shadingRes = xref.fetchIfRef(res.get("Shading"));
if (!shadingRes)
error("No shading resource found");
@@ -2804,13 +3022,15 @@ var CanvasGraphics = (function() {
if (background)
TODO("handle background colors");
- const types = [null, this.fillFunctionShading,
- this.fillAxialShading, this.fillRadialShading];
-
+ const types = [null,
+ this.fillFunctionShading,
+ this.fillAxialShading,
+ this.fillRadialShading];
+
var typeNum = shading.get("ShadingType");
var fillFn = types[typeNum];
- if (!fillFn)
- error("Unknown type of shading");
+ if (!fillFn)
+ error("Unknown or NYI type of shading '"+ typeNum +"'");
fillFn.apply(this, [shading]);
this.restore();
@@ -2820,7 +3040,7 @@ var CanvasGraphics = (function() {
var coordsArr = sh.get("Coords");
var x0 = coordsArr[0], y0 = coordsArr[1],
x1 = coordsArr[2], y1 = coordsArr[3];
-
+
var t0 = 0.0, t1 = 1.0;
if (sh.has("Domain")) {
var domainArr = sh.get("Domain");
@@ -2863,6 +3083,10 @@ var CanvasGraphics = (function() {
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
},
+ fillRadialShading: function(sh) {
+ TODO("radial shading");
+ },
+
// Images
beginInlineImage: function() {
TODO("inline images");
@@ -2879,12 +3103,12 @@ var CanvasGraphics = (function() {
return;
xobj = this.xref.fetchIfRef(xobj);
assertWellFormed(IsStream(xobj), "XObject should be a stream");
-
+
var oc = xobj.dict.get("OC");
if (oc) {
TODO("oc for xobject");
}
-
+
var opi = xobj.dict.get("OPI");
if (opi) {
TODO("opi for xobject");
@@ -2986,15 +3210,14 @@ var CanvasGraphics = (function() {
// actual image
var csStream = dict.get2("ColorSpace", "CS");
csStream = xref.fetchIfRef(csStream);
- if (IsName(csStream) && inline)
+ if (IsName(csStream) && inline)
csStream = colorSpaces.get(csStream);
-
- var colorSpace = new ColorSpace(xref, csStream);
+ var colorSpace = new ColorSpace(xref, csStream);
var decode = dict.get2("Decode", "D");
TODO("create color map");
-
+
var mask = image.dict.get("Mask");
mask = xref.fetchIfRef(mask);
var smask = image.dict.get("SMask");
@@ -3019,7 +3242,7 @@ var CanvasGraphics = (function() {
var maskBPC = maskDict.get2("BitsPerComponent", "BPC");
if (!maskBPC)
error("Invalid image mask bpc");
-
+
var maskCsStream = maskDict.get2("ColorSpace", "CS");
maskCsStream = xref.fetchIfRef(maskCsStream);
var maskColorSpace = new ColorSpace(xref, maskCsStream);
@@ -3029,9 +3252,7 @@ var CanvasGraphics = (function() {
var maskDecode = maskDict.get2("Decode", "D");
if (maskDecode)
TODO("Handle mask decode");
- // handle matte object
- } else {
- smask = null;
+ // handle matte object
}
var tmpCanvas = document.createElement("canvas");
@@ -3040,21 +3261,21 @@ var CanvasGraphics = (function() {
var tmpCtx = tmpCanvas.getContext("2d");
var imgData = tmpCtx.getImageData(0, 0, w, h);
var pixels = imgData.data;
-
+
if (bitsPerComponent != 8)
- error("unhandled number of bits per component");
-
+ error("unhandled number of bits per component");
+
if (smask) {
if (maskColorSpace.numComps != 1)
error("Incorrect number of components in smask");
-
+
var numComps = colorSpace.numComps;
var imgArray = image.getBytes(numComps * w * h);
var imgIdx = 0;
var smArray = smask.getBytes(w * h);
var smIdx = 0;
-
+
var length = 4 * w * h;
switch (numComps) {
case 1:
@@ -3075,13 +3296,13 @@ 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;
var imgArray = image.getBytes(numComps * w * h);
var imgIdx = 0;
-
+
var length = 4 * w * h;
switch (numComps) {
case 1:
@@ -3102,7 +3323,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);
@@ -3183,12 +3404,15 @@ var ColorSpace = (function() {
case "G":
this.numComps = 1;
break;
+ case "DeviceRGB":
+ this.numComps = 3;
+ break;
}
TODO("fill in color space constructor");
} else if (IsArray(cs)) {
var mode = cs[0].name;
this.mode = mode;
-
+
var stream = cs[1];
stream = xref.fetchIfRef(stream);
@@ -3201,19 +3425,28 @@ var ColorSpace = (function() {
break;
case "ICCBased":
var dict = stream.dict;
-
this.stream = stream;
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");
}
};
-
+
constructor.prototype = {
};
@@ -3226,13 +3459,15 @@ var PDFFunction = (function() {
if (!dict)
dict = fn;
- const types = [this.constructSampled, null,
- this.constructInterpolated, this.constructStiched,
- this.constructPostScript];
-
+ const types = [this.constructSampled,
+ null,
+ this.constructInterpolated,
+ this.constructStiched,
+ this.constructPostScript];
+
var typeNum = dict.get("FunctionType");
var typeFn = types[typeNum];
- if (!typeFn)
+ if (!typeFn)
error("Unknown type of function");
typeFn.apply(this, [fn, dict]);
@@ -3245,7 +3480,7 @@ var PDFFunction = (function() {
if (!domain || !range)
error("No domain or range");
-
+
var inputSize = domain.length / 2;
var outputSize = range.length / 2;
@@ -3259,7 +3494,7 @@ var PDFFunction = (function() {
order = 1;
if (order !== 1)
error ("No support for cubic spline interpolation");
-
+
var encode = dict.get("Encode");
if (!encode) {
encode = [];
@@ -3288,15 +3523,14 @@ var PDFFunction = (function() {
for (var i = 0; i < inputSize; i++) {
var i2 = i * 2;
-
+
// clip to the domain
var v = clip(args[i], domain[i2], domain[i2 + 1]);
// encode
- v = encode[i2] + ((v - domain[i2]) *
- (encode[i2 + 1] - encode[i2]) /
- (domain[i2 + 1] - domain[i2]));
-
+ v = encode[i2] + ((v - domain[i2]) *
+ (encode[i2 + 1] - encode[i2]) /
+ (domain[i2 + 1] - domain[i2]));
// clip to the size
args[i] = clip(v, 0, size[i] - 1);
}
@@ -3319,12 +3553,11 @@ var PDFFunction = (function() {
var high = samples[ceil + i];
var v = low * scale + high * (1 - scale);
}
-
+
var i2 = i * 2;
// decode
- v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) /
- ((1 << bps) - 1));
-
+ v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) /
+ ((1 << bps) - 1));
// clip to the domain
output.push(clip(v, range[i2], range[i2 + 1]));
}
@@ -3359,10 +3592,10 @@ var PDFFunction = (function() {
},
constructInterpolated: function() {
error("unhandled type of function");
- },
+ },
constructStiched: function() {
error("unhandled type of function");
- },
+ },
constructPostScript: function() {
error("unhandled type of function");
}
diff --git a/test.py b/test.py
index 46d30fef5..0c326ec09 100644
--- a/test.py
+++ b/test.py
@@ -1,7 +1,16 @@
-import json, os, sys, subprocess
+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 = {
@@ -20,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):
@@ -34,8 +49,11 @@ class PDFTestHandler(BaseHTTPRequestHandler):
BaseHTTPRequestHandler.log_request(code, size)
def do_GET(self):
+ url = urlparse(self.path)
+ # Ignore query string
+ path, _ = url.path, url.query
cwd = os.getcwd()
- path = os.path.abspath(os.path.realpath(cwd + os.sep + self.path))
+ path = os.path.abspath(os.path.realpath(cwd + os.sep + path))
cwd = os.path.abspath(cwd)
prefix = os.path.commonprefix(( path, cwd ))
_, ext = os.path.splitext(path)
@@ -69,31 +87,59 @@ class PDFTestHandler(BaseHTTPRequestHandler):
self.end_headers()
result = json.loads(self.rfile.read(numBytes))
- browser = 'firefox4'
- id, failure, round, page, snapshot = result['id'], result['failure'], result['round'], result['page'], result['snapshot']
+ browser, id, failure, round, page, snapshot = result['browser'], result['id'], result['failure'], result['round'], result['page'], result['snapshot']
taskResults = State.taskResults[browser][id]
- taskResults[round][page - 1] = Result(snapshot, failure)
+ taskResults[round].append(Result(snapshot, failure))
+ assert len(taskResults[round]) == page
if result['taskDone']:
check(State.manifest[id], taskResults, browser)
+ # Please oh please GC this ...
+ del State.taskResults[browser][id]
State.remaining -= 1
State.done = (0 == State.remaining)
-
-def set_up():
+
+def 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('test_manifest.json')
+ mf = open(manifestFile)
manifestList = json.load(mf)
mf.close()
+ for item in manifestList:
+ f, isLink = item['file'], item.get('link', False)
+ if isLink and not os.access(f, os.R_OK):
+ linkFile = open(f +'.link')
+ link = linkFile.read()
+ linkFile.close()
+
+ sys.stdout.write('Downloading '+ link +' to '+ f +' ...')
+ sys.stdout.flush()
+ response = urllib2.urlopen(link)
+
+ out = open(f, 'w')
+ out.write(response.read())
+ out.close()
+
+ print 'done'
+
for b in testBrowsers:
State.taskResults[b] = { }
for item in manifestList:
@@ -101,15 +147,16 @@ def set_up():
State.manifest[id] = item
taskResults = [ ]
for r in xrange(rounds):
- taskResults.append([ None ] * 100)
+ taskResults.append([ ])
State.taskResults[b][id] = taskResults
State.remaining = len(manifestList)
for b in testBrowsers:
print 'Launching', b
+ qs = 'browser='+ b +'&manifestFile='+ manifestFile
subprocess.Popen(( os.path.abspath(os.path.realpath(b)),
- 'http://localhost:8080/test_slave.html' ))
+ 'http://localhost:8080/test_slave.html?'+ qs))
def check(task, results, browser):
@@ -123,13 +170,14 @@ 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:
return
kind = task['type']
- if '==' == kind:
+ if 'eq' == kind:
checkEq(task, results, browser)
elif 'fbf' == kind:
checkFBF(task, results, browser)
@@ -140,23 +188,60 @@ def check(task, results, browser):
def checkEq(task, results, browser):
- print ' !!! [TODO: == tests] !!!'
- print 'TEST-PASS | == test', task['id'], '| in', browser
+ pfx = os.path.join(REFDIR, sys.platform, browser, task['id'])
+ results = results[0]
+ passed = True
+ for page in xrange(len(results)):
+ snapshot = results[page].snapshot
+ ref = None
+ 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()
+
+ 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
-printed = [False]
def checkFBF(task, results, browser):
round0, round1 = results[0], results[1]
assert len(round0) == len(round1)
+ passed = True
for page in xrange(len(round1)):
r0Page, r1Page = round0[page], round1[page]
if r0Page is None:
break
if r0Page.snapshot != r1Page.snapshot:
print 'TEST-UNEXPECTED-FAIL | forward-back-forward test', task['id'], '| in', browser, '| first rendering of page', page + 1, '!= second'
- print 'TEST-PASS | forward-back-forward test', task['id'], '| in', browser
+ passed = False
+ State.numFBFFailures += 1
+ if passed:
+ print 'TEST-PASS | forward-back-forward test', task['id'], '| in', browser
def checkLoad(task, results, browser):
@@ -165,11 +250,55 @@ def checkLoad(task, results, browser):
print 'TEST-PASS | load test', task['id'], '| in', browser
-def main():
- set_up()
+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):
+ masterMode = False
+ manifestFile = DEFAULT_MANIFEST_FILE
+ if len(args) == 1:
+ masterMode = (args[0] == '-m')
+ manifestFile = args[0] if not masterMode else manifestFile
+
+ setUp(manifestFile, masterMode)
+
server = HTTPServer(('127.0.0.1', 8080), PDFTestHandler)
while not State.done:
server.handle_request()
+ processResults()
+
if __name__ == '__main__':
- main()
+ main(sys.argv[1:])
diff --git a/test_manifest.json b/test_manifest.json
index 2f45a026c..036b7aafc 100644
--- a/test_manifest.json
+++ b/test_manifest.json
@@ -1,8 +1,8 @@
[
- { "id": "tracemonkey-==",
+ { "id": "tracemonkey-eq",
"file": "tests/tracemonkey.pdf",
"rounds": 1,
- "type": "=="
+ "type": "eq"
},
{ "id": "tracemonkey-fbf",
"file": "tests/tracemonkey.pdf",
@@ -13,5 +13,11 @@
"file": "tests/canvas.pdf",
"rounds": 1,
"type": "load"
+ },
+ { "id": "pdfspec-load",
+ "file": "tests/pdf.pdf",
+ "link": true,
+ "rounds": 1,
+ "type": "load"
}
]
diff --git a/test_slave.html b/test_slave.html
index c560d90d0..06b911810 100644
--- a/test_slave.html
+++ b/test_slave.html
@@ -5,9 +5,24 @@