From ada3f333754771da8fdff6c4d0378fab971a1791 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 19 Jan 2012 19:25:29 +0100 Subject: [PATCH 01/32] Replace some setScale by parseScale calls to update value --- web/viewer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/viewer.js b/web/viewer.js index ac3fbff0c..5da8b8314 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -203,12 +203,12 @@ var PDFView = { zoomIn: function pdfViewZoomIn() { var newScale = Math.min(kMaxScale, this.currentScale * kDefaultScaleDelta); - this.setScale(newScale, true); + this.parseScale(newScale, true); }, zoomOut: function pdfViewZoomOut() { var newScale = Math.max(kMinScale, this.currentScale / kDefaultScaleDelta); - this.setScale(newScale, true); + this.parseScale(newScale, true); }, set page(val) { @@ -1234,7 +1234,7 @@ window.addEventListener('keydown', function keydown(evt) { handled = true; break; case 48: // '0' - PDFView.setScale(kDefaultScale, true); + PDFView.parseScale(kDefaultScale, true); handled = true; break; case 37: // left arrow From edc632e4696c460ea4f76b079187177f2d91c3cd Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 20 Jan 2012 14:48:57 -0800 Subject: [PATCH 02/32] Fix innerHtml warnings. Remove compatibility.js from the extension. --- Makefile | 7 +++---- web/viewer.js | 20 ++++++++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index c9de61c1c..fe5a3fc42 100644 --- a/Makefile +++ b/Makefile @@ -207,9 +207,8 @@ pages-repo: | $(BUILD_DIR) # copy of the pdf.js source. CONTENT_DIR := content BUILD_NUMBER := `git log --format=oneline $(EXTENSION_BASE_VERSION).. | wc -l | awk '{print $$1}'` -PDF_WEB_FILES = \ +EXTENSION_WEB_FILES = \ web/images \ - web/compatibility.js \ web/viewer.css \ web/viewer.js \ web/viewer-production.html \ @@ -249,7 +248,7 @@ extension: | production @cd extensions/firefox; cp -r $(FIREFOX_EXTENSION_FILES_TO_COPY) ../../$(FIREFOX_BUILD_DIR)/ # Copy a standalone version of pdf.js inside the content directory @cp $(BUILD_TARGET) $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/ - @cp -r $(PDF_WEB_FILES) $(FIREFOX_BUILD_CONTENT)/web/ + @cp -r $(EXTENSION_WEB_FILES) $(FIREFOX_BUILD_CONTENT)/web/ @mv -f $(FIREFOX_BUILD_CONTENT)/web/viewer-production.html $(FIREFOX_BUILD_CONTENT)/web/viewer.html # Update the build version number @sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/install.rdf @@ -272,7 +271,7 @@ extension: | production @cp -R $(CHROME_EXTENSION_FILES) $(CHROME_BUILD_DIR)/ # Copy a standalone version of pdf.js inside the content directory @cp $(BUILD_TARGET) $(CHROME_BUILD_CONTENT)/$(BUILD_DIR)/ - @cp -r $(PDF_WEB_FILES) $(CHROME_BUILD_CONTENT)/web/ + @cp -r $(EXTENSION_WEB_FILES) $(CHROME_BUILD_CONTENT)/web/ @mv -f $(CHROME_BUILD_CONTENT)/web/viewer-production.html $(CHROME_BUILD_CONTENT)/web/viewer.html # Create the crx diff --git a/web/viewer.js b/web/viewer.js index ac3fbff0c..6c72df3d0 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -257,7 +257,7 @@ var PDFView = { }, error: function getPdfError(e) { var loadingIndicator = document.getElementById('loading'); - loadingIndicator.innerHTML = 'Error'; + loadingIndicator.textContent = 'Error'; var moreInfo = { message: 'Unexpected server response of ' + e.target.status + '.' }; @@ -327,7 +327,7 @@ var PDFView = { errorWrapper.removeAttribute('hidden'); var errorMessage = document.getElementById('errorMessage'); - errorMessage.innerHTML = message; + errorMessage.textContent = message; var closeButton = document.getElementById('errorClose'); closeButton.onclick = function() { @@ -362,7 +362,7 @@ var PDFView = { progress: function pdfViewProgress(level) { var percent = Math.round(level * 100); var loadingIndicator = document.getElementById('loading'); - loadingIndicator.innerHTML = 'Loading... ' + percent + '%'; + loadingIndicator.textContent = 'Loading... ' + percent + '%'; }, load: function pdfViewLoad(data, scale) { @@ -402,7 +402,7 @@ var PDFView = { var pagesCount = pdf.numPages; var id = pdf.fingerprint; var storedHash = null; - document.getElementById('numPages').innerHTML = pagesCount; + document.getElementById('numPages').textContent = pagesCount; document.getElementById('pageNumber').max = pagesCount; PDFView.documentFingerprint = id; var store = PDFView.store = new Settings(id); @@ -648,7 +648,15 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, if (!item.content) { content.setAttribute('hidden', true); } else { - text.innerHTML = item.content.replace('\n', '
'); + var e = document.createElement('span'); + var lines = item.content.split('\n'); + for (var i = 0, ii = lines.length; i < ii; ++i) { + var line = lines[i]; + e.appendChild(document.createTextNode(line)); + if (i < (ii - 1)) + e.appendChild(document.createElement('br')); + } + text.appendChild(e); image.addEventListener('mouseover', function annotationImageOver() { this.nextSibling.removeAttribute('hidden'); }, false); @@ -822,7 +830,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, var t1 = stats.compile, t2 = stats.fonts, t3 = stats.render; var str = 'Time to compile/fonts/render: ' + (t1 - stats.begin) + '/' + (t2 - t1) + '/' + (t3 - t2) + ' ms'; - document.getElementById('info').innerHTML = str; + document.getElementById('info').textContent = str; }; }; From c6662d12e1bc354ace66c4650160d05b4b22cfa8 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Sat, 21 Jan 2012 17:18:36 -0600 Subject: [PATCH 03/32] Changing glyphNameMap and GlyphUnicode lookup order --- src/fonts.js | 5 ++--- test/pdfs/issue1096.pdf.link | 1 + test/test_manifest.json | 8 ++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 test/pdfs/issue1096.pdf.link diff --git a/src/fonts.js b/src/fonts.js index f96c15458..adcedd55c 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -2235,9 +2235,8 @@ var Font = (function FontClosure() { } // MacRoman encoding address by re-encoding the cmap table - unicode = glyphName in GlyphsUnicode ? - GlyphsUnicode[glyphName] : - this.glyphNameMap[glyphName]; + unicode = glyphName in this.glyphNameMap ? + this.glyphNameMap[glyphName] : GlyphsUnicode[glyphName]; break; default: warn('Unsupported font type: ' + this.type); diff --git a/test/pdfs/issue1096.pdf.link b/test/pdfs/issue1096.pdf.link new file mode 100644 index 000000000..aa07f14dd --- /dev/null +++ b/test/pdfs/issue1096.pdf.link @@ -0,0 +1 @@ +http://www.faithaliveresources.org/Content/Site135/FilesSamples/105315400440pdf_00000009843.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index 5bc344abf..648d1b49b 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -402,6 +402,14 @@ "link": true, "type": "eq" }, + { "id": "issue1096", + "file": "pdfs/issue1096.pdf", + "md5": "7f75d2b4b93c78d401ff39e8c1b00612", + "rounds": 1, + "pageLimit": 10, + "link": true, + "type": "eq" + }, { "id": "liveprogramming", "file": "pdfs/liveprogramming.pdf", "md5": "7bd4dad1188232ef597d36fd72c33e52", From 178b89342af2185de7771fba8112cb92cd6dfaa9 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Mon, 23 Jan 2012 16:50:45 -0800 Subject: [PATCH 04/32] Switch to stream converter for extension. --- Makefile | 14 ++- extensions/firefox/bootstrap.js | 3 - extensions/firefox/chrome.manifest | 4 +- .../firefox/components/pdfContentHandler.js | 93 ++++++++++++------- src/core.js | 20 +++- web/viewer-snippet-firefox-extension.html | 14 +++ web/viewer.html | 4 +- web/viewer.js | 35 ++----- 8 files changed, 115 insertions(+), 72 deletions(-) create mode 100644 web/viewer-snippet-firefox-extension.html diff --git a/Makefile b/Makefile index fe5a3fc42..eaaa3e81f 100644 --- a/Makefile +++ b/Makefile @@ -211,6 +211,7 @@ EXTENSION_WEB_FILES = \ web/images \ web/viewer.css \ web/viewer.js \ + web/viewer.html \ web/viewer-production.html \ $(NULL) @@ -249,7 +250,18 @@ extension: | production # Copy a standalone version of pdf.js inside the content directory @cp $(BUILD_TARGET) $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/ @cp -r $(EXTENSION_WEB_FILES) $(FIREFOX_BUILD_CONTENT)/web/ - @mv -f $(FIREFOX_BUILD_CONTENT)/web/viewer-production.html $(FIREFOX_BUILD_CONTENT)/web/viewer.html + @rm $(FIREFOX_BUILD_CONTENT)/web/viewer-production.html + # Copy over the firefox extension snippet so we can inline pdf.js in it + cp web/viewer-snippet-firefox-extension.html $(FIREFOX_BUILD_CONTENT)/web/ + # Modify the viewer so it does all the extension only stuff. + cd $(FIREFOX_BUILD_CONTENT)/web; \ + sed -i.bak '/PDFJSSCRIPT_INCLUDE_BUNDLE/ r ../build/pdf.js' viewer-snippet-firefox-extension.html; \ + sed -i.bak '/PDFJSSCRIPT_REMOVE/d' viewer.html; \ + sed -i.bak '/PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION/d' viewer.html; \ + sed -i.bak '/PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION/ r viewer-snippet-firefox-extension.html' viewer.html; \ + rm -f *.bak; + # We don't need pdf.js anymore since its inlined + rm -Rf $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/; # Update the build version number @sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/install.rdf @sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/update.rdf diff --git a/extensions/firefox/bootstrap.js b/extensions/firefox/bootstrap.js index e51df28f8..bbc53195e 100644 --- a/extensions/firefox/bootstrap.js +++ b/extensions/firefox/bootstrap.js @@ -34,13 +34,10 @@ function shutdown(aData, aReason) { } function install(aData, aReason) { - let url = 'chrome://pdf.js/content/web/viewer.html?file=%s'; - Services.prefs.setCharPref('extensions.pdf.js.url', url); Services.prefs.setBoolPref('extensions.pdf.js.active', false); } function uninstall(aData, aReason) { - Services.prefs.clearUserPref('extensions.pdf.js.url'); Services.prefs.clearUserPref('extensions.pdf.js.active'); } diff --git a/extensions/firefox/chrome.manifest b/extensions/firefox/chrome.manifest index d7db20b38..ec7c9a964 100644 --- a/extensions/firefox/chrome.manifest +++ b/extensions/firefox/chrome.manifest @@ -1,5 +1,5 @@ -content pdf.js content/ +resource pdf.js content/ component {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a} components/pdfContentHandler.js -contract @mozilla.org/uriloader/content-handler;1?type=application/pdf {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a} +contract @mozilla.org/streamconv;1?from=application/pdf&to=*/* {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a} diff --git a/extensions/firefox/components/pdfContentHandler.js b/extensions/firefox/components/pdfContentHandler.js index 67459b759..fa9f329fc 100644 --- a/extensions/firefox/components/pdfContentHandler.js +++ b/extensions/firefox/components/pdfContentHandler.js @@ -21,47 +21,74 @@ function log(aMsg) { } const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001; + function pdfContentHandler() { -} +}; pdfContentHandler.prototype = { - handleContent: function handleContent(aMimetype, aContext, aRequest) { - if (aMimetype != PDF_CONTENT_TYPE) - throw NS_ERROR_WONT_HANDLE_CONTENT; - if (!(aRequest instanceof Ci.nsIChannel)) - throw NS_ERROR_WONT_HANDLE_CONTENT; + // properties required for XPCOM registration: + classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'), + classDescription: 'pdf.js Component', + contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*', + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsISupports, + Ci.nsIStreamConverter, + Ci.nsIStreamListener, + Ci.nsIRequestObserver + ]), - if (!Services.prefs.getBoolPref('extensions.pdf.js.active')) - throw NS_ERROR_WONT_HANDLE_CONTENT; + /* + * This component works as such: + * 1. asyncConvertData stores the listener + * 2. onStartRequest creates a new channel, streams the viewer and cancels + * the request so pdf.js can do the request + * Since the request is cancelled onDataAvailable should not be called. The + * onStopRequest does nothing. The convert function just returns the stream, + * it's just the synchronous version of asyncConvertData. + */ - let window = null; - let callbacks = aRequest.notificationCallbacks || - aRequest.loadGroup.notificationCallbacks; - if (!callbacks) - return; - - window = callbacks.getInterface(Ci.nsIDOMWindow); - - let url = null; - try { - url = Services.prefs.getCharPref('extensions.pdf.js.url'); - } catch (e) { - log('Error retrieving the pdf.js base url - ' + e); - throw NS_ERROR_WONT_HANDLE_CONTENT; - } - - let targetUrl = aRequest.URI.spec; - if (targetUrl.indexOf('#pdfjs.action=download') >= 0) - throw NS_ERROR_WONT_HANDLE_CONTENT; - - aRequest.cancel(Cr.NS_BINDING_ABORTED); - window.location = url.replace('%s', encodeURIComponent(targetUrl)); + // nsIStreamConverter::convert + convert: function (aFromStream, aFromType, aToType, aCtxt) { + return aFromStream; }, - classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'), - QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler]) + // nsIStreamConverter::asyncConvertData + asyncConvertData: function (aFromType, aToType, aListener, aCtxt) { + // Store the listener passed to us + this.listener = aListener; + }, + + // nsIStreamListener::onDataAvailable + onDataAvailable: function (aRequest, aContext, aInputStream, aOffset, aCount) { + // Do nothing since all the data loading is handled by the viewer. + log("SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!"); + }, + + // nsIRequestObserver::onStartRequest + onStartRequest: function (aRequest, aContext) { + // Setup the request so we can use it below. + aRequest.QueryInterface(Ci.nsIChannel); + + // Create a new channel that is viewer loaded as a resource. + var ioService = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + var channel = ioService.newChannel( + 'resource://pdf.js/web/viewer.html', null, null); + // Keep the URL the same so the browser sees it as the same. + channel.originalURI = aRequest.originalURI; + channel.asyncOpen(this.listener, aContext); + + // Cancel the request so the viewer can handle it. + aRequest.cancel(Cr.NS_BINDING_ABORTED); + }, + + // nsIRequestObserver::onStopRequest + onStopRequest: function (aRequest, aContext, aStatusCode) { + // Do nothing. + return; + } }; var NSGetFactory = XPCOMUtils.generateNSGetFactory([pdfContentHandler]); - diff --git a/src/core.js b/src/core.js index 7a9f3ee03..3e3d991a9 100644 --- a/src/core.js +++ b/src/core.js @@ -624,9 +624,19 @@ var PDFDoc = (function PDFDocClosure() { } try { - // Some versions of FF can't create a worker on localhost, see: - // https://bugzilla.mozilla.org/show_bug.cgi?id=683280 - var worker = new Worker(workerSrc); + var worker; + if (PDFJS.isFirefoxExtension) { + // The firefox extension can't load the worker from the resource:// + // url so we have to inline the script and then use the blob loader. + var bb = new MozBlobBuilder(); + bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent); + var blobUrl = window.URL.createObjectURL(bb.getBlob()); + worker = new Worker(blobUrl); + } else { + // Some versions of FF can't create a worker on localhost, see: + // https://bugzilla.mozilla.org/show_bug.cgi?id=683280 + worker = new Worker(workerSrc); + } var messageHandler = new MessageHandler('main', worker); @@ -645,7 +655,9 @@ var PDFDoc = (function PDFDocClosure() { // serializing the typed array. messageHandler.send('test', testObj); return; - } catch (e) {} + } catch (e) { + warn('The worker has been disabled.') + } } // Either workers are disabled, not supported or have thrown an exception. // Thus, we fallback to a faked worker. diff --git a/web/viewer-snippet-firefox-extension.html b/web/viewer-snippet-firefox-extension.html new file mode 100644 index 000000000..a3d3502a8 --- /dev/null +++ b/web/viewer-snippet-firefox-extension.html @@ -0,0 +1,14 @@ + + + + diff --git a/web/viewer.html b/web/viewer.html index 40e99004f..f395292f9 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -2,9 +2,11 @@ Simple pdf.js page viewer + + - + diff --git a/web/viewer.js b/web/viewer.js index 6c72df3d0..f65c75434 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -73,23 +73,11 @@ var Settings = (function SettingsClosure() { } 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) + if (isLocalStorageEnabled) database = localStorage.getItem('database') || '{}'; else return false; @@ -110,31 +98,20 @@ var Settings = (function SettingsClosure() { 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) + 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) + 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 this.file[name] || defaultValue; } }; @@ -1011,7 +988,9 @@ window.addEventListener('load', function webViewerLoad(evt) { } var scale = ('scale' in params) ? params.scale : 0; - PDFView.open(params.file || kDefaultURL, parseFloat(scale)); + var file = PDFJS.isFirefoxExtension ? + window.location.toString() : params.file || kDefaultURL; + PDFView.open(file, parseFloat(scale)); if (!window.File || !window.FileReader || !window.FileList || !window.Blob) document.getElementById('fileInput').setAttribute('hidden', 'true'); From 4d3057aba747ffc2cc6ca54ca475504aac57608e Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Mon, 23 Jan 2012 17:52:53 -0800 Subject: [PATCH 05/32] Fix lint. --- .../firefox/components/pdfContentHandler.js | 18 +++++++++--------- src/core.js | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/extensions/firefox/components/pdfContentHandler.js b/extensions/firefox/components/pdfContentHandler.js index fa9f329fc..fc42b9260 100644 --- a/extensions/firefox/components/pdfContentHandler.js +++ b/extensions/firefox/components/pdfContentHandler.js @@ -23,7 +23,7 @@ function log(aMsg) { const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001; function pdfContentHandler() { -}; +} pdfContentHandler.prototype = { @@ -31,7 +31,7 @@ pdfContentHandler.prototype = { classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'), classDescription: 'pdf.js Component', contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*', - + QueryInterface: XPCOMUtils.generateQI([ Ci.nsISupports, Ci.nsIStreamConverter, @@ -50,29 +50,29 @@ pdfContentHandler.prototype = { */ // nsIStreamConverter::convert - convert: function (aFromStream, aFromType, aToType, aCtxt) { + convert: function(aFromStream, aFromType, aToType, aCtxt) { return aFromStream; }, // nsIStreamConverter::asyncConvertData - asyncConvertData: function (aFromType, aToType, aListener, aCtxt) { + asyncConvertData: function(aFromType, aToType, aListener, aCtxt) { // Store the listener passed to us this.listener = aListener; }, // nsIStreamListener::onDataAvailable - onDataAvailable: function (aRequest, aContext, aInputStream, aOffset, aCount) { + onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) { // Do nothing since all the data loading is handled by the viewer. - log("SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!"); + log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!'); }, // nsIRequestObserver::onStartRequest - onStartRequest: function (aRequest, aContext) { + onStartRequest: function(aRequest, aContext) { // Setup the request so we can use it below. aRequest.QueryInterface(Ci.nsIChannel); // Create a new channel that is viewer loaded as a resource. - var ioService = Cc["@mozilla.org/network/io-service;1"] + var ioService = Cc['@mozilla.org/network/io-service;1'] .getService(Ci.nsIIOService); var channel = ioService.newChannel( 'resource://pdf.js/web/viewer.html', null, null); @@ -85,7 +85,7 @@ pdfContentHandler.prototype = { }, // nsIRequestObserver::onStopRequest - onStopRequest: function (aRequest, aContext, aStatusCode) { + onStopRequest: function(aRequest, aContext, aStatusCode) { // Do nothing. return; } diff --git a/src/core.js b/src/core.js index 3e3d991a9..93dde00ed 100644 --- a/src/core.js +++ b/src/core.js @@ -656,7 +656,7 @@ var PDFDoc = (function PDFDocClosure() { messageHandler.send('test', testObj); return; } catch (e) { - warn('The worker has been disabled.') + warn('The worker has been disabled.'); } } // Either workers are disabled, not supported or have thrown an exception. From 66e3441e0eccc2bea2d608fd02f1c4795f1c02f6 Mon Sep 17 00:00:00 2001 From: Kalervo Kujala Date: Tue, 24 Jan 2012 22:04:59 +0200 Subject: [PATCH 06/32] Change throws to errors. --- src/canvas.js | 4 ++-- src/core.js | 10 +++++----- src/jpx.js | 16 ++++++++-------- src/obj.js | 4 ++-- src/stream.js | 22 +++++++++++++--------- src/util.js | 16 ++++++++-------- src/worker.js | 8 ++++---- 7 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index 5ef900861..d0b0064f6 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -548,7 +548,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var fontObj = this.objs.get(fontRefName).fontObj; if (!fontObj) { - throw 'Can\'t find font for ' + fontRefName; + error('Can\'t find font for ' + fontRefName); } var name = fontObj.loadedName || 'sans-serif'; @@ -866,7 +866,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') { var pattern = Pattern.shadingFromIR(this.ctx, IR); } else { - throw 'Unkown IR type'; + error('Unkown IR type ' + IR[0]); } return pattern; }, diff --git a/src/core.js b/src/core.js index 7a9f3ee03..df38c086f 100644 --- a/src/core.js +++ b/src/core.js @@ -410,14 +410,14 @@ var Page = (function PageClosure() { if (callback) callback(e); else - throw e; + error(e); } }.bind(this), function pageDisplayReadPromiseError(reason) { if (callback) callback(reason); else - throw reason; + error(reason); } ); } @@ -620,7 +620,7 @@ var PDFDoc = (function PDFDocClosure() { if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') { var workerSrc = PDFJS.workerSrc; if (typeof workerSrc === 'undefined') { - throw 'No PDFJS.workerSrc specified'; + error('No PDFJS.workerSrc specified'); } try { @@ -716,7 +716,7 @@ var PDFDoc = (function PDFDocClosure() { }); break; default: - throw 'Got unkown object type ' + type; + error('Got unkown object type ' + type); } }, this); @@ -737,7 +737,7 @@ var PDFDoc = (function PDFDocClosure() { if (page.displayReadyPromise) page.displayReadyPromise.reject(data.error); else - throw data.error; + error(data.error); }, this); messageHandler.on('jpeg_decode', function(data, promise) { diff --git a/src/jpx.js b/src/jpx.js index 61a8f4487..c212c6fd0 100644 --- a/src/jpx.js +++ b/src/jpx.js @@ -1052,7 +1052,7 @@ var JpxImage = (function JpxImageClosure() { } r = 0; } - throw 'Out of packets'; + error('Out of packets'); }; } function ResolutionLayerComponentPositionIterator(context) { @@ -1091,7 +1091,7 @@ var JpxImage = (function JpxImageClosure() { } l = 0; } - throw 'Out of packets'; + error('Out of packets'); }; } function buildPackets(context) { @@ -1187,7 +1187,7 @@ var JpxImage = (function JpxImageClosure() { new ResolutionLayerComponentPositionIterator(context); break; default: - throw 'Unsupported progression order'; + error('Unsupported progression order ' + progressionOrder); } } function parseTilePackets(context, data, offset, dataLength) { @@ -1589,7 +1589,7 @@ var JpxImage = (function JpxImageClosure() { if (lbox == 0) lbox = length - position + headerSize; if (lbox < headerSize) - throw 'Invalid box field size'; + error('Invalid box field size'); var dataLength = lbox - headerSize; var jumpDataLength = true; switch (tbox) { @@ -1675,7 +1675,7 @@ var JpxImage = (function JpxImageClosure() { scalarExpounded = true; break; default: - throw 'Invalid SQcd value'; + error('Invalid SQcd value ' + sqcd); } qcd.noQuantization = spqcdSize == 8; qcd.scalarExpounded = scalarExpounded; @@ -1728,7 +1728,7 @@ var JpxImage = (function JpxImageClosure() { scalarExpounded = true; break; default: - throw 'Invalid SQcd value'; + error('Invalid SQcd value ' + sqcd); } qcc.noQuantization = spqcdSize == 8; qcc.scalarExpounded = scalarExpounded; @@ -1795,7 +1795,7 @@ var JpxImage = (function JpxImageClosure() { cod.terminationOnEachCodingPass || cod.verticalyStripe || cod.predictableTermination || cod.segmentationSymbolUsed) - throw 'Unsupported COD options: ' + uneval(cod); + error('Unsupported COD options: ' + uneval(cod)); if (context.mainHeader) context.COD = cod; @@ -1840,7 +1840,7 @@ var JpxImage = (function JpxImageClosure() { // skipping content break; default: - throw 'Unknown codestream code: ' + code.toString(16); + error('Unknown codestream code: ' + code.toString(16)); } position += length; } diff --git a/src/obj.js b/src/obj.js index ef7932546..5b87fec13 100644 --- a/src/obj.js +++ b/src/obj.js @@ -574,7 +574,7 @@ var XRef = (function XRefClosure() { var stream, parser; if (e.uncompressed) { if (e.gen != gen) - throw ('inconsistent generation in XRef'); + error('inconsistent generation in XRef'); stream = this.stream.makeSubStream(e.offset); parser = new Parser(new Lexer(stream), true, this); var obj1 = parser.getObj(); @@ -703,7 +703,7 @@ var PDFObjects = (function PDFObjectsClosure() { // If there isn't an object yet or the object isn't resolved, then the // data isn't ready yet! if (!obj || !obj.isResolved) { - throw 'Requesting object that isn\'t resolved yet ' + objId; + error('Requesting object that isn\'t resolved yet ' + objId); return null; } else { return obj.data; diff --git a/src/stream.js b/src/stream.js index 610a54d38..c7b7c83e6 100644 --- a/src/stream.js +++ b/src/stream.js @@ -821,15 +821,19 @@ var JpegStream = (function JpegStreamClosure() { JpegStream.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) { if (this.bufferLength) return; - var jpegImage = new JpegImage(); - if (this.colorTransform != -1) - jpegImage.colorTransform = this.colorTransform; - jpegImage.parse(this.bytes); - var width = jpegImage.width; - var height = jpegImage.height; - var data = jpegImage.getData(width, height); - this.buffer = data; - this.bufferLength = data.length; + try { + var jpegImage = new JpegImage(); + if (this.colorTransform != -1) + jpegImage.colorTransform = this.colorTransform; + jpegImage.parse(this.bytes); + var width = jpegImage.width; + var height = jpegImage.height; + var data = jpegImage.getData(width, height); + this.buffer = data; + this.bufferLength = data.length; + } catch (e) { + error(e); + } }; JpegStream.prototype.getIR = function jpegStreamGetIR() { return bytesToString(this.bytes); diff --git a/src/util.js b/src/util.js index 99b422296..759908e9e 100644 --- a/src/util.js +++ b/src/util.js @@ -255,8 +255,8 @@ var Promise = (function PromiseClosure() { return; } if (this._data !== EMPTY_PROMISE) { - throw 'Promise ' + this.name + - ': Cannot set the data of a promise twice'; + error('Promise ' + this.name + + ': Cannot set the data of a promise twice'); } this._data = value; this.hasData = true; @@ -268,7 +268,7 @@ var Promise = (function PromiseClosure() { get data() { if (this._data === EMPTY_PROMISE) { - throw 'Promise ' + this.name + ': Cannot get data that isn\'t set'; + error('Promise ' + this.name + ': Cannot get data that isn\'t set'); } return this._data; }, @@ -283,10 +283,10 @@ var Promise = (function PromiseClosure() { resolve: function promiseResolve(data) { if (this.isResolved) { - throw 'A Promise can be resolved only once ' + this.name; + error('A Promise can be resolved only once ' + this.name); } if (this.isRejected) { - throw 'The Promise was already rejected ' + this.name; + error('The Promise was already rejected ' + this.name); } this.isResolved = true; @@ -300,10 +300,10 @@ var Promise = (function PromiseClosure() { reject: function proimseReject(reason) { if (this.isRejected) { - throw 'A Promise can be rejected only once ' + this.name; + error('A Promise can be rejected only once ' + this.name); } if (this.isResolved) { - throw 'The Promise was already resolved ' + this.name; + error('The Promise was already resolved ' + this.name); } this.isRejected = true; @@ -317,7 +317,7 @@ var Promise = (function PromiseClosure() { then: function promiseThen(callback, errback) { if (!callback) { - throw 'Requiring callback' + this.name; + error('Requiring callback' + this.name); } // If the promise is already resolved, call the callback directly. diff --git a/src/worker.js b/src/worker.js index 4d9dd1bb6..f9777d7df 100644 --- a/src/worker.js +++ b/src/worker.js @@ -26,7 +26,7 @@ function MessageHandler(name, comObj) { delete callbacks[callbackId]; callback(data.data); } else { - throw 'Cannot resolve callback ' + callbackId; + error('Cannot resolve callback ' + callbackId); } } else if (data.action in ah) { var action = ah[data.action]; @@ -44,7 +44,7 @@ function MessageHandler(name, comObj) { action[0].call(action[1], data.data); } } else { - throw 'Unkown action from worker: ' + data.action; + error('Unkown action from worker: ' + data.action); } }; } @@ -53,7 +53,7 @@ MessageHandler.prototype = { on: function messageHandlerOn(actionName, handler, scope) { var ah = this.actionHandler; if (ah[actionName]) { - throw 'There is already an actionName called "' + actionName + '"'; + error('There is already an actionName called "' + actionName + '"'); } ah[actionName] = [handler, scope]; }, @@ -217,7 +217,7 @@ var workerConsole = { timeEnd: function timeEnd(name) { var time = consoleTimer[name]; if (time == null) { - throw 'Unkown timer name ' + name; + error('Unkown timer name ' + name); } this.log('Timer:', name, Date.now() - time); } From 19622406da911244d88ca15a073c185d0d83612d Mon Sep 17 00:00:00 2001 From: Jeff Wagner Date: Tue, 24 Jan 2012 12:19:52 -0800 Subject: [PATCH 07/32] define console for IE9 when debugging tools is not opened --- web/compatibility.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/compatibility.js b/web/compatibility.js index 26405ad8f..7addbe3e2 100644 --- a/web/compatibility.js +++ b/web/compatibility.js @@ -224,3 +224,10 @@ } }); })(); + +//IE9 console +(function checkConsoleCompatibility() { + if (typeof console == "undefined") { + console = {log: function() {}}; + } +})(); From 08f8aed5215d4ba861d4675f91aabb9bffa5997f Mon Sep 17 00:00:00 2001 From: Jeff Wagner Date: Tue, 24 Jan 2012 14:19:41 -0800 Subject: [PATCH 08/32] define console for IE9; updated to fix lint errors and comment --- web/compatibility.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/compatibility.js b/web/compatibility.js index 7addbe3e2..b22153516 100644 --- a/web/compatibility.js +++ b/web/compatibility.js @@ -225,9 +225,9 @@ }); })(); -//IE9 console +// Check console compatability (function checkConsoleCompatibility() { - if (typeof console == "undefined") { + if (typeof console == 'undefined') { console = {log: function() {}}; } })(); From 23bf35d5ef34039fd181b3f25b973a4d30f1720d Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 24 Jan 2012 14:55:07 -0800 Subject: [PATCH 09/32] Fix anchor links. --- web/viewer.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/viewer.js b/web/viewer.js index f65c75434..1ed9019ff 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -269,15 +269,18 @@ var PDFView = { }, getDestinationHash: function pdfViewGetDestinationHash(dest) { + // We add the full url for the extension so the anchor links don't come up + // as resource:// urls and so open in new tab/window works. + var url = PDFJS.isFirefoxExtension ? this.url.split('#')[0] : ''; if (typeof dest === 'string') - return '#' + escape(dest); + return url + '#' + escape(dest); if (dest instanceof Array) { var destRef = dest[0]; // see navigateTo method for dest format var pageNumber = destRef instanceof Object ? this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1); if (pageNumber) { - var pdfOpenParams = '#page=' + pageNumber; + var pdfOpenParams = url + '#page=' + pageNumber; var destKind = dest[1]; if ('name' in destKind && destKind.name == 'XYZ') { var scale = (dest[4] || this.currentScale); From f3b2a03de63b4216790a00c3ebc95b7e449211e6 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 24 Jan 2012 15:13:50 -0800 Subject: [PATCH 10/32] Hide the browse bar for the ff extension. --- web/viewer.css | 2 +- web/viewer.html | 2 +- web/viewer.js | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/web/viewer.css b/web/viewer.css index e355f7fc2..b9fd3e9e4 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -9,7 +9,7 @@ body { } [hidden] { - display: none; + display: none !important; } /* === Toolbar === */ diff --git a/web/viewer.html b/web/viewer.html index f395292f9..ad9d1189e 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -92,7 +92,7 @@ -
+
Bookmark diff --git a/web/viewer.js b/web/viewer.js index 1ed9019ff..131d12b42 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -995,10 +995,14 @@ window.addEventListener('load', function webViewerLoad(evt) { window.location.toString() : params.file || kDefaultURL; PDFView.open(file, parseFloat(scale)); - if (!window.File || !window.FileReader || !window.FileList || !window.Blob) + if (PDFJS.isFirefoxExtension || !window.File || !window.FileReader || + !window.FileList || !window.Blob) { document.getElementById('fileInput').setAttribute('hidden', 'true'); - else + document.getElementById('fileInputSeperator') + .setAttribute('hidden', 'true'); + } else { document.getElementById('fileInput').value = null; + } if ('disableWorker' in params) PDFJS.disableWorker = (params['disableWorker'] === 'true'); From dd8c39d8e14d283777f8c4e5788ada2c719d9542 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 24 Jan 2012 15:46:53 -0800 Subject: [PATCH 11/32] Fix the bookmark button and redo the anchor prefix. --- web/viewer.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/web/viewer.js b/web/viewer.js index 131d12b42..b784e6f5a 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -269,18 +269,15 @@ var PDFView = { }, getDestinationHash: function pdfViewGetDestinationHash(dest) { - // We add the full url for the extension so the anchor links don't come up - // as resource:// urls and so open in new tab/window works. - var url = PDFJS.isFirefoxExtension ? this.url.split('#')[0] : ''; if (typeof dest === 'string') - return url + '#' + escape(dest); + return PDFView.getAnchorUrl('#' + escape(dest)); if (dest instanceof Array) { var destRef = dest[0]; // see navigateTo method for dest format var pageNumber = destRef instanceof Object ? this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1); if (pageNumber) { - var pdfOpenParams = url + '#page=' + pageNumber; + var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber); var destKind = dest[1]; if ('name' in destKind && destKind.name == 'XYZ') { var scale = (dest[4] || this.currentScale); @@ -295,6 +292,17 @@ var PDFView = { return ''; }, + /** + * For the firefox extension we prefix the full url on anchor links so they + * don't come up as resource:// urls and so open in new tab/window works. + * @param {String} anchor The anchor hash include the #. + */ + getAnchorUrl: function getAnchorUrl(anchor) { + if (PDFJS.isFirefoxExtension) + return this.url.split('#')[0] + anchor; + return anchor; + }, + /** * Show the error box. * @param {String} message A message that is human readable. @@ -1087,8 +1095,8 @@ function updateViewarea() { store.set('zoom', normalizedScaleValue); store.set('scrollLeft', Math.round(topLeft.x)); store.set('scrollTop', Math.round(topLeft.y)); - - document.getElementById('viewBookmark').href = pdfOpenParams; + var href = PDFView.getAnchorUrl(pdfOpenParams); + document.getElementById('viewBookmark').href = href; } window.addEventListener('scroll', function webViewerScroll(evt) { From 858aab008f236f4cfdba79be62d22677b9699e60 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 24 Jan 2012 21:33:03 -0800 Subject: [PATCH 12/32] Fix the download button. --- extensions/firefox/components/pdfContentHandler.js | 14 +++++++++++--- web/viewer.js | 9 ++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/extensions/firefox/components/pdfContentHandler.js b/extensions/firefox/components/pdfContentHandler.js index fc42b9260..320d69d30 100644 --- a/extensions/firefox/components/pdfContentHandler.js +++ b/extensions/firefox/components/pdfContentHandler.js @@ -70,6 +70,17 @@ pdfContentHandler.prototype = { onStartRequest: function(aRequest, aContext) { // Setup the request so we can use it below. aRequest.QueryInterface(Ci.nsIChannel); + // Cancel the request so the viewer can handle it. + aRequest.cancel(Cr.NS_BINDING_ABORTED); + + // Check if we should download. + var targetUrl = aRequest.originalURI.spec; + var downloadHash = targetUrl.indexOf('?#pdfjs.action=download'); + if (downloadHash >= 0) { + targetUrl = targetUrl.substring(0, downloadHash); + Services.wm.getMostRecentWindow("navigator:browser").saveURL(targetUrl); + return; + } // Create a new channel that is viewer loaded as a resource. var ioService = Cc['@mozilla.org/network/io-service;1'] @@ -79,9 +90,6 @@ pdfContentHandler.prototype = { // Keep the URL the same so the browser sees it as the same. channel.originalURI = aRequest.originalURI; channel.asyncOpen(this.listener, aContext); - - // Cancel the request so the viewer can handle it. - aRequest.cancel(Cr.NS_BINDING_ABORTED); }, // nsIRequestObserver::onStopRequest diff --git a/web/viewer.js b/web/viewer.js index b784e6f5a..55d0a595c 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -249,7 +249,14 @@ var PDFView = { }, download: function pdfViewDownload() { - window.open(this.url + '#pdfjs.action=download', '_parent'); + var url = this.url.split('#')[0]; + // For the extension we add an extra '?' to force the page to reload, its + // stripped off by the extension. + if (PDFJS.isFirefoxExtension) + url += '?#pdfjs.action=download'; + else + url += '#pdfjs.action=download', '_parent'; + window.open(url, '_parent'); }, navigateTo: function pdfViewNavigateTo(dest) { From 0d839c1c597b8054cb259d469c02300543023fca Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 25 Jan 2012 17:40:08 -0800 Subject: [PATCH 13/32] Fix how we're storing settings and change how the save pdf works. --- extensions/firefox/bootstrap.js | 152 +++++++++++++++++- .../firefox/components/pdfContentHandler.js | 15 +- web/viewer.js | 49 ++++-- 3 files changed, 195 insertions(+), 21 deletions(-) diff --git a/extensions/firefox/bootstrap.js b/extensions/firefox/bootstrap.js index bbc53195e..06fcf182f 100644 --- a/extensions/firefox/bootstrap.js +++ b/extensions/firefox/bootstrap.js @@ -3,17 +3,155 @@ 'use strict'; +const EXT_PREFIX = 'extensions.uriloader@pdf.js'; +const PDFJS_EVENT_ID = 'pdf.js.message'; let Cc = Components.classes; let Ci = Components.interfaces; let Cm = Components.manager; let Cu = Components.utils; +let application = Cc['@mozilla.org/fuel/application;1'] + .getService(Ci.fuelIApplication); +let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] + .getService(Ci.nsIPrivateBrowsingService); Cu.import('resource://gre/modules/Services.jsm'); function log(str) { dump(str + '\n'); } +// watchWindows() and unload() are from Ed Lee's examples at +// https://github.com/Mardak/restartless/blob/watchWindows/bootstrap.js +/** + * Apply a callback to each open and new browser windows. + * + * @param {function} callback 1-parameter function that gets a browser window. + */ +function watchWindows(callback) { + // Wrap the callback in a function that ignores failures + function watcher(window) { + try { + // Now that the window has loaded, only handle browser windows + let {documentElement} = window.document; + if (documentElement.getAttribute('windowtype') == 'navigator:browser') + callback(window); + } + catch (ex) {} + } + // Wait for the window to finish loading before running the callback + function runOnLoad(window) { + // Listen for one load event before checking the window type + window.addEventListener('load', function runOnce() { + window.removeEventListener('load', runOnce, false); + watcher(window); + }, false); + } + + // Add functionality to existing windows + let windows = Services.wm.getEnumerator(null); + while (windows.hasMoreElements()) { + // Only run the watcher immediately if the window is completely loaded + let window = windows.getNext(); + if (window.document.readyState == 'complete') + watcher(window); + // Wait for the window to load before continuing + else + runOnLoad(window); + } + + // Watch for new browser windows opening then wait for it to load + function windowWatcher(subject, topic) { + if (topic == 'domwindowopened') + runOnLoad(subject); + } + Services.ww.registerNotification(windowWatcher); + + // Make sure to stop watching for windows if we're unloading + unload(function() Services.ww.unregisterNotification(windowWatcher)); +} + +/** + * Save callbacks to run when unloading. Optionally scope the callback to a + * container, e.g., window. Provide a way to run all the callbacks. + * + * @param {function} callback 0-parameter function to call on unload. + * @param {node} container Remove the callback when this container unloads. + * @return {function} A 0-parameter function that undoes adding the callback. + */ +function unload(callback, container) { + // Initialize the array of unloaders on the first usage + let unloaders = unload.unloaders; + if (unloaders == null) + unloaders = unload.unloaders = []; + + // Calling with no arguments runs all the unloader callbacks + if (callback == null) { + unloaders.slice().forEach(function(unloader) unloader()); + unloaders.length = 0; + return; + } + + // The callback is bound to the lifetime of the container if we have one + if (container != null) { + // Remove the unloader when the container unloads + container.addEventListener('unload', removeUnloader, false); + + // Wrap the callback to additionally remove the unload listener + let origCallback = callback; + callback = function() { + container.removeEventListener('unload', removeUnloader, false); + origCallback(); + } + } + + // Wrap the callback in a function that ignores failures + function unloader() { + try { + callback(); + } + catch (ex) {} + } + unloaders.push(unloader); + + // Provide a way to remove the unloader + function removeUnloader() { + let index = unloaders.indexOf(unloader); + if (index != -1) + unloaders.splice(index, 1); + } + return removeUnloader; +} + +function messageCallback(event) { + log(event.target.ownerDocument.currentScript); + var message = event.target, doc = message.ownerDocument; + var inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; + // Verify the message came from a PDF. + // TODO + var action = message.getUserData('action'); + var data = message.getUserData('data'); + switch (action) { + case 'download': + Services.wm.getMostRecentWindow('navigator:browser').saveURL(data); + break; + case 'setDatabase': + if (inPrivateBrowswing) + return; + application.prefs.setValue(EXT_PREFIX + '.database', data); + break; + case 'getDatabase': + var response; + if (inPrivateBrowswing) + response = '{}'; + else + response = application.prefs.getValue(EXT_PREFIX + '.database', '{}'); + message.setUserData('response', response, null); + break; + } +} + + +// All the boostrap functions: function startup(aData, aReason) { let manifestPath = 'chrome.manifest'; let manifest = Cc['@mozilla.org/file/local;1'] @@ -26,11 +164,22 @@ function startup(aData, aReason) { } catch (e) { log(e); } + + watchWindows(function(window) { + window.addEventListener(PDFJS_EVENT_ID, messageCallback, false, true); + unload(function() { + window.removeEventListener(PDFJS_EVENT_ID, messageCallback, false, true); + }); + }); } function shutdown(aData, aReason) { - if (Services.prefs.getBoolPref('extensions.pdf.js.active')) + if (Services.prefs.getBoolPref('extensions.pdf.js.active')) { Services.prefs.setBoolPref('extensions.pdf.js.active', false); + // Clean up with unloaders when we're deactivating + if (aReason != APP_SHUTDOWN) + unload(); + } } function install(aData, aReason) { @@ -39,5 +188,6 @@ function install(aData, aReason) { function uninstall(aData, aReason) { Services.prefs.clearUserPref('extensions.pdf.js.active'); + application.prefs.setValue(EXT_PREFIX + '.database', '{}'); } diff --git a/extensions/firefox/components/pdfContentHandler.js b/extensions/firefox/components/pdfContentHandler.js index 320d69d30..edd8ee3e0 100644 --- a/extensions/firefox/components/pdfContentHandler.js +++ b/extensions/firefox/components/pdfContentHandler.js @@ -20,7 +20,7 @@ function log(aMsg) { dump(msg + '\n'); } -const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001; +const NS_ERROR_NOT_IMPLEMENTED = 0x80004001; function pdfContentHandler() { } @@ -51,11 +51,13 @@ pdfContentHandler.prototype = { // nsIStreamConverter::convert convert: function(aFromStream, aFromType, aToType, aCtxt) { - return aFromStream; + return aFromStream; }, // nsIStreamConverter::asyncConvertData asyncConvertData: function(aFromType, aToType, aListener, aCtxt) { + if (!Services.prefs.getBoolPref('extensions.pdf.js.active')) + throw NS_ERROR_NOT_IMPLEMENTED; // Store the listener passed to us this.listener = aListener; }, @@ -73,15 +75,6 @@ pdfContentHandler.prototype = { // Cancel the request so the viewer can handle it. aRequest.cancel(Cr.NS_BINDING_ABORTED); - // Check if we should download. - var targetUrl = aRequest.originalURI.spec; - var downloadHash = targetUrl.indexOf('?#pdfjs.action=download'); - if (downloadHash >= 0) { - targetUrl = targetUrl.substring(0, downloadHash); - Services.wm.getMostRecentWindow("navigator:browser").saveURL(targetUrl); - return; - } - // Create a new channel that is viewer loaded as a resource. var ioService = Cc['@mozilla.org/network/io-service;1'] .getService(Ci.nsIIOService); diff --git a/web/viewer.js b/web/viewer.js index 55d0a595c..790b5ae63 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -61,6 +61,31 @@ var RenderingQueue = (function RenderingQueueClosure() { return RenderingQueue; })(); +var FirefoxCom = (function FirefoxComClosure() { + return { + /** + * Creates an event that hopefully the extension is listening for and will + * synchronously respond to. + * @param {String} action The action to trigger. + * @param {String} data Optional data to send. + * @return {*} The response. + */ + request: function(action, data) { + var request = document.createTextNode(''); + request.setUserData('action', action, null); + request.setUserData('data', data, null); + document.documentElement.appendChild(request); + + var sender = document.createEvent('Events'); + sender.initEvent('pdf.js.message', true, false); + request.dispatchEvent(sender); + var response = request.getUserData('response'); + document.documentElement.removeChild(request); + return response; + } + }; +})(); + // 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 @@ -74,10 +99,14 @@ var Settings = (function SettingsClosure() { return true; })(); + var isFirefoxExtension = PDFJS.isFirefoxExtension; + function Settings(fingerprint) { var database = null; var index; - if (isLocalStorageEnabled) + if (isFirefoxExtension) + database = FirefoxCom.request('getDatabase', null); + else if (isLocalStorageEnabled) database = localStorage.getItem('database') || '{}'; else return false; @@ -106,8 +135,11 @@ var Settings = (function SettingsClosure() { set: function settingsSet(name, val) { var file = this.file; file[name] = val; - if (isLocalStorageEnabled) - localStorage.setItem('database', JSON.stringify(this.database)); + var database = JSON.stringify(this.database); + if (isFirefoxExtension) + FirefoxCom.request('setDatabase', database); + else if (isLocalStorageEnabled) + localStorage.setItem('database', database); }, get: function settingsGet(name, defaultValue) { @@ -250,13 +282,12 @@ var PDFView = { download: function pdfViewDownload() { var url = this.url.split('#')[0]; - // For the extension we add an extra '?' to force the page to reload, its - // stripped off by the extension. - if (PDFJS.isFirefoxExtension) - url += '?#pdfjs.action=download'; - else + if (PDFJS.isFirefoxExtension) { + FirefoxCom.request('download', url); + } else { url += '#pdfjs.action=download', '_parent'; - window.open(url, '_parent'); + window.open(url, '_parent'); + } }, navigateTo: function pdfViewNavigateTo(dest) { From 2b2e4b19abf2fc5728cf5551455174b8dbb60e22 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Wed, 25 Jan 2012 21:52:10 -0600 Subject: [PATCH 14/32] Fixing initial scale when named destination is specified --- web/viewer.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/web/viewer.js b/web/viewer.js index dcd3f51b6..dd16b0282 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -6,6 +6,7 @@ var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf'; var kDefaultScale = 'auto'; var kDefaultScaleDelta = 1.1; +var kUnknownScale = 0; var kCacheSize = 20; var kCssUnits = 96.0 / 72.0; var kScrollbarPadding = 40; @@ -148,7 +149,7 @@ var currentPageNumber = 1; var PDFView = { pages: [], thumbnails: [], - currentScale: 0, + currentScale: kUnknownScale, currentScaleValue: null, initialBookmark: document.location.hash.substring(1), @@ -452,10 +453,16 @@ var PDFView = { } else if (storedHash) this.setHash(storedHash); - else { - this.parseScale(scale || kDefaultScale, true); + else if (scale) { + this.parseScale(scale, true); this.page = 1; } + + if (PDFView.currentScale === kUnknownScale) { + // Scale was not initialized: invalid bookmark or scale was not specified. + // Setting the default one. + this.parseScale(kDefaultScale, true); + } }, setHash: function pdfViewSetHash(hash) { @@ -742,6 +749,8 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, if (scale && scale !== PDFView.currentScale) PDFView.parseScale(scale, true); + else if (PDFView.currentScale === kUnknownScale) + PDFView.parseScale(kDefaultScale, true); setTimeout(function pageViewScrollIntoViewRelayout() { // letting page to re-layout before scrolling From dd066f8369cad79ca534c077af9f421da3f611e3 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Thu, 26 Jan 2012 18:51:58 -0600 Subject: [PATCH 15/32] Fixing standard encoding mapping --- src/fonts.js | 27 ++++++++++++++------------- test/pdfs/issue1127.pdf.link | 1 + test/test_manifest.json | 7 +++++++ 3 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 test/pdfs/issue1127.pdf.link diff --git a/src/fonts.js b/src/fonts.js index adcedd55c..7f72c8086 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -160,19 +160,20 @@ var Encodings = { 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', 'exclamdown', - 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', - 'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', - 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', 'daggerdbl', - 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', - 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', - 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', - 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', - 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', 'emdash', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', - 'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', - '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', 'lslash', - 'oslash', 'oe', 'germandbls' + 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', + 'fl', '', 'endash', 'dagger', 'daggerdbl', 'periodcentered', '', + 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', + 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', + 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', + 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'AE', '', 'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE', + 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', + 'lslash', 'oslash', 'oe', 'germandbls' ]); }, get WinAnsiEncoding() { diff --git a/test/pdfs/issue1127.pdf.link b/test/pdfs/issue1127.pdf.link new file mode 100644 index 000000000..2df2304ba --- /dev/null +++ b/test/pdfs/issue1127.pdf.link @@ -0,0 +1 @@ +https://vmp.ethz.ch/pdfs/diplome/vordiplome/Block%201/Algorithmen_%26_Komplexitaet/AlgoKo_f08_Aufg.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index 648d1b49b..c6fed0a35 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -410,6 +410,13 @@ "link": true, "type": "eq" }, + { "id": "issue1127", + "file": "pdfs/issue1127.pdf", + "md5": "4fb2be5ffefeafda4ba977de2a1bb4d8", + "rounds": 1, + "link": true, + "type": "eq" + }, { "id": "liveprogramming", "file": "pdfs/liveprogramming.pdf", "md5": "7bd4dad1188232ef597d36fd72c33e52", From e7a0a2e1291fb744cdfa769fa3a8409d246078da Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 27 Jan 2012 10:53:07 -0800 Subject: [PATCH 16/32] Better way to listen to events and verify them. --- extensions/firefox/bootstrap.js | 148 +----------------- .../firefox/components/pdfContentHandler.js | 70 ++++++++- 2 files changed, 68 insertions(+), 150 deletions(-) diff --git a/extensions/firefox/bootstrap.js b/extensions/firefox/bootstrap.js index 06fcf182f..f1a712c0c 100644 --- a/extensions/firefox/bootstrap.js +++ b/extensions/firefox/bootstrap.js @@ -9,149 +9,14 @@ let Cc = Components.classes; let Ci = Components.interfaces; let Cm = Components.manager; let Cu = Components.utils; -let application = Cc['@mozilla.org/fuel/application;1'] - .getService(Ci.fuelIApplication); -let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] - .getService(Ci.nsIPrivateBrowsingService); Cu.import('resource://gre/modules/Services.jsm'); function log(str) { dump(str + '\n'); } -// watchWindows() and unload() are from Ed Lee's examples at -// https://github.com/Mardak/restartless/blob/watchWindows/bootstrap.js -/** - * Apply a callback to each open and new browser windows. - * - * @param {function} callback 1-parameter function that gets a browser window. - */ -function watchWindows(callback) { - // Wrap the callback in a function that ignores failures - function watcher(window) { - try { - // Now that the window has loaded, only handle browser windows - let {documentElement} = window.document; - if (documentElement.getAttribute('windowtype') == 'navigator:browser') - callback(window); - } - catch (ex) {} - } - - // Wait for the window to finish loading before running the callback - function runOnLoad(window) { - // Listen for one load event before checking the window type - window.addEventListener('load', function runOnce() { - window.removeEventListener('load', runOnce, false); - watcher(window); - }, false); - } - - // Add functionality to existing windows - let windows = Services.wm.getEnumerator(null); - while (windows.hasMoreElements()) { - // Only run the watcher immediately if the window is completely loaded - let window = windows.getNext(); - if (window.document.readyState == 'complete') - watcher(window); - // Wait for the window to load before continuing - else - runOnLoad(window); - } - - // Watch for new browser windows opening then wait for it to load - function windowWatcher(subject, topic) { - if (topic == 'domwindowopened') - runOnLoad(subject); - } - Services.ww.registerNotification(windowWatcher); - - // Make sure to stop watching for windows if we're unloading - unload(function() Services.ww.unregisterNotification(windowWatcher)); -} - -/** - * Save callbacks to run when unloading. Optionally scope the callback to a - * container, e.g., window. Provide a way to run all the callbacks. - * - * @param {function} callback 0-parameter function to call on unload. - * @param {node} container Remove the callback when this container unloads. - * @return {function} A 0-parameter function that undoes adding the callback. - */ -function unload(callback, container) { - // Initialize the array of unloaders on the first usage - let unloaders = unload.unloaders; - if (unloaders == null) - unloaders = unload.unloaders = []; - - // Calling with no arguments runs all the unloader callbacks - if (callback == null) { - unloaders.slice().forEach(function(unloader) unloader()); - unloaders.length = 0; - return; - } - - // The callback is bound to the lifetime of the container if we have one - if (container != null) { - // Remove the unloader when the container unloads - container.addEventListener('unload', removeUnloader, false); - - // Wrap the callback to additionally remove the unload listener - let origCallback = callback; - callback = function() { - container.removeEventListener('unload', removeUnloader, false); - origCallback(); - } - } - - // Wrap the callback in a function that ignores failures - function unloader() { - try { - callback(); - } - catch (ex) {} - } - unloaders.push(unloader); - - // Provide a way to remove the unloader - function removeUnloader() { - let index = unloaders.indexOf(unloader); - if (index != -1) - unloaders.splice(index, 1); - } - return removeUnloader; -} - -function messageCallback(event) { - log(event.target.ownerDocument.currentScript); - var message = event.target, doc = message.ownerDocument; - var inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; - // Verify the message came from a PDF. - // TODO - var action = message.getUserData('action'); - var data = message.getUserData('data'); - switch (action) { - case 'download': - Services.wm.getMostRecentWindow('navigator:browser').saveURL(data); - break; - case 'setDatabase': - if (inPrivateBrowswing) - return; - application.prefs.setValue(EXT_PREFIX + '.database', data); - break; - case 'getDatabase': - var response; - if (inPrivateBrowswing) - response = '{}'; - else - response = application.prefs.getValue(EXT_PREFIX + '.database', '{}'); - message.setUserData('response', response, null); - break; - } -} -// All the boostrap functions: function startup(aData, aReason) { let manifestPath = 'chrome.manifest'; let manifest = Cc['@mozilla.org/file/local;1'] @@ -164,22 +29,11 @@ function startup(aData, aReason) { } catch (e) { log(e); } - - watchWindows(function(window) { - window.addEventListener(PDFJS_EVENT_ID, messageCallback, false, true); - unload(function() { - window.removeEventListener(PDFJS_EVENT_ID, messageCallback, false, true); - }); - }); } function shutdown(aData, aReason) { - if (Services.prefs.getBoolPref('extensions.pdf.js.active')) { + if (Services.prefs.getBoolPref('extensions.pdf.js.active')) Services.prefs.setBoolPref('extensions.pdf.js.active', false); - // Clean up with unloaders when we're deactivating - if (aReason != APP_SHUTDOWN) - unload(); - } } function install(aData, aReason) { diff --git a/extensions/firefox/components/pdfContentHandler.js b/extensions/firefox/components/pdfContentHandler.js index edd8ee3e0..1c6c72c81 100644 --- a/extensions/firefox/components/pdfContentHandler.js +++ b/extensions/firefox/components/pdfContentHandler.js @@ -7,8 +7,10 @@ const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; - +const PDFJS_EVENT_ID = 'pdf.js.message'; const PDF_CONTENT_TYPE = 'application/pdf'; +const NS_ERROR_NOT_IMPLEMENTED = 0x80004001; +const EXT_PREFIX = 'extensions.uriloader@pdf.js'; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); @@ -19,8 +21,50 @@ function log(aMsg) { .logStringMessage(msg); dump(msg + '\n'); } +let application = Cc['@mozilla.org/fuel/application;1'] + .getService(Ci.fuelIApplication); +let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] + .getService(Ci.nsIPrivateBrowsingService); +let inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; + +// All the priviledged actions. +function ChromeActions() { + this.inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled; +} +ChromeActions.prototype = { + download: function(data) { + Services.wm.getMostRecentWindow('navigator:browser').saveURL(data); + }, + setDatabase: function() { + if (this.inPrivateBrowswing) + return; + application.prefs.setValue(EXT_PREFIX + '.database', data); + }, + getDatabase: function() { + if (this.inPrivateBrowswing) + return '{}'; + return application.prefs.getValue(EXT_PREFIX + '.database', '{}'); + } +}; + +// Event listener to trigger chrome privedged code. +function RequestListener(actions) { + this.actions = actions; +} +// Recieves an event and synchronously responds. +RequestListener.prototype.recieve = function(event) { + var message = event.target; + var action = message.getUserData('action'); + var data = message.getUserData('data'); + var actions = this.actions; + if (!(action in actions)) { + log('Unknown action: ' + action); + return; + } + var response = actions[action].call(this.actions, data); + message.setUserData('response', response, null); +}; -const NS_ERROR_NOT_IMPLEMENTED = 0x80004001; function pdfContentHandler() { } @@ -70,6 +114,7 @@ pdfContentHandler.prototype = { // nsIRequestObserver::onStartRequest onStartRequest: function(aRequest, aContext) { + // Setup the request so we can use it below. aRequest.QueryInterface(Ci.nsIChannel); // Cancel the request so the viewer can handle it. @@ -80,15 +125,34 @@ pdfContentHandler.prototype = { .getService(Ci.nsIIOService); var channel = ioService.newChannel( 'resource://pdf.js/web/viewer.html', null, null); + // Keep the URL the same so the browser sees it as the same. channel.originalURI = aRequest.originalURI; channel.asyncOpen(this.listener, aContext); + + // Setup a global listener waiting for the next DOM to be created and verfiy + // that its the one we want by its URL. When the correct DOM is found create + // an event listener on that window for the pdf.js events that require + // chrome priviledges. + var url = aRequest.originalURI.spec; + var gb = Services.wm.getMostRecentWindow('navigator:browser'); + var domListener = function domListener(event) { + var doc = event.originalTarget; + var win = doc.defaultView; + if (doc.location.href === url) { + gb.removeEventListener('DOMContentLoaded', domListener); + var requestListener = new RequestListener(new ChromeActions()); + win.addEventListener(PDFJS_EVENT_ID, function(event) { + requestListener.recieve(event); + }, false, true); + } + }; + gb.addEventListener('DOMContentLoaded', domListener, false); }, // nsIRequestObserver::onStopRequest onStopRequest: function(aRequest, aContext, aStatusCode) { // Do nothing. - return; } }; From 337806deed4f75ba7d93bd14963264588c11b72e Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 27 Jan 2012 10:53:37 -0800 Subject: [PATCH 17/32] Fix url for thumbnail. (still not working 100) --- web/viewer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/viewer.js b/web/viewer.js index 790b5ae63..3431e18c4 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -862,7 +862,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { var anchor = document.createElement('a'); - anchor.href = '#' + id; + anchor.href = PDFView.getAnchorUrl('#page=' + id); anchor.onclick = function stopNivigation() { PDFView.page = id; return false; From f46b0474ce4bad106f3c6dd813f23af8459e1479 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 27 Jan 2012 11:02:27 -0800 Subject: [PATCH 18/32] Change name to reflect what it is now. Change GUID. --- extensions/firefox/chrome.manifest | 4 ++-- .../{pdfContentHandler.js => PdfStreamConverter.js} | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) rename extensions/firefox/components/{pdfContentHandler.js => PdfStreamConverter.js} (95%) diff --git a/extensions/firefox/chrome.manifest b/extensions/firefox/chrome.manifest index ec7c9a964..5351257e7 100644 --- a/extensions/firefox/chrome.manifest +++ b/extensions/firefox/chrome.manifest @@ -1,5 +1,5 @@ resource pdf.js content/ -component {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a} components/pdfContentHandler.js -contract @mozilla.org/streamconv;1?from=application/pdf&to=*/* {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a} +component {6457a96b-2d68-439a-bcfa-44465fbcdbb1} components/PdfStreamConverter.js +contract @mozilla.org/streamconv;1?from=application/pdf&to=*/* {6457a96b-2d68-439a-bcfa-44465fbcdbb1} diff --git a/extensions/firefox/components/pdfContentHandler.js b/extensions/firefox/components/PdfStreamConverter.js similarity index 95% rename from extensions/firefox/components/pdfContentHandler.js rename to extensions/firefox/components/PdfStreamConverter.js index 1c6c72c81..83c930d51 100644 --- a/extensions/firefox/components/pdfContentHandler.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -16,7 +16,7 @@ Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); function log(aMsg) { - let msg = 'pdfContentHandler.js: ' + (aMsg.join ? aMsg.join('') : aMsg); + let msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg); Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService) .logStringMessage(msg); dump(msg + '\n'); @@ -66,13 +66,13 @@ RequestListener.prototype.recieve = function(event) { }; -function pdfContentHandler() { +function PdfStreamConverter() { } -pdfContentHandler.prototype = { +PdfStreamConverter.prototype = { // properties required for XPCOM registration: - classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'), + classID: Components.ID('{6457a96b-2d68-439a-bcfa-44465fbcdbb1}'), classDescription: 'pdf.js Component', contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*', @@ -156,4 +156,4 @@ pdfContentHandler.prototype = { } }; -var NSGetFactory = XPCOMUtils.generateNSGetFactory([pdfContentHandler]); +var NSGetFactory = XPCOMUtils.generateNSGetFactory([PdfStreamConverter]); From 10a0a60f8e7ae231a44a91a7b1df9ab1ff166be8 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Fri, 27 Jan 2012 18:53:05 -0600 Subject: [PATCH 19/32] Fixing symbols encoding --- src/evaluator.js | 9 ++++++--- src/fonts.js | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/evaluator.js b/src/evaluator.js index 21530f42f..c70013d25 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -481,8 +481,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { properties.cidToGidMap = this.readCidToGidMap(cidToGidMap); } + var flags = properties.flags; var differences = []; - var baseEncoding = Encodings.StandardEncoding; + var baseEncoding = !!(flags & FontFlags.Symbolic) ? + Encodings.symbolsEncoding : Encodings.StandardEncoding; var hasEncoding = dict.has('Encoding'); if (hasEncoding) { var encoding = xref.fetchIfRef(dict.get('Encoding')); @@ -761,8 +763,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // Simulating descriptor flags attribute var fontNameWoStyle = baseFontName.split('-')[0]; var flags = (serifFonts[fontNameWoStyle] || - (fontNameWoStyle.search(/serif/gi) != -1) ? 2 : 0) | - (symbolsFonts[fontNameWoStyle] ? 4 : 32); + (fontNameWoStyle.search(/serif/gi) != -1) ? FontFlags.Serif : 0) | + (symbolsFonts[fontNameWoStyle] ? FontFlags.Symbolic : + FontFlags.Nonsymbolic); var properties = { type: type.name, diff --git a/src/fonts.js b/src/fonts.js index 7f72c8086..bb91d7969 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -19,6 +19,18 @@ var kPDFGlyphSpaceUnits = 1000; // Until hinting is fully supported this constant can be used var kHintingEnabled = false; +var FontFlags = { + FixedPitch: 1, + Serif: 2, + Symbolic: 4, + Script: 8, + Nonsymbolic: 32, + Italic: 64, + AllCap: 65536, + SmallCap: 131072, + ForceBold: 262144 +}; + var Encodings = { get ExpertEncoding() { return shadow(this, 'ExpertEncoding', ['', '', '', '', '', '', '', '', '', @@ -762,8 +774,8 @@ var Font = (function FontClosure() { var names = name.split('+'); names = names.length > 1 ? names[1] : names[0]; names = names.split(/[-,_]/g)[0]; - this.isSerifFont = !!(properties.flags & 2); - this.isSymbolicFont = !!(properties.flags & 4); + this.isSerifFont = !!(properties.flags & FontFlags.Serif); + this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic); var type = properties.type; this.type = type; From 5415fed14d13a117438efef12a2ea616479785c1 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Fri, 27 Jan 2012 20:36:27 -0600 Subject: [PATCH 20/32] Mapping well-known chars to the similar equivalents in the normal characters range --- src/fonts.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/fonts.js b/src/fonts.js index bb91d7969..3f618b82a 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -418,6 +418,19 @@ var symbolsFonts = { 'Dingbats': true, 'Symbol': true, 'ZapfDingbats': true }; +// Some characters, e.g. copyrightserif, mapped to the private use area and +// might not be displayed using standard fonts. Mapping/hacking well-known chars +// to the similar equivalents in the normal characters range. +function mapPrivateUseChars(code) { + switch (code) { + case 0xF8E9: // copyrightsans + case 0xF6D9: // copyrightserif + return 0x00A9; // copyright + default: + return code; + } +} + var FontLoader = { listeningForFontLoad: false, @@ -2199,7 +2212,7 @@ var Font = (function FontClosure() { case 'CIDFontType0': if (this.noUnicodeAdaptation) { width = this.widths[this.unicodeToCID[charcode] || charcode]; - unicode = charcode; + unicode = mapPrivateUseChars(charcode); break; } unicode = this.toUnicode[charcode] || charcode; @@ -2207,7 +2220,7 @@ var Font = (function FontClosure() { case 'CIDFontType2': if (this.noUnicodeAdaptation) { width = this.widths[this.unicodeToCID[charcode] || charcode]; - unicode = charcode; + unicode = mapPrivateUseChars(charcode); break; } unicode = this.toUnicode[charcode] || charcode; @@ -2217,7 +2230,7 @@ var Font = (function FontClosure() { if (!isNum(width)) width = this.widths[glyphName]; if (this.noUnicodeAdaptation) { - unicode = GlyphsUnicode[glyphName] || charcode; + unicode = mapPrivateUseChars(GlyphsUnicode[glyphName] || charcode); break; } unicode = this.glyphNameMap[glyphName] || From 28f82e3dd7c3075c221e8e01ab359c87470a3791 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Sun, 29 Jan 2012 11:09:33 -0800 Subject: [PATCH 21/32] rewrite CMYK to RGB conversion --- src/colorspace.js | 57 ++++++++--------------------------------------- 1 file changed, 9 insertions(+), 48 deletions(-) diff --git a/src/colorspace.js b/src/colorspace.js index 827fd2e19..ad808df95 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -369,55 +369,16 @@ var DeviceCmykCS = (function DeviceCmykCSClosure() { DeviceCmykCS.prototype = { getRgb: function cmykcs_getRgb(color) { var c = color[0], m = color[1], y = color[2], k = color[3]; - var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k; - var x, r, g, b; - // this is a matrix multiplication, unrolled for performance - // code is taken from the poppler implementation - x = c1 * m1 * y1 * k1; // 0 0 0 0 - r = g = b = x; - x = c1 * m1 * y1 * k; // 0 0 0 1 - r += 0.1373 * x; - g += 0.1216 * x; - b += 0.1255 * x; - x = c1 * m1 * y * k1; // 0 0 1 0 - r += x; - g += 0.9490 * x; - x = c1 * m1 * y * k; // 0 0 1 1 - r += 0.1098 * x; - g += 0.1020 * x; - x = c1 * m * y1 * k1; // 0 1 0 0 - r += 0.9255 * x; - b += 0.5490 * x; - x = c1 * m * y1 * k; // 0 1 0 1 - r += 0.1412 * x; - x = c1 * m * y * k1; // 0 1 1 0 - r += 0.9294 * x; - g += 0.1098 * x; - b += 0.1412 * x; - x = c1 * m * y * k; // 0 1 1 1 - r += 0.1333 * x; - x = c * m1 * y1 * k1; // 1 0 0 0 - g += 0.6784 * x; - b += 0.9373 * x; - x = c * m1 * y1 * k; // 1 0 0 1 - g += 0.0588 * x; - b += 0.1412 * x; - x = c * m1 * y * k1; // 1 0 1 0 - g += 0.6510 * x; - b += 0.3137 * x; - x = c * m1 * y * k; // 1 0 1 1 - g += 0.0745 * x; - x = c * m * y1 * k1; // 1 1 0 0 - r += 0.1804 * x; - g += 0.1922 * x; - b += 0.5725 * x; - x = c * m * y1 * k; // 1 1 0 1 - b += 0.0078 * x; - x = c * m * y * k1; // 1 1 1 0 - r += 0.2118 * x; - g += 0.2119 * x; - b += 0.2235 * x; + // CMYK -> CMY: http://www.easyrgb.com/index.php?X=MATH&H=14#text14 + c = (c * (1-k) + k); + m = (m * (1-k) + k); + y = (y * (1-k) + k); + + // CMY -> RGB: http://www.easyrgb.com/index.php?X=MATH&H=12#text12 + r = (1-c); + g = (1-m); + b = (1-y); return [r, g, b]; }, From e3a3ec6f2e3b57bd310ed8cb0d663946dae9b2d2 Mon Sep 17 00:00:00 2001 From: Kalervo Kujala Date: Sun, 29 Jan 2012 22:25:06 +0200 Subject: [PATCH 22/32] Use JPX and JPEG in error messages. Also throw in workerConsole. --- src/jpx.js | 16 ++++++++-------- src/stream.js | 2 +- src/worker.js | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/jpx.js b/src/jpx.js index c212c6fd0..a15c3db54 100644 --- a/src/jpx.js +++ b/src/jpx.js @@ -1052,7 +1052,7 @@ var JpxImage = (function JpxImageClosure() { } r = 0; } - error('Out of packets'); + error('JPX error: Out of packets'); }; } function ResolutionLayerComponentPositionIterator(context) { @@ -1091,7 +1091,7 @@ var JpxImage = (function JpxImageClosure() { } l = 0; } - error('Out of packets'); + error('JPX error: Out of packets'); }; } function buildPackets(context) { @@ -1187,7 +1187,7 @@ var JpxImage = (function JpxImageClosure() { new ResolutionLayerComponentPositionIterator(context); break; default: - error('Unsupported progression order ' + progressionOrder); + error('JPX error: Unsupported progression order ' + progressionOrder); } } function parseTilePackets(context, data, offset, dataLength) { @@ -1589,7 +1589,7 @@ var JpxImage = (function JpxImageClosure() { if (lbox == 0) lbox = length - position + headerSize; if (lbox < headerSize) - error('Invalid box field size'); + error('JPX error: Invalid box field size'); var dataLength = lbox - headerSize; var jumpDataLength = true; switch (tbox) { @@ -1675,7 +1675,7 @@ var JpxImage = (function JpxImageClosure() { scalarExpounded = true; break; default: - error('Invalid SQcd value ' + sqcd); + error('JPX error: Invalid SQcd value ' + sqcd); } qcd.noQuantization = spqcdSize == 8; qcd.scalarExpounded = scalarExpounded; @@ -1728,7 +1728,7 @@ var JpxImage = (function JpxImageClosure() { scalarExpounded = true; break; default: - error('Invalid SQcd value ' + sqcd); + error('JPX error: Invalid SQcd value ' + sqcd); } qcc.noQuantization = spqcdSize == 8; qcc.scalarExpounded = scalarExpounded; @@ -1795,7 +1795,7 @@ var JpxImage = (function JpxImageClosure() { cod.terminationOnEachCodingPass || cod.verticalyStripe || cod.predictableTermination || cod.segmentationSymbolUsed) - error('Unsupported COD options: ' + uneval(cod)); + error('JPX error: Unsupported COD options: ' + uneval(cod)); if (context.mainHeader) context.COD = cod; @@ -1840,7 +1840,7 @@ var JpxImage = (function JpxImageClosure() { // skipping content break; default: - error('Unknown codestream code: ' + code.toString(16)); + error('JPX error: Unknown codestream code: ' + code.toString(16)); } position += length; } diff --git a/src/stream.js b/src/stream.js index c7b7c83e6..fc163171f 100644 --- a/src/stream.js +++ b/src/stream.js @@ -832,7 +832,7 @@ var JpegStream = (function JpegStreamClosure() { this.buffer = data; this.bufferLength = data.length; } catch (e) { - error(e); + error('JPEG error: ' + e); } }; JpegStream.prototype.getIR = function jpegStreamGetIR() { diff --git a/src/worker.js b/src/worker.js index f9777d7df..b81ff0540 100644 --- a/src/worker.js +++ b/src/worker.js @@ -208,6 +208,7 @@ var workerConsole = { action: 'console_error', data: args }); + throw 'pdf.js execution error'; }, time: function time(name) { From 9650df5c10a51976d1b8600d79ff244274e5764c Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Sun, 29 Jan 2012 15:05:26 -0800 Subject: [PATCH 23/32] fix style and add var declaration --- src/colorspace.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/colorspace.js b/src/colorspace.js index ad808df95..d67d928b1 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -371,14 +371,14 @@ var DeviceCmykCS = (function DeviceCmykCSClosure() { var c = color[0], m = color[1], y = color[2], k = color[3]; // CMYK -> CMY: http://www.easyrgb.com/index.php?X=MATH&H=14#text14 - c = (c * (1-k) + k); - m = (m * (1-k) + k); - y = (y * (1-k) + k); + c = (c * (1 - k) + k); + m = (m * (1 - k) + k); + y = (y * (1 - k) + k); // CMY -> RGB: http://www.easyrgb.com/index.php?X=MATH&H=12#text12 - r = (1-c); - g = (1-m); - b = (1-y); + var r = (1 - c); + var g = (1 - m); + var b = (1 - y); return [r, g, b]; }, From 59283bdf6d439fdcd1a0ab07b318b48031091b34 Mon Sep 17 00:00:00 2001 From: Andreas Gal Date: Mon, 30 Jan 2012 01:38:28 -0800 Subject: [PATCH 24/32] implement sampled functions based on the PDF spec --- src/function.js | 126 ++++++++++++++++-------------------------------- 1 file changed, 42 insertions(+), 84 deletions(-) diff --git a/src/function.js b/src/function.js index 26b8fe679..3093b0a99 100644 --- a/src/function.js +++ b/src/function.js @@ -125,109 +125,67 @@ var PDFFunction = (function PDFFunctionClosure() { else decode = toMultiArray(decode); - // Precalc the multipliers - var inputMul = new Float64Array(inputSize); - for (var i = 0; i < inputSize; ++i) { - inputMul[i] = (encode[i][1] - encode[i][0]) / - (domain[i][1] - domain[i][0]); - } - - var idxMul = new Int32Array(inputSize); - idxMul[0] = outputSize; - for (i = 1; i < inputSize; ++i) { - idxMul[i] = idxMul[i - 1] * size[i - 1]; - } - - var nSamples = outputSize; - for (i = 0; i < inputSize; ++i) - nSamples *= size[i]; - var samples = this.getSampleArray(size, outputSize, bps, str); return [ CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, - outputSize, bps, range, inputMul, idxMul, nSamples + outputSize, Math.pow(2, bps) - 1, range ]; }, constructSampledFromIR: function pdfFunctionConstructSampledFromIR(IR) { - var inputSize = IR[1]; - var domain = IR[2]; - var encode = IR[3]; - var decode = IR[4]; - var samples = IR[5]; - var size = IR[6]; - var outputSize = IR[7]; - var bps = IR[8]; - var range = IR[9]; - var inputMul = IR[10]; - var idxMul = IR[11]; - var nSamples = IR[12]; + // See chapter 3, page 109 of the PDF reference + function interpolate(x, xmin, xmax, ymin, ymax) { + return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); + } return function constructSampledFromIRResult(args) { - if (inputSize != args.length) + // See chapter 3, page 110 of the PDF reference. + var m = IR[1]; + var domain = IR[2]; + var encode = IR[3]; + var decode = IR[4]; + var samples = IR[5]; + var size = IR[6]; + var n = IR[7]; + var mask = IR[8]; + var range = IR[9]; + + if (m != args.length) error('Incorrect number of arguments: ' + inputSize + ' != ' + args.length); - // Most of the below is a port of Poppler's implementation. - // TODO: There's a few other ways to do multilinear interpolation such - // as piecewise, which is much faster but an approximation. - var out = new Float64Array(outputSize); - var x; - var e = new Array(inputSize); - var efrac0 = new Float64Array(inputSize); - var efrac1 = new Float64Array(inputSize); - var sBuf = new Float64Array(1 << inputSize); - var i, j, k, idx, t; - // map input values into sample array - for (i = 0; i < inputSize; ++i) { - x = (args[i] - domain[i][0]) * inputMul[i] + encode[i][0]; - if (x < 0) { - x = 0; - } else if (x > size[i] - 1) { - x = size[i] - 1; - } - e[i] = [Math.floor(x), 0]; - if ((e[i][1] = e[i][0] + 1) >= size[i]) { - // this happens if in[i] = domain[i][1] - e[i][1] = e[i][0]; - } - efrac1[i] = x - e[i][0]; - efrac0[i] = 1 - efrac1[i]; - } + var x = args; + var y = new Float64Array(n * m); - // for each output, do m-linear interpolation - for (i = 0; i < outputSize; ++i) { + // Map x_i to y_j for 0 <= i < m using the sampled function. + for (var i = 0; i < m; ++i) { + // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) + var domain_2i = domain[2 * i]; + var domain_2i_1 = domain[2 * i + 1]; + var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); - // pull 2^m values out of the sample array - for (j = 0; j < (1 << inputSize); ++j) { - idx = i; - for (k = 0, t = j; k < inputSize; ++k, t >>= 1) { - idx += idxMul[k] * (e[k][t & 1]); - } - if (idx >= 0 && idx < nSamples) { - sBuf[j] = samples[idx]; - } else { - sBuf[j] = 0; // TODO Investigate if this is what Adobe does - } - } + // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, Encode_2i, Encode_2i+1) + var e = interpolate(xi, domain_2i, domain_2i_1, encode[2 * i], encode[2 * i + 1]); - // do m sets of interpolations - for (j = 0, t = (1 << inputSize); j < inputSize; ++j, t >>= 1) { - for (k = 0; k < t; k += 2) { - sBuf[k >> 1] = efrac0[j] * sBuf[k] + efrac1[j] * sBuf[k + 1]; - } - } + // e_i' = min(max(e_i, 0), Size_i - 1) + e = Math.min(Math.max(e, 0), size[i] - 1); - // map output value to range - out[i] = (sBuf[0] * (decode[i][1] - decode[i][0]) + decode[i][0]); - if (out[i] < range[i][0]) { - out[i] = range[i][0]; - } else if (out[i] > range[i][1]) { - out[i] = range[i][1]; + var in = i * n; + + for (var j = 0; j < n; ++j) { + // average the two nearest neighbors in the sampling table + var rj = (samples[Math.floor(e) * n + j] + samples[Math.ceil(e) * n + j]) / 2; + + // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, Decode_2j, Decode_2j+1) + rj = interpolate(rj, 0, mask, 1, decode[2 * j], decode[2 * j + 1]); + + // y_j = min(max(r_j, range_2j, range_2j+1) + y[in + j] = Math.min(Math.max(rj, range[2 * j], range[2 * j + 1])); } } - return out; + + return y; } }, From 8068ff242d0d4c067743f9d55ae3886a240e733c Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Tue, 31 Jan 2012 09:01:04 -0500 Subject: [PATCH 25/32] readXRefTable rewrite, progress --- src/obj.js | 71 +++++++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/src/obj.js b/src/obj.js index ef7932546..0bf29347c 100644 --- a/src/obj.js +++ b/src/obj.js @@ -287,44 +287,49 @@ var XRef = (function XRefClosure() { XRef.prototype = { readXRefTable: function readXRefTable(parser) { - var obj; - while (true) { - if (isCmd(obj = parser.getObj(), 'trailer')) - break; - if (!isInt(obj)) + // Example of cross-reference table: + // xref + // 0 1 <-- subsection header (first obj #, obj count) + // 0000000000 65535 f <-- actual object (offset, generation #, f/n) + // 23 2 <-- subsection header ... and so on ... + // 0000025518 00002 n + // 0000025635 00000 n + // trailer + // ... + + // Outer loop is over subsection headers + var first; + while (!isCmd(first = parser.getObj(), 'trailer')) { + var count = parser.getObj(); + + if (!isInt(first) || !isInt(count)) error('Invalid XRef table'); - var first = obj; - if (!isInt(obj = parser.getObj())) - error('Invalid XRef table'); - var n = obj; - if (first < 0 || n < 0 || (first + n) != ((first + n) | 0)) - error('Invalid XRef table: ' + first + ', ' + n); - for (var i = first; i < first + n; ++i) { - var entry = {}; - if (!isInt(obj = parser.getObj())) - error('Invalid XRef table: ' + first + ', ' + n); - entry.offset = obj; - if (!isInt(obj = parser.getObj())) - error('Invalid XRef table: ' + first + ', ' + n); - entry.gen = obj; - obj = parser.getObj(); - if (isCmd(obj, 'n')) { - entry.uncompressed = true; - } else if (isCmd(obj, 'f')) { + + // Inner loop is over objects themselves + for (var i = first; i < first + count; ++i) { + var entry = {}; + entry.offset = parser.getObj(); + entry.gen = parser.getObj(); + var type = parser.getObj(); + + if (type === 'f') entry.free = true; - } else { - error('Invalid XRef table: ' + first + ', ' + n); + else if (type === 'n') + entry.uncompressed = true; + + // Validate entry obj + if ( !isInt(entry.offset) || !isInt(entry.gen) || + !(('free' in entry) || ('uncompressed' in entry)) ) { + error('Invalid XRef table: ' + first + ', ' + count); } - if (!this.entries[i]) { - // In some buggy PDF files the xref table claims to start at 1 - // instead of 0. - if (i == 1 && first == 1 && - entry.offset == 0 && entry.gen == 65535 && entry.free) { - i = first = 0; - } + + if (!this.entries[i]) this.entries[i] = entry; - } } + + // No objects added? + if (i - first <= 0) + error('Invalid XRef table: ' + first + ', ' + count); } // read the trailer dictionary From 4375bd22194d78e5e123c100a7c5e93d325d36ab Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Tue, 31 Jan 2012 09:57:12 -0500 Subject: [PATCH 26/32] progress --- src/obj.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/obj.js b/src/obj.js index 0bf29347c..0db057b68 100644 --- a/src/obj.js +++ b/src/obj.js @@ -303,32 +303,32 @@ var XRef = (function XRefClosure() { var count = parser.getObj(); if (!isInt(first) || !isInt(count)) - error('Invalid XRef table'); + error('Invalid XRef table: wrong types in subsection header'); // Inner loop is over objects themselves - for (var i = first; i < first + count; ++i) { + for (var i = 0; i < count; i++) { var entry = {}; entry.offset = parser.getObj(); entry.gen = parser.getObj(); var type = parser.getObj(); - if (type === 'f') + if (isCmd(type, 'f')) entry.free = true; - else if (type === 'n') + else if (isCmd(type, 'n')) entry.uncompressed = true; // Validate entry obj if ( !isInt(entry.offset) || !isInt(entry.gen) || - !(('free' in entry) || ('uncompressed' in entry)) ) { + !(entry.free || entry.uncompressed) ) { error('Invalid XRef table: ' + first + ', ' + count); } - if (!this.entries[i]) - this.entries[i] = entry; + if (!this.entries[i + first]) + this.entries[i + first] = entry; } // No objects added? - if (i - first <= 0) + if (!(i > 0)) error('Invalid XRef table: ' + first + ', ' + count); } @@ -339,7 +339,7 @@ var XRef = (function XRefClosure() { // get the 'Prev' pointer var prev; - obj = dict.get('Prev'); + var obj = dict.get('Prev'); if (isInt(obj)) { prev = obj; } else if (isRef(obj)) { From 0959cd35172558b5a22647e846d43ece40e56c0c Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Tue, 31 Jan 2012 10:49:06 -0500 Subject: [PATCH 27/32] New readXRefTable, working --- src/obj.js | 101 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 44 deletions(-) diff --git a/src/obj.js b/src/obj.js index 0db057b68..d03ee4889 100644 --- a/src/obj.js +++ b/src/obj.js @@ -298,9 +298,10 @@ var XRef = (function XRefClosure() { // ... // Outer loop is over subsection headers - var first; - while (!isCmd(first = parser.getObj(), 'trailer')) { - var count = parser.getObj(); + var obj; + while (!isCmd(obj = parser.getObj(), 'trailer')) { + var first = obj, + count = parser.getObj(); if (!isInt(first) || !isInt(count)) error('Invalid XRef table: wrong types in subsection header'); @@ -320,46 +321,35 @@ var XRef = (function XRefClosure() { // Validate entry obj if ( !isInt(entry.offset) || !isInt(entry.gen) || !(entry.free || entry.uncompressed) ) { - error('Invalid XRef table: ' + first + ', ' + count); + error('Invalid entry in XRef subsection: ' + first + ', ' + count); } if (!this.entries[i + first]) this.entries[i + first] = entry; } - - // No objects added? - if (!(i > 0)) - error('Invalid XRef table: ' + first + ', ' + count); } - // read the trailer dictionary - var dict; - if (!isDict(dict = parser.getObj())) - error('Invalid XRef table'); + // Sanity check: as per spec, first object must have these properties + if ( this.entries[0] && + !(this.entries[0].gen === 65535 && this.entries[0].free) ) + error('Invalid XRef table: unexpected first object'); - // get the 'Prev' pointer - var prev; - var obj = dict.get('Prev'); - if (isInt(obj)) { - prev = obj; - } else if (isRef(obj)) { - // certain buggy PDF generators generate "/Prev NNN 0 R" instead - // of "/Prev NNN" - prev = obj.num; - } - if (prev) { - this.readXRef(prev); - } + // Sanity check + if (!isCmd(obj, 'trailer')) + error('Invalid XRef table: could not find trailer dictionary'); - // check for 'XRefStm' key - if (isInt(obj = dict.get('XRefStm'))) { - var pos = obj; - // ignore previously loaded xref streams (possible infinite recursion) - if (!(pos in this.xrefstms)) { - this.xrefstms[pos] = 1; - this.readXRef(pos); - } - } + // Read trailer dictionary, e.g. + // trailer + // << /Size 22 + // /Root 20R + // /Info 10R + // /ID [ <81b14aafa313db63dbd6f981e49f94f4> ] + // >> + // The parser goes through the entire stream << ... >> and provides + // a getter interface for the key-value table + var dict = parser.getObj(); + if (!isDict(dict)) + error('Invalid XRef table: could not parse trailer dictionary'); return dict; }, @@ -412,9 +402,6 @@ var XRef = (function XRefClosure() { } range.splice(0, 2); } - var prev = streamParameters.get('Prev'); - if (isInt(prev)) - this.readXRef(prev); return streamParameters; }, indexObjects: function indexObjects() { @@ -534,22 +521,48 @@ var XRef = (function XRefClosure() { try { var parser = new Parser(new Lexer(stream), true); var obj = parser.getObj(); + var dict; - // parse an old-style xref table - if (isCmd(obj, 'xref')) - return this.readXRefTable(parser); + // Get dictionary + if (isCmd(obj, 'xref')) { + // Parse end-of-file XRef + dict = this.readXRefTable(parser); - // parse an xref stream - if (isInt(obj)) { + // Recursively get other XRefs 'XRefStm', if any + obj = dict.get('XRefStm'); + if (isInt(obj)) { + var pos = obj; + // ignore previously loaded xref streams + // (possible infinite recursion) + if (!(pos in this.xrefstms)) { + this.xrefstms[pos] = 1; + this.readXRef(pos); + } + } + } else if (isInt(obj)) { + // Parse in-stream XRef if (!isInt(parser.getObj()) || !isCmd(parser.getObj(), 'obj') || !isStream(obj = parser.getObj())) { error('Invalid XRef stream'); } - return this.readXRefStream(obj); + dict = this.readXRefStream(obj); } + + // Recursively get previous dictionary, if any + obj = dict.get('Prev'); + if (isInt(obj)) + this.readXRef(obj); + else if (isRef(obj)) { + // The spec says Prev must not be a reference, i.e. "/Prev NNN" + // This is a fallback for non-compliant PDFs, i.e. "/Prev NNN 0 R" + this.readXRef(obj.num); + } + + return dict; } catch (e) { - log('Reading of the xref table/stream failed: ' + e); + // log('(while reading XRef): ' + e); +error('(while reading XRef): ' + e); } warn('Indexing all PDF objects'); From 9e9674d45c025d20ed1480d485e905b51aedef98 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Tue, 31 Jan 2012 10:50:30 -0500 Subject: [PATCH 28/32] Remove debugging line --- src/obj.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/obj.js b/src/obj.js index d03ee4889..2088839c0 100644 --- a/src/obj.js +++ b/src/obj.js @@ -561,8 +561,7 @@ var XRef = (function XRefClosure() { return dict; } catch (e) { - // log('(while reading XRef): ' + e); -error('(while reading XRef): ' + e); + log('(while reading XRef): ' + e); } warn('Indexing all PDF objects'); From 775290d69806726652c4c1f3fa1184f40e7c0492 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Tue, 31 Jan 2012 10:57:32 -0500 Subject: [PATCH 29/32] Lint --- src/obj.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/obj.js b/src/obj.js index 2088839c0..8ac4706c0 100644 --- a/src/obj.js +++ b/src/obj.js @@ -292,11 +292,11 @@ var XRef = (function XRefClosure() { // 0 1 <-- subsection header (first obj #, obj count) // 0000000000 65535 f <-- actual object (offset, generation #, f/n) // 23 2 <-- subsection header ... and so on ... - // 0000025518 00002 n + // 0000025518 00002 n // 0000025635 00000 n // trailer // ... - + // Outer loop is over subsection headers var obj; while (!isCmd(obj = parser.getObj(), 'trailer')) { @@ -308,7 +308,7 @@ var XRef = (function XRefClosure() { // Inner loop is over objects themselves for (var i = 0; i < count; i++) { - var entry = {}; + var entry = {}; entry.offset = parser.getObj(); entry.gen = parser.getObj(); var type = parser.getObj(); @@ -319,19 +319,19 @@ var XRef = (function XRefClosure() { entry.uncompressed = true; // Validate entry obj - if ( !isInt(entry.offset) || !isInt(entry.gen) || - !(entry.free || entry.uncompressed) ) { + if (!isInt(entry.offset) || !isInt(entry.gen) || + !(entry.free || entry.uncompressed)) { error('Invalid entry in XRef subsection: ' + first + ', ' + count); } - + if (!this.entries[i + first]) this.entries[i + first] = entry; } } // Sanity check: as per spec, first object must have these properties - if ( this.entries[0] && - !(this.entries[0].gen === 65535 && this.entries[0].free) ) + if (this.entries[0] && + !(this.entries[0].gen === 65535 && this.entries[0].free)) error('Invalid XRef table: unexpected first object'); // Sanity check @@ -532,7 +532,7 @@ var XRef = (function XRefClosure() { obj = dict.get('XRefStm'); if (isInt(obj)) { var pos = obj; - // ignore previously loaded xref streams + // ignore previously loaded xref streams // (possible infinite recursion) if (!(pos in this.xrefstms)) { this.xrefstms[pos] = 1; From 1047f0264aeeef7c176a3878dc501ec12e02537c Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Tue, 31 Jan 2012 15:31:26 -0500 Subject: [PATCH 30/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f12fce934..09cc95039 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ rendering PDFs, and eventually release a PDF reader extension powered by pdf.js. Integration with Firefox is a possibility if the experiment proves successful. - + # Getting started From 6cf3109329f122996d7343a67b4c67ebf7981ff9 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 31 Jan 2012 17:53:42 -0800 Subject: [PATCH 31/32] Address review concerns. --- Makefile | 6 +++--- extensions/firefox/components/PdfStreamConverter.js | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index eaaa3e81f..adeb91b12 100644 --- a/Makefile +++ b/Makefile @@ -252,16 +252,16 @@ extension: | production @cp -r $(EXTENSION_WEB_FILES) $(FIREFOX_BUILD_CONTENT)/web/ @rm $(FIREFOX_BUILD_CONTENT)/web/viewer-production.html # Copy over the firefox extension snippet so we can inline pdf.js in it - cp web/viewer-snippet-firefox-extension.html $(FIREFOX_BUILD_CONTENT)/web/ + @cp web/viewer-snippet-firefox-extension.html $(FIREFOX_BUILD_CONTENT)/web/ # Modify the viewer so it does all the extension only stuff. - cd $(FIREFOX_BUILD_CONTENT)/web; \ + @cd $(FIREFOX_BUILD_CONTENT)/web; \ sed -i.bak '/PDFJSSCRIPT_INCLUDE_BUNDLE/ r ../build/pdf.js' viewer-snippet-firefox-extension.html; \ sed -i.bak '/PDFJSSCRIPT_REMOVE/d' viewer.html; \ sed -i.bak '/PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION/d' viewer.html; \ sed -i.bak '/PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION/ r viewer-snippet-firefox-extension.html' viewer.html; \ rm -f *.bak; # We don't need pdf.js anymore since its inlined - rm -Rf $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/; + @rm -Rf $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/; # Update the build version number @sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/install.rdf @sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/update.rdf diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index 83c930d51..984915d23 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -35,7 +35,7 @@ ChromeActions.prototype = { download: function(data) { Services.wm.getMostRecentWindow('navigator:browser').saveURL(data); }, - setDatabase: function() { + setDatabase: function(data) { if (this.inPrivateBrowswing) return; application.prefs.setValue(EXT_PREFIX + '.database', data); @@ -51,8 +51,8 @@ ChromeActions.prototype = { function RequestListener(actions) { this.actions = actions; } -// Recieves an event and synchronously responds. -RequestListener.prototype.recieve = function(event) { +// Receive an event and synchronously responds. +RequestListener.prototype.receive = function(event) { var message = event.target; var action = message.getUserData('action'); var data = message.getUserData('data'); @@ -143,7 +143,7 @@ PdfStreamConverter.prototype = { gb.removeEventListener('DOMContentLoaded', domListener); var requestListener = new RequestListener(new ChromeActions()); win.addEventListener(PDFJS_EVENT_ID, function(event) { - requestListener.recieve(event); + requestListener.receive(event); }, false, true); } }; From 5fba376a336e57e5961a239f387d99b1cfb11c71 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Tue, 31 Jan 2012 20:19:44 -0600 Subject: [PATCH 32/32] Fixing interpolation (continuation of #1143) --- src/function.js | 66 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/src/function.js b/src/function.js index 3093b0a99..4f81158f0 100644 --- a/src/function.js +++ b/src/function.js @@ -156,33 +156,65 @@ var PDFFunction = (function PDFFunctionClosure() { args.length); var x = args; - var y = new Float64Array(n * m); + // Building the cube vertices: its part and sample index + // http://rjwagner49.com/Mathematics/Interpolation.pdf + var cubeVertices = 1 << m; + var cubeN = new Float64Array(cubeVertices); + var cubeVertex = new Uint32Array(cubeVertices); + for (var j = 0; j < cubeVertices; j++) + cubeN[j] = 1; + + var k = n, pos = 1; // Map x_i to y_j for 0 <= i < m using the sampled function. for (var i = 0; i < m; ++i) { // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) - var domain_2i = domain[2 * i]; - var domain_2i_1 = domain[2 * i + 1]; + var domain_2i = domain[i][0]; + var domain_2i_1 = domain[i][1]; var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); - // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, Encode_2i, Encode_2i+1) - var e = interpolate(xi, domain_2i, domain_2i_1, encode[2 * i], encode[2 * i + 1]); + // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, + // Encode_2i, Encode_2i+1) + var e = interpolate(xi, domain_2i, domain_2i_1, + encode[i][0], encode[i][1]); // e_i' = min(max(e_i, 0), Size_i - 1) - e = Math.min(Math.max(e, 0), size[i] - 1); + var size_i = size[i]; + e = Math.min(Math.max(e, 0), size_i - 1); - var in = i * n; - - for (var j = 0; j < n; ++j) { - // average the two nearest neighbors in the sampling table - var rj = (samples[Math.floor(e) * n + j] + samples[Math.ceil(e) * n + j]) / 2; - - // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, Decode_2j, Decode_2j+1) - rj = interpolate(rj, 0, mask, 1, decode[2 * j], decode[2 * j + 1]); - - // y_j = min(max(r_j, range_2j, range_2j+1) - y[in + j] = Math.min(Math.max(rj, range[2 * j], range[2 * j + 1])); + // Adjusting the cube: N and vertex sample index + var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1; + var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0); + var n1 = e - e0; // (e - e0) / (e1 - e0); + var offset0 = e0 * k; + var offset1 = offset0 + k; // e1 * k + for (var j = 0; j < cubeVertices; j++) { + if (j & pos) { + cubeN[j] *= n1; + cubeVertex[j] += offset1; + } else { + cubeN[j] *= n0; + cubeVertex[j] += offset0; + } } + + k *= size_i; + pos <<= 1; + } + + var y = new Float64Array(n); + for (var j = 0; j < n; ++j) { + // Sum all cube vertices' samples portions + var rj = 0; + for (var i = 0; i < cubeVertices; i++) + rj += samples[cubeVertex[i] + j] * cubeN[i]; + + // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, + // Decode_2j, Decode_2j+1) + rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); + + // y_j = min(max(r_j, range_2j), range_2j+1) + y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); } return y;