diff --git a/src/core.js b/src/core.js index 26e376327..0c1e4b6cb 100644 --- a/src/core.js +++ b/src/core.js @@ -527,6 +527,13 @@ var PDFDocModel = (function PDFDocModelClosure() { this.startXRef, this.mainXRefEntriesOffset); this.catalog = new Catalog(this.xref); + if (this.xref.trailer && this.xref.trailer.has('ID')) { + var fileID = ''; + this.xref.trailer.get('ID')[0].split('').forEach(function(el) { + fileID += Number(el.charCodeAt(0)).toString(16); + }); + this.fileID = fileID; + } }, get numPages() { var linearization = this.linearization; @@ -534,6 +541,22 @@ var PDFDocModel = (function PDFDocModelClosure() { // shadow the prototype getter return shadow(this, 'numPages', num); }, + getFingerprint: function pdfDocGetFingerprint() { + if (this.fileID) { + return this.fileID; + } else { + // If we got no fileID, then we generate one, + // from the first 100 bytes of PDF + var data = this.stream.bytes.subarray(0, 100); + var hash = calculateMD5(data, 0, data.length); + var strHash = ''; + for (var i = 0, length = hash.length; i < length; i++) { + strHash += Number(hash[i]).toString(16); + } + + return strHash; + } + }, getPage: function pdfDocGetPage(n) { return this.catalog.getPage(n); } @@ -560,7 +583,7 @@ var PDFDoc = (function PDFDocClosure() { this.data = data; this.stream = stream; this.pdf = new PDFDocModel(stream); - + this.fingerprint = this.pdf.getFingerprint(); this.catalog = this.pdf.catalog; this.objs = new PDFObjects(); diff --git a/src/obj.js b/src/obj.js index bdb0ba58a..a0c1fdc8a 100644 --- a/src/obj.js +++ b/src/obj.js @@ -269,7 +269,7 @@ var XRef = (function XRefClosure() { this.entries = []; this.xrefstms = {}; var trailerDict = this.readXRef(startXRef); - + this.trailer = trailerDict; // prepare the XRef cache this.cache = []; diff --git a/web/viewer.js b/web/viewer.js index 93b4e70c2..b596de213 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -12,6 +12,7 @@ var kScrollbarPadding = 40; var kMinScale = 0.25; var kMaxScale = 4.0; var kImageDirectory = './images/'; +var kSettingsMemory = 20; var Cache = function cacheCache(size) { var data = []; @@ -25,13 +26,126 @@ var Cache = function cacheCache(size) { }; }; +var RenderingQueue = (function RenderingQueueClosure() { + function RenderingQueue() { + this.items = []; + } + + RenderingQueue.prototype = { + enqueueDraw: function RenderingQueueEnqueueDraw(item) { + if ('rendering' in item) + return; // is already in the queue + + item.rendering = true; + this.items.push(item); + if (this.items.length > 1) + return; // not first item + + item.draw(this.continueExecution.bind(this)); + }, + continueExecution: function RenderingQueueContinueExecution() { + var item = this.items.shift(); + delete item.rendering; + + if (this.items.length == 0) + return; // queue is empty + + item = this.items[0]; + item.draw(this.continueExecution.bind(this)); + } + }; + + return RenderingQueue; +})(); + +// Settings Manager - This is a utility for saving settings +// First we see if localStorage is available, FF bug #495747 +// If not, we use FUEL in FF +var Settings = (function SettingsClosure() { + var isLocalStorageEnabled = (function localStorageEnabledTest() { + try { + localStorage; + } catch (e) { + return false; + } + return true; + })(); + var extPrefix = 'extensions.uriloader@pdf.js'; + var isExtension = location.protocol == 'chrome:' && !isLocalStorageEnabled; + var inPrivateBrowsing = false; + if (isExtension) { + var pbs = Components.classes['@mozilla.org/privatebrowsing;1'] + .getService(Components.interfaces.nsIPrivateBrowsingService); + inPrivateBrowsing = pbs.privateBrowsingEnabled; + } + + function Settings(fingerprint) { + var database = null; + var index; + if (inPrivateBrowsing) + return false; + else if (isExtension) + database = Application.prefs.getValue(extPrefix + '.database', '{}'); + else if (isLocalStorageEnabled) + database = localStorage.getItem('database') || '{}'; + else + return false; + + database = JSON.parse(database); + if (!('files' in database)) + database.files = []; + if (database.files.length >= kSettingsMemory) + database.files.shift(); + for (var i = 0, length = database.files.length; i < length; i++) { + var branch = database.files[i]; + if (branch.fingerprint == fingerprint) { + index = i; + break; + } + } + if (typeof index != 'number') + index = database.files.push({fingerprint: fingerprint}) - 1; + this.file = database.files[index]; + this.database = database; + if (isExtension) + Application.prefs.setValue(extPrefix + '.database', + JSON.stringify(database)); + else if (isLocalStorageEnabled) + localStorage.setItem('database', JSON.stringify(database)); + } + + Settings.prototype = { + set: function settingsSet(name, val) { + if (inPrivateBrowsing) + return false; + var file = this.file; + file[name] = val; + if (isExtension) + Application.prefs.setValue(extPrefix + '.database', + JSON.stringify(this.database)); + else if (isLocalStorageEnabled) + localStorage.setItem('database', JSON.stringify(this.database)); + }, + + get: function settingsGet(name, defaultValue) { + if (inPrivateBrowsing) + return defaultValue; + else + return this.file[name] || defaultValue; + } + }; + + return Settings; +})(); + var cache = new Cache(kCacheSize); +var renderingQueue = new RenderingQueue(); var currentPageNumber = 1; var PDFView = { pages: [], thumbnails: [], - currentScale: kDefaultScale, + currentScale: 0, initialBookmark: document.location.hash.substring(1), setScale: function pdfViewSetScale(val, resetAutoSettings) { @@ -272,8 +386,20 @@ var PDFView = { this.error('An error occurred while reading the PDF.', e); } var pagesCount = pdf.numPages; + var id = pdf.fingerprint; + var storedHash = null; document.getElementById('numPages').innerHTML = pagesCount; document.getElementById('pageNumber').max = pagesCount; + PDFView.documentFingerprint = id; + var store = PDFView.store = new Settings(id); + if (store.get('exists', false)) { + var page = store.get('page', '1'); + var zoom = store.get('zoom', PDFView.currentScale); + var left = store.get('scrollLeft', '0'); + var top = store.get('scrollTop', '0'); + + storedHash = 'page=' + page + '&zoom=' + zoom + ',' + left + ',' + top; + } var pages = this.pages = []; var pagesRefMap = {}; @@ -294,7 +420,6 @@ var PDFView = { this.pagesRefMap = pagesRefMap; this.destinations = pdf.catalog.destinations; - this.setScale(scale || kDefaultScale, true); if (pdf.catalog.documentOutline) { this.outline = new DocumentOutlineView(pdf.catalog.documentOutline); @@ -307,8 +432,12 @@ var PDFView = { this.setHash(this.initialBookmark); this.initialBookmark = null; } - else + else if (storedHash) + this.setHash(storedHash); + else { + this.setScale(scale || kDefaultScale, true); this.page = 1; + } }, setHash: function pdfViewSetHash(hash) { @@ -334,7 +463,7 @@ var PDFView = { if ('zoom' in params) { var zoomArgs = params.zoom.split(','); // scale,left,top // building destination array - var dest = [null, new Name('XYZ'), (zoomArgs[1] | 0), + var dest = [null, {name: 'XYZ'}, (zoomArgs[1] | 0), (zoomArgs[2] | 0), (zoomArgs[0] | 0) / 100]; var currentPage = this.pages[pageNumber - 1]; currentPage.scrollIntoView(dest); @@ -609,10 +738,11 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, }, 0); }; - this.draw = function pageviewDraw() { + this.draw = function pageviewDraw(callback) { if (div.hasChildNodes()) { this.updateStats(); - return false; + callback(); + return; } var canvas = document.createElement('canvas'); @@ -644,13 +774,14 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, this.updateStats(); if (this.onAfterDraw) this.onAfterDraw(); + + cache.push(this); + callback(); }).bind(this), textLayer ); setupAnnotations(this.content, this.scale); div.setAttribute('data-loaded', true); - - return true; }; this.updateStats = function pageViewUpdateStats() { @@ -717,12 +848,16 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { return ctx; } - this.draw = function thumbnailViewDraw() { - if (this.hasImage) + this.draw = function thumbnailViewDraw(callback) { + if (this.hasImage) { + callback(); return; + } var ctx = getPageDrawContext(); - page.startRendering(ctx, function thumbnailViewDrawStartRendering() {}); + page.startRendering(ctx, function thumbnailViewDrawStartRendering() { + callback(); + }); this.hasImage = true; }; @@ -805,8 +940,7 @@ function updateViewarea() { var visiblePages = PDFView.getVisiblePages(); for (var i = 0; i < visiblePages.length; i++) { var page = visiblePages[i]; - if (PDFView.pages[page.id - 1].draw()) - cache.push(page.view); + renderingQueue.enqueueDraw(PDFView.pages[page.id - 1]); } if (!visiblePages.length) @@ -826,6 +960,14 @@ function updateViewarea() { var topLeft = currentPage.getPagePoint(window.pageXOffset, window.pageYOffset - firstPage.y - kViewerTopMargin); pdfOpenParams += ',' + Math.round(topLeft.x) + ',' + Math.round(topLeft.y); + + var store = PDFView.store; + store.set('exists', true); + store.set('page', pageNumber); + store.set('zoom', Math.round(PDFView.currentScale * 100)); + store.set('scrollLeft', Math.round(topLeft.x)); + store.set('scrollTop', Math.round(topLeft.y)); + document.getElementById('viewBookmark').href = pdfOpenParams; } @@ -848,7 +990,7 @@ function updateThumbViewArea() { var visibleThumbs = PDFView.getVisibleThumbs(); for (var i = 0; i < visibleThumbs.length; i++) { var thumb = visibleThumbs[i]; - PDFView.thumbnails[thumb.id - 1].draw(); + renderingQueue.enqueueDraw(PDFView.thumbnails[thumb.id - 1]); } }, delay); } @@ -888,7 +1030,6 @@ window.addEventListener('change', function webViewerChange(evt) { // implemented in Firefox. var file = files[0]; fileReader.readAsBinaryString(file); - document.title = file.name; // URL does not reflect proper document location - hiding some icons.