diff --git a/multi-page-viewer.css b/multi-page-viewer.css
new file mode 100644
index 000000000..488b10bd9
--- /dev/null
+++ b/multi-page-viewer.css
@@ -0,0 +1,50 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+
+body {
+ 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;
+}
+
+.page {
+ width: 816px;
+ height: 1056px;
+ margin: 10px auto;
+}
+
+#controls {
+ background-color: #eee;
+ border-bottom: 1px solid #666;
+ padding: 4px 0px 0px 10px;
+ position:fixed;
+ left: 0px;
+ top: 0px;
+ height: 28px;
+ width: 100%;
+ box-shadow: 0px 2px 8px #000;
+ -moz-box-shadow: 0px 2px 8px #000;
+ -webkit-box-shadow: 0px 2px 8px #000;
+}
+
+#pageNumber {
+ margin: 0px 0px 0px 10px;
+ text-align: right;
+}
+
+#viewer {
+ background-color: #929292;
+ margin: 32px 0px 0px;
+ padding: 8px 0px;
+ width: 100%;
+}
diff --git a/multi-page-viewer.html b/multi-page-viewer.html
new file mode 100644
index 000000000..a166f7fd4
--- /dev/null
+++ b/multi-page-viewer.html
@@ -0,0 +1,21 @@
+
+
+
+pdf.js Multi-Page Viewer
+
+
+
+
+
+
+
+
+
+
+
+ /
+ --
+
+
+
+
diff --git a/multi-page-viewer.js b/multi-page-viewer.js
new file mode 100644
index 000000000..20d2e373e
--- /dev/null
+++ b/multi-page-viewer.js
@@ -0,0 +1,261 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
+/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+
+var PDFViewer = {
+ queryParams: {},
+
+ element: null,
+
+ pageNumberInput: null,
+
+ 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';
+
+ PDFViewer.element.appendChild(anchor);
+ PDFViewer.element.appendChild(div);
+ },
+
+ removePage: function(num) {
+ var div = document.getElementById('pageContainer' + num);
+
+ if (div && div.hasChildNodes()) {
+ while (div.childNodes.length > 0) {
+ 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 = 816;
+ canvas.height = 1056;
+
+ 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);
+
+ // This should be called when font loading is complete
+ page.display(gfx);
+
+ div.appendChild(canvas);
+ }
+ }
+ },
+
+ goToPage: function(num) {
+ if (0 <= num && num <= PDFViewer.numberOfPages) {
+ PDFViewer.pageNumber = num;
+ }
+
+ PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
+ document.location.hash = PDFViewer.pageNumber;
+ },
+
+ goToPreviousPage: function() {
+ if (PDFViewer.pageNumber > 1) {
+ --PDFViewer.pageNumber;
+ }
+
+ PDFViewer.goToPage(PDFViewer.pageNumber);
+ },
+
+ goToNextPage: function() {
+ if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
+ ++PDFViewer.pageNumber;
+ }
+
+ PDFViewer.goToPage(PDFViewer.pageNumber);
+ },
+
+ open: 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.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);
+ }
+ }
+ };
+
+ req.send(null);
+ }
+};
+
+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();
+ };
+
+ var previousPageButton = document.getElementById('previousPageButton');
+ previousPageButton.onclick = PDFViewer.goToPreviousPage;
+
+ var nextPageButton = document.getElementById('nextPageButton');
+ nextPageButton.onclick = PDFViewer.goToNextPage;
+
+ PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
+ PDFViewer.open(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);
+ };
+};