author | Ryan VanderMeulen <ryanvm@gmail.com> |
Wed, 17 Oct 2012 15:33:58 -0400 | |
changeset 110582 | af98d67916ada9ff478f4f321a2a8460d31f2d7e |
parent 110581 | 5a94cbed4b7a1a733307c195083f6337272b0bb8 |
child 110583 | 357778ffa80165c04d0b71ab284fa300c870df51 |
push id | 23700 |
push user | ryanvm@gmail.com |
push date | Thu, 18 Oct 2012 02:10:26 +0000 |
treeherder | mozilla-central@5142bbd4da12 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | dtownsend |
bugs | 801280 |
milestone | 19.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/extensions/pdfjs/README.mozilla +++ b/browser/extensions/pdfjs/README.mozilla @@ -1,4 +1,4 @@ This is the pdf.js project output, https://github.com/mozilla/pdf.js -Current extension version is: 0.5.184 +Current extension version is: 0.6.39
--- a/browser/extensions/pdfjs/components/PdfStreamConverter.js +++ b/browser/extensions/pdfjs/components/PdfStreamConverter.js @@ -26,40 +26,46 @@ const Cu = Components.utils; // True only if this is the version of pdf.js that is included with firefox. const MOZ_CENTRAL = true; const PDFJS_EVENT_ID = 'pdf.js.message'; const PDF_CONTENT_TYPE = 'application/pdf'; const PREF_PREFIX = 'pdfjs'; const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html'; const MAX_DATABASE_LENGTH = 4096; const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}'; -const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}'; -const METRO_ID = '{99bceaaa-e3c6-48c1-b981-ef9b46b67d60}'; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://gre/modules/NetUtil.jsm'); let appInfo = Cc['@mozilla.org/xre/app-info;1'] .getService(Ci.nsIXULAppInfo); -let privateBrowsing, inPrivateBrowsing; let Svc = {}; XPCOMUtils.defineLazyServiceGetter(Svc, 'mime', '@mozilla.org/mime;1', 'nsIMIMEService'); +let isInPrivateBrowsing; if (appInfo.ID === FIREFOX_ID) { - privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] - .getService(Ci.nsIPrivateBrowsingService); - inPrivateBrowsing = privateBrowsing.privateBrowsingEnabled; -} else if (appInfo.ID === SEAMONKEY_ID || - appInfo.ID === METRO_ID) { - privateBrowsing = null; - inPrivateBrowsing = false; + let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1'] + .getService(Ci.nsIPrivateBrowsingService); + isInPrivateBrowsing = function getInPrivateBrowsing() { + return privateBrowsing.privateBrowsingEnabled; + }; +} else { + isInPrivateBrowsing = function() { return false; }; +} + +function getChromeWindow(domWindow) { + var containingBrowser = domWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell) + .chromeEventHandler; + return containingBrowser.ownerDocument.defaultView; } function getBoolPref(pref, def) { try { return Services.prefs.getBoolPref(pref); } catch (ex) { return def; } @@ -268,25 +274,25 @@ ChromeActions.prototype = { aOffset, aCount); } }; channel.asyncOpen(listener, null); }); }, setDatabase: function(data) { - if (inPrivateBrowsing) + if (isInPrivateBrowsing()) return; // Protect against something sending tons of data to setDatabase. if (data.length > MAX_DATABASE_LENGTH) return; setStringPref(PREF_PREFIX + '.database', data); }, getDatabase: function() { - if (inPrivateBrowsing) + if (isInPrivateBrowsing()) return '{}'; return getStringPref(PREF_PREFIX + '.database', '{}'); }, getLocale: function() { return getStringPref('general.useragent.locale', 'en-US'); }, getLoadingType: function() { return this.dataListener ? 'passive' : 'active'; @@ -331,18 +337,21 @@ ChromeActions.prototype = { } catch (e) { log('Unable to retrive localized strings: ' + e); return 'null'; } }, pdfBugEnabled: function() { return getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false); }, - searchEnabled: function() { - return getBoolPref(PREF_PREFIX + '.searchEnabled', false); + supportsIntegratedFind: function() { + // Integrated find is only supported when we're not in a frame and when the + // new find events code exists. + return this.domWindow.frameElement === null && + 'updateControlState' in getChromeWindow(this.domWindow).gFindBar; }, fallback: function(url, sendResponse) { var self = this; var domWindow = this.domWindow; var strings = getLocalizedStrings('chrome.properties'); var message = getLocalizedString(strings, 'unsupported_feature'); var notificationBox = null; @@ -386,16 +395,30 @@ ChromeActions.prototype = { // added in the future we still only care about removed at the moment. if (eventType !== 'removed') return; // Don't send a response again if we already responded when the button was // clicked. if (!sentResponse) sendResponse(false); }); + }, + updateFindControlState: function(data) { + if (!this.supportsIntegratedFind()) + return; + // Verify what we're sending to the findbar. + var result = data.result; + var findPrevious = data.findPrevious; + var findPreviousType = typeof findPrevious; + if ((typeof result !== 'number' || result < 0 || result > 3) || + (findPreviousType !== 'undefined' && findPreviousType !== 'boolean')) { + return; + } + getChromeWindow(this.domWindow).gFindBar + .updateControlState(result, findPrevious); } }; // Event listener to trigger chrome privedged code. function RequestListener(actions) { this.actions = actions; } // Receive an event and synchronously or asynchronously responds. @@ -426,16 +449,67 @@ RequestListener.prototype.receive = func listener.initEvent('pdf.js.response', true, false); return message.dispatchEvent(listener); } } actions[action].call(this.actions, data, response); } }; +// Forwards events from the eventElement to the contentWindow only if the +// content window matches the currently selected browser window. +function FindEventManager(eventElement, contentWindow, chromeWindow) { + this.types = ['find', + 'findagain', + 'findhighlightallchange', + 'findcasesensitivitychange']; + this.chromeWindow = chromeWindow; + this.contentWindow = contentWindow; + this.eventElement = eventElement; +} + +FindEventManager.prototype.bind = function() { + var unload = function(e) { + this.unbind(); + this.contentWindow.removeEventListener(e.type, unload); + }.bind(this); + this.contentWindow.addEventListener('unload', unload); + + for (var i = 0; i < this.types.length; i++) { + var type = this.types[i]; + this.eventElement.addEventListener(type, this, true); + } +}; + +FindEventManager.prototype.handleEvent = function(e) { + var chromeWindow = this.chromeWindow; + var contentWindow = this.contentWindow; + // Only forward the events if they are for our dom window. + if (chromeWindow.gBrowser.selectedBrowser.contentWindow === contentWindow) { + var detail = e.detail; + detail.__exposedProps__ = { + query: 'r', + caseSensitive: 'r', + highlightAll: 'r', + findPrevious: 'r' + }; + var forward = contentWindow.document.createEvent('CustomEvent'); + forward.initCustomEvent(e.type, true, true, detail); + contentWindow.dispatchEvent(forward); + e.preventDefault(); + } +}; + +FindEventManager.prototype.unbind = function() { + for (var i = 0; i < this.types.length; i++) { + var type = this.types[i]; + this.eventElement.removeEventListener(type, this, true); + } +}; + function PdfStreamConverter() { } PdfStreamConverter.prototype = { // properties required for XPCOM registration: classID: Components.ID('{d0c5195d-e798-49d4-b1d3-9324328b2291}'), classDescription: 'pdf.js Component', @@ -538,31 +612,39 @@ PdfStreamConverter.prototype = { var domWindow = getDOMWindow(channel); // Double check the url is still the correct one. if (domWindow.document.documentURIObject.equals(aRequest.URI)) { let actions = new ChromeActions(domWindow, dataListener); let requestListener = new RequestListener(actions); domWindow.addEventListener(PDFJS_EVENT_ID, function(event) { requestListener.receive(event); }, false, true); + if (actions.supportsIntegratedFind()) { + var chromeWindow = getChromeWindow(domWindow); + var findEventManager = new FindEventManager(chromeWindow.gFindBar, + domWindow, + chromeWindow); + findEventManager.bind(); + } } listener.onStopRequest.apply(listener, arguments); } }; // Keep the URL the same so the browser sees it as the same. channel.originalURI = aRequest.URI; channel.asyncOpen(proxy, aContext); if (useFetchByChrome) { // We can use resource principal when data is fetched by the chrome // e.g. useful for NoScript var securityManager = Cc['@mozilla.org/scriptsecuritymanager;1'] .getService(Ci.nsIScriptSecurityManager); var uri = ioService.newURI(PDF_VIEWER_WEB_PAGE, null, null); - // FF16 and below had getCodebasePrincipal (bug 774585) + // FF16 and below had getCodebasePrincipal, it was replaced by + // getNoAppCodebasePrincipal (bug 758258). var resourcePrincipal = 'getNoAppCodebasePrincipal' in securityManager ? securityManager.getNoAppCodebasePrincipal(uri) : securityManager.getCodebasePrincipal(uri); channel.owner = resourcePrincipal; } }, // nsIRequestObserver::onStopRequest
--- a/browser/extensions/pdfjs/content/PdfJs.jsm +++ b/browser/extensions/pdfjs/content/PdfJs.jsm @@ -18,20 +18,22 @@ var EXPORTED_SYMBOLS = ["PdfJs"]; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cm = Components.manager; const Cu = Components.utils; const PREF_PREFIX = 'pdfjs'; const PREF_DISABLED = PREF_PREFIX + '.disabled'; -const PREF_FIRST_RUN = PREF_PREFIX + '.firstRun'; +const PREF_MIGRATION_VERSION = PREF_PREFIX + '.migrationVersion'; const PREF_PREVIOUS_ACTION = PREF_PREFIX + '.previousHandler.preferredAction'; const PREF_PREVIOUS_ASK = PREF_PREFIX + '.previousHandler.alwaysAskBeforeHandling'; +const PREF_DISABLED_PLUGIN_TYPES = 'plugin.disable_full_page_plugin_for_types'; const TOPIC_PDFJS_HANDLER_CHANGED = 'pdfjs:handlerChanged'; +const PDF_CONTENT_TYPE = 'application/pdf'; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://pdf.js.components/PdfStreamConverter.js'); let Svc = {}; XPCOMUtils.defineLazyServiceGetter(Svc, 'mime', '@mozilla.org/mime;1', @@ -40,16 +42,24 @@ XPCOMUtils.defineLazyServiceGetter(Svc, function getBoolPref(aPref, aDefaultValue) { try { return Services.prefs.getBoolPref(aPref); } catch (ex) { return aDefaultValue; } } +function getIntPref(aPref, aDefaultValue) { + try { + return Services.prefs.getIntPref(aPref); + } catch (ex) { + return aDefaultValue; + } +} + // Register/unregister a constructor as a component. let Factory = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]), _targetConstructor: null, register: function register(targetConstructor) { this._targetConstructor = targetConstructor; var proto = targetConstructor.prototype; @@ -79,47 +89,80 @@ let Factory = { } }; let PdfJs = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), _registered: false, init: function init() { - // On first run make pdf.js the default handler. - if (!getBoolPref(PREF_DISABLED, true) && getBoolPref(PREF_FIRST_RUN, false)) { - Services.prefs.setBoolPref(PREF_FIRST_RUN, false); - - let handlerInfo = Svc.mime.getFromTypeAndExtension('application/pdf', 'pdf'); - // Store the previous settings of preferredAction and - // alwaysAskBeforeHandling in case we need to revert them in a hotfix that - // would turn pdf.js off. - Services.prefs.setIntPref(PREF_PREVIOUS_ACTION, handlerInfo.preferredAction); - Services.prefs.setBoolPref(PREF_PREVIOUS_ASK, handlerInfo.alwaysAskBeforeHandling); - - let handlerService = Cc['@mozilla.org/uriloader/handler-service;1']. - getService(Ci.nsIHandlerService); - - // Change and save mime handler settings. - handlerInfo.alwaysAskBeforeHandling = false; - handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally; - handlerService.store(handlerInfo); + if (!getBoolPref(PREF_DISABLED, true)) { + this._migrate(); } if (this.enabled) this._ensureRegistered(); else this._ensureUnregistered(); // Listen for when pdf.js is completely disabled or a different pdf handler // is chosen. Services.prefs.addObserver(PREF_DISABLED, this, false); Services.obs.addObserver(this, TOPIC_PDFJS_HANDLER_CHANGED, false); }, + _migrate: function migrate() { + const VERSION = 1; + var currentVersion = getIntPref(PREF_MIGRATION_VERSION, 0); + if (currentVersion >= VERSION) { + return; + } + // Make pdf.js the default pdf viewer on the first migration. + if (currentVersion < 2) { + this._becomeHandler(); + } + Services.prefs.setIntPref(PREF_MIGRATION_VERSION, VERSION); + }, + + _becomeHandler: function _becomeHandler() { + let handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, 'pdf'); + let prefs = Services.prefs; + if (handlerInfo.preferredAction !== Ci.nsIHandlerInfo.handleInternally && + handlerInfo.preferredAction !== false) { + // Store the previous settings of preferredAction and + // alwaysAskBeforeHandling in case we need to revert them in a hotfix that + // would turn pdf.js off. + prefs.setIntPref(PREF_PREVIOUS_ACTION, handlerInfo.preferredAction); + prefs.setBoolPref(PREF_PREVIOUS_ASK, handlerInfo.alwaysAskBeforeHandling); + } + + let handlerService = Cc['@mozilla.org/uriloader/handler-service;1']. + getService(Ci.nsIHandlerService); + + // Change and save mime handler settings. + handlerInfo.alwaysAskBeforeHandling = false; + handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally; + handlerService.store(handlerInfo); + + // Also disable any plugins for pdfs. + var stringTypes = ''; + var types = []; + if (prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES)) { + stringTypes = prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES); + } + if (stringTypes !== '') { + types = stringTypes.split(','); + } + + if (types.indexOf(PDF_CONTENT_TYPE) === -1) { + types.push(PDF_CONTENT_TYPE); + } + prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES, types.join(',')); + }, + // nsIObserver observe: function observe(aSubject, aTopic, aData) { if (this.enabled) this._ensureRegistered(); else this._ensureUnregistered(); },
new file mode 100644 index 0000000000000000000000000000000000000000..08a2c25327d7df3c77186cc3048bf02b7e72f393 GIT binary patch literal 371 zc%17D@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6n3BBRT^Rni_n+Ah<Z%{wL>4nJ za0`PlBg3pY5<o%r5>H=O_B&jxVj>1dW-W_hU|?kNba4#PIA1#9tk+=&k=FgQ+pdT` zUbe$HMe!GtU6WGBI$aLYZm~5FlsUCLM9rJ>x)ki1v&;2jM441q`dLYetDQYE-*|TQ z^E<_9?^Y%rUfkJzY)ZIaD_<MC&cmBh$CtgYp8dL|p1-2*N5P)WO~<yom=<t<b<MX{ zV0F4SV~dnB5HBwOxorLKx2`PB3<(U3Mv^-!Sehqq>wl!)(o;G8S#tSeKda!GbIv?G zbmhsbOZ!$BA5pw%wO?3d{?EV_k<*RN+E-q_-@A4@_jI3mOCvtN%FsWiAoSU(>6p5s zu;<LjPZQOdDxY?~D4*1|{qN@6tRl%q{~C{f`Y<K**{Z}4_g5KqIsN)|hXdZr_a9ya P3?c?kS3j3^P6<r_kMx(q
new file mode 100644 index 0000000000000000000000000000000000000000..beef8ccea48c932571c9c9009df916a61c850a1c GIT binary patch literal 381 zc$@)!0fPRCP)<h;3K|Lk000e1NJLTq000mG000mO1ONa4wfZ;e00002VoOIv0RM-N z%)bBt010qNS#tmY3ljhU3ljkVnw%H_000McNliru+zJI29sn4uetG}^0S-w-K~yNu zb&)+w13?gmpWQt!_hI5eD#4IKO<|GNA0TLBqfH`0&_WQwe<O&XAP9<DC}<P>Cn>#B z0z$y#_7d0P?!t27TP(BlKC{g1C~Qf5JYZ(0Y`HA=4{6~34>Y$nUyp4>0?NXx%y!n^ zPO8lU5v<pjhGYCX!!bT>vjOn@v3E&~2En%3M*;>A02l=3;j?=|jFv2*(IokqkYu+` z?f#Sot_0GOokeN4@_t0%AeiK{n7pfbJT>4jCFQRS;-#{C!?(j16Xd+w6vSiLIVVO7 zT0|%UMkoT>Cd44_!h@@9OoxH>aKs|3a2xe<yZqHZjL=WD-gRM@<<aV&v0^Vvl-BK` bNbKrg3mRK+IzR8P00000NkvXXu0mjfaG9N{
new file mode 100644 index 0000000000000000000000000000000000000000..beef8ccea48c932571c9c9009df916a61c850a1c GIT binary patch literal 381 zc$@)!0fPRCP)<h;3K|Lk000e1NJLTq000mG000mO1ONa4wfZ;e00002VoOIv0RM-N z%)bBt010qNS#tmY3ljhU3ljkVnw%H_000McNliru+zJI29sn4uetG}^0S-w-K~yNu zb&)+w13?gmpWQt!_hI5eD#4IKO<|GNA0TLBqfH`0&_WQwe<O&XAP9<DC}<P>Cn>#B z0z$y#_7d0P?!t27TP(BlKC{g1C~Qf5JYZ(0Y`HA=4{6~34>Y$nUyp4>0?NXx%y!n^ zPO8lU5v<pjhGYCX!!bT>vjOn@v3E&~2En%3M*;>A02l=3;j?=|jFv2*(IokqkYu+` z?f#Sot_0GOokeN4@_t0%AeiK{n7pfbJT>4jCFQRS;-#{C!?(j16Xd+w6vSiLIVVO7 zT0|%UMkoT>Cd44_!h@@9OoxH>aKs|3a2xe<yZqHZjL=WD-gRM@<<aV&v0^Vvl-BK` bNbKrg3mRK+IzR8P00000NkvXXu0mjfaG9N{
new file mode 100644 index 0000000000000000000000000000000000000000..08a2c25327d7df3c77186cc3048bf02b7e72f393 GIT binary patch literal 371 zc%17D@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6n3BBRT^Rni_n+Ah<Z%{wL>4nJ za0`PlBg3pY5<o%r5>H=O_B&jxVj>1dW-W_hU|?kNba4#PIA1#9tk+=&k=FgQ+pdT` zUbe$HMe!GtU6WGBI$aLYZm~5FlsUCLM9rJ>x)ki1v&;2jM441q`dLYetDQYE-*|TQ z^E<_9?^Y%rUfkJzY)ZIaD_<MC&cmBh$CtgYp8dL|p1-2*N5P)WO~<yom=<t<b<MX{ zV0F4SV~dnB5HBwOxorLKx2`PB3<(U3Mv^-!Sehqq>wl!)(o;G8S#tSeKda!GbIv?G zbmhsbOZ!$BA5pw%wO?3d{?EV_k<*RN+E-q_-@A4@_jI3mOCvtN%FsWiAoSU(>6p5s zu;<LjPZQOdDxY?~D4*1|{qN@6tRl%q{~C{f`Y<K**{Z}4_g5KqIsN)|hXdZr_a9ya P3?c?kS3j3^P6<r_kMx(q
new file mode 100644 index 0000000000000000000000000000000000000000..1b2df8093d630ca5e4b7c9127fd4fc855655599b GIT binary patch literal 9025 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
new file mode 100644 index 0000000000000000000000000000000000000000..604e652e5ec48052efa7b342c41c33729dbd287c GIT binary patch literal 503 zc$@+E0SNwyP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0005LNkl<Zc-obb zy-Px27{$$?$UZCyA`$~HWJC>7L`x76v;_%@mM9iB^r1sVf!&a+LRUjYsGy;SXlXDS zqS4Xa%|ApGMeLk-iHe)y13!4)=bZC?+^cI*oxlhzV3LU5D72Q<MRPnJ-!GL)kJW1R zov`PO8a=H}9?#`+$!4?p242AnVb2*gdj7h}U?dVbV4z;FKgVLRw8diCHJi-=_MB0p zhhD7>ti@vS4yLDQG<t>g5*Q=w6NyBM8a-G`S{uxk%jE}{?yXkqCe|j<DHz6ViyA%j zYHctN@fxOENs>0OHhj|zoKb`4hF+}=rVE9F3R6Z^Rhdr$-2y3-$y`vQ2kR-VKQN;x z3QcOQR^uk8Hk)mQu*WP-&F}XI;T`=Q9`X5ne*E)Gm=u8uQbhDhGMNm+I|q7xguPy` zr&6iJU^;I!8e!bzJfF{p@!n6^yWQ?Xco)GC==chIJf1CtWrR-<K6W~tN0_gHRj@8t z1~xDN^g2;}fk0p#;WLCo2nQVwM@W2O6Lnw|^nxyNvM89c+3YsL`v~vZ?e;_QzJ6i+ tw+vuGmgQZS%eB*Pn57q2od8n1;SVuD34zFH^ql|z002ovPDHLkV1l!1+}HpB
--- a/browser/extensions/pdfjs/content/web/viewer.css +++ b/browser/extensions/pdfjs/content/web/viewer.css @@ -39,48 +39,62 @@ select { display: none; } [hidden] { display: none !important; } #viewerContainer:-webkit-full-screen { top: 0px; - border-top: 5px solid transparent; + border-top: 2px solid transparent; background-color: #404040; background-image: url(images/texture.png); width: 100%; height: 100%; overflow: hidden; + cursor: none; } #viewerContainer:-moz-full-screen { top: 0px; - border-top: 5px solid transparent; + border-top: 2px solid transparent; background-color: #404040; background-image: url(images/texture.png); width: 100%; height: 100%; overflow: hidden; -} - -:-webkit-full-screen .page:last-child { - margin-bottom: 40px; + cursor: none; } -:-moz-full-screen .page:last-child { - margin-bottom: 40px; -} - -#viewerContainer:full-screen { +#viewerContainer:fullscreen { top: 0px; + border-top: 2px solid transparent; background-color: #404040; background-image: url(images/texture.png); width: 100%; height: 100%; + overflow: hidden; + cursor: none; +} + + +:-webkit-full-screen .page { + margin-bottom: 100%; +} + +:-moz-full-screen .page { + margin-bottom: 100%; +} + +:fullscreen .page { + margin-bottom: 100%; +} + +#viewerContainer.presentationControls { + cursor: default; } /* outer/inner center provides horizontal center */ html[dir='ltr'] .outerCenter { float: right; position: relative; right: 50%; } @@ -199,17 +213,16 @@ html[dir='ltr'] #sidebarContent { } html[dir='rtl'] #sidebarContent { right: 0; } #viewerContainer { overflow: auto; box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05); - padding-top: 30px; position: absolute; top: 32px; right: 0; bottom: 0; left: 0; } .toolbar { @@ -240,17 +253,17 @@ html[dir='rtl'] #sidebarContent { linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95)); box-shadow: inset -2px 0 0 hsla(0,0%,100%,.08), inset 0 1px 1px hsla(0,0%,0%,.15), inset 0 -1px 0 hsla(0,0%,100%,.05), 0 1px 0 hsla(0,0%,0%,.15), 0 1px 1px hsla(0,0%,0%,.1); } -#toolbarViewer { +#toolbarViewer, .findbar { position: relative; height: 32px; background-image: url(images/texture.png), -webkit-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95)); background-image: url(images/texture.png), -moz-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95)); background-image: url(images/texture.png), -ms-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95)); @@ -260,16 +273,104 @@ html[dir='rtl'] #sidebarContent { linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95)); border-left: 1px solid hsla(0,0%,0%,.5); box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08), inset 0 1px 1px hsla(0,0%,0%,.15), inset 0 -1px 0 hsla(0,0%,100%,.05), 0 1px 0 hsla(0,0%,0%,.15), 0 1px 1px hsla(0,0%,0%,.1); } + +.findbar { + top: 32px; + position: absolute; + z-index: 10000; + height: 32px; + + min-width: 16px; + padding: 0px 6px 0px 6px; + margin: 4px 2px 4px 2px; + color: hsl(0,0%,85%); + font-size: 12px; + line-height: 14px; + text-align: left; + cursor: default; +} + +html[dir='ltr'] .findbar { + left: 68px; +} + +html[dir='rtl'] .findbar { + right: 68px; +} + +.findbar label { + -webkit-user-select:none; + -moz-user-select:none; +} + +#findInput[data-status="pending"] { + background-image: url(images/loading-small.png); + background-repeat: no-repeat; + background-position: right; +} + +.doorHanger { + border: 1px solid hsla(0,0%,0%,.5); + border-radius: 2px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); +} +.doorHanger:after, .doorHanger:before { + bottom: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; +} +.doorHanger:after { + border-bottom-color: hsla(0,0%,32%,.99); + border-width: 8px; +} +.doorHanger:before { + border-bottom-color: hsla(0,0%,0%,.5); + border-width: 9px; +} + +html[dir='ltr'] .doorHanger:after { + left: 16px; + margin-left: -8px; +} + +html[dir='ltr'] .doorHanger:before { + left: 16px; + margin-left: -9px; +} + +html[dir='rtl'] .doorHanger:after { + right: 16px; + margin-right: -8px; +} + +html[dir='rtl'] .doorHanger:before { + right: 16px; + margin-right: -9px; +} + +#findMsg { + font-style: italic; + color: #A6B7D0; +} + +.notFound { + background-color: rgb(255, 137, 153); +} + html[dir='ltr'] #toolbarViewerLeft { margin-left: -1px; } html[dir='rtl'] #toolbarViewerRight { margin-left: -1px; } @@ -282,22 +383,24 @@ html[dir='rtl'] #toolbarViewerRight { html[dir='ltr'] #toolbarViewerRight, html[dir='rtl'] #toolbarViewerLeft { position: absolute; top: 0; right: 0; } html[dir='ltr'] #toolbarViewerLeft > *, html[dir='ltr'] #toolbarViewerMiddle > *, -html[dir='ltr'] #toolbarViewerRight > * { +html[dir='ltr'] #toolbarViewerRight > *, +html[dir='ltr'] .findbar > * { float: left; } html[dir='rtl'] #toolbarViewerLeft > *, html[dir='rtl'] #toolbarViewerMiddle > *, -html[dir='rtl'] #toolbarViewerRight > * { +html[dir='rtl'] #toolbarViewerRight > *, +html[dir='rtl'] .findbar > * { float: right; } html[dir='ltr'] .splitToolbarButton { margin: 3px 2px 4px 0; display: inline-block; } html[dir='rtl'] .splitToolbarButton { @@ -625,16 +728,36 @@ html[dir='rtl'] .toolbarButton:first-chi min-width: 30px; } .toolbarButton#sidebarToggle::before { display: inline-block; content: url(images/toolbarButton-sidebarToggle.png); } +html[dir='ltr'] .toolbarButton.findPrevious::before { + display: inline-block; + content: url(images/findbarButton-previous.png); +} + +html[dir='rtl'] .toolbarButton.findPrevious::before { + display: inline-block; + content: url(images/findbarButton-previous-rtl.png); +} + +html[dir='ltr'] .toolbarButton.findNext::before { + display: inline-block; + content: url(images/findbarButton-next.png); +} + +html[dir='rtl'] .toolbarButton.findNext::before { + display: inline-block; + content: url(images/findbarButton-next-rtl.png); +} + html[dir='ltr'] .toolbarButton.pageUp::before { display: inline-block; content: url(images/toolbarButton-pageUp.png); } html[dir='rtl'] .toolbarButton.pageUp::before { display: inline-block; content: url(images/toolbarButton-pageUp-rtl.png); @@ -697,17 +820,17 @@ html[dir='rtl'] .toolbarButton.pageDown: content: url(images/toolbarButton-viewThumbnail.png); } #viewOutline.toolbarButton::before { display: inline-block; content: url(images/toolbarButton-viewOutline.png); } -#viewSearch.toolbarButton::before { +#viewFind.toolbarButton::before { display: inline-block; content: url(images/toolbarButton-search.png); } .toolbarField { padding: 3px 6px; margin: 4px 0 4px 0; @@ -724,16 +847,21 @@ html[dir='rtl'] .toolbarButton.pageDown: font-size: 12px; line-height: 14px; outline-style: none; -moz-transition-property: background-color, border-color, box-shadow; -moz-transition-duration: 150ms; -moz-transition-timing-function: ease; } +.toolbarField[type=checkbox] { + display: inline-block; + margin: 8px 0px; +} + .toolbarField.pageNumber { min-width: 16px; text-align: right; width: 40px; } .toolbarField.pageNumber::-webkit-inner-spin-button, .toolbarField.pageNumber::-webkit-outer-spin-button { @@ -839,35 +967,33 @@ a:focus > .thumbnail > .thumbnailSelecti -webkit-user-select:none; -moz-user-select:none; } .outlineItem > .outlineItems { margin-left: 20px; } -.outlineItem > a, -#searchResults > a { +.outlineItem > a { text-decoration: none; display: inline-block; min-width: 95%; height: 20px; padding: 2px 0 0 10px; margin-bottom: 1px; border-radius: 2px; color: hsla(0,0%,100%,.8); font-size: 13px; line-height: 15px; -moz-user-select:none; cursor: default; white-space: nowrap; } -.outlineItem > a:hover, -#searchResults > a:hover { +.outlineItem > a:hover { background-color: hsla(0,0%,100%,.02); background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); background-clip: padding-box; box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, 0 0 1px hsla(0,0%,100%,.2) inset, 0 0 1px hsla(0,0%,0%,.2); color: hsla(0,0%,100%,.9); } @@ -884,54 +1010,24 @@ a:focus > .thumbnail > .thumbnailSelecti .noOutline, .noResults { font-size: 12px; color: hsla(0,0%,100%,.8); font-style: italic; } -#searchScrollView { +#findScrollView { position: absolute; top: 10px; bottom: 10px; left: 10px; width: 280px; } -#searchToolbar { - padding-left: 0px; - right: 0px; - padding-top: 0px; - padding-bottom: 5px; -} - -#searchToolbar > input { - margin-left: 4px; - width: 124px; -} - -#searchToolbar button { - width: auto; - margin: 0; - padding: 0 6px; - height: 22px; -} - -#searchResults { - overflow: auto; - position: absolute; - top: 30px; - bottom: 0px; - left: 0px; - right: 0; - padding: 4px 4px 0; - font-size: smaller; -} - #sidebarControls { position:absolute; width: 180px; height: 32px; left: 15px; bottom: 35px; } @@ -1064,16 +1160,40 @@ canvas { .textLayer > div { color: transparent; position: absolute; line-height:1.3; white-space:pre; } +.textLayer .highlight { + margin: -1px; + padding: 1px; + + background-color: rgba(180, 0, 170, 0.2); + border-radius: 4px; +} + +.textLayer .highlight.begin { + border-radius: 4px 0px 0px 4px; +} + +.textLayer .highlight.end { + border-radius: 0px 4px 4px 0px; +} + +.textLayer .highlight.middle { + border-radius: 0px; +} + +.textLayer .highlight.selected { + background-color: rgba(0, 100, 0, 0.2); +} + /* TODO: file FF bug to support ::-moz-selection:window-inactive so we can override the opaque grey background when the window is inactive; see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */ ::selection { background:rgba(0,0,255,0.3); } ::-moz-selection { background:rgba(0,0,255,0.3); } .annotComment > div { position: absolute; @@ -1286,17 +1406,17 @@ canvas { } html[dir='rtl'] .outerCenter { float: right; right: 180px; } } @media all and (max-width: 600px) { - #toolbarViewerRight { + #toolbarViewerRight, #findbar, #viewFind { display: none; } } @media all and (max-width: 500px) { #scaleSelectContainer, #pageNumberLabel { display: none; }
--- a/browser/extensions/pdfjs/content/web/viewer.html +++ b/browser/extensions/pdfjs/content/web/viewer.html @@ -46,17 +46,17 @@ limitations under the License. var PDFJS = {}; (function pdfjsWrapper() { // Use strict in our context only - users might not want it 'use strict'; PDFJS.build = -'e98eba1'; +'c8cf445'; /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* Copyright 2012 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -352,18 +352,27 @@ var Page = (function PageClosure() { // entry in the dictionary. if (!isValidUrl(url)) url = ''; item.url = url; break; case 'GoTo': item.dest = a.get('D'); break; + case 'GoToR': + var url = a.get('F'); + // TODO: pdf reference says that GoToR + // can also have 'NewWindow' attribute + if (!isValidUrl(url)) + url = ''; + item.url = url; + item.dest = a.get('D'); + break; default: - TODO('other link types'); + TODO('unrecognized link type: ' + a.get('S').name); } } else if (annotation.has('Dest')) { // simple destination link var dest = annotation.get('Dest'); item.dest = isName(dest) ? dest.name : dest; } break; case 'Widget': @@ -633,18 +642,16 @@ var PDFDocument = (function PDFDocumentC // Use only for debugging purposes. This should not be used in any code that is // in mozilla master. var log = (function() { if ('console' in globalScope && 'log' in globalScope['console']) { return globalScope['console']['log'].bind(globalScope['console']); - } else if ('print' in globalScope) { - return globalScope['print'].bind(globalScope); } else { return function nop() { }; } })(); // A notice for devs that will not trigger the fallback UI. These are good // for things that are helpful to devs, such as warning that Workers were @@ -897,17 +904,17 @@ var Util = PDFJS.Util = (function UtilCl var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { function PageViewport(viewBox, scale, rotate, offsetX, offsetY) { // creating transform to convert pdf coordinate system to the normal // canvas like coordinates taking in account scale and rotation var centerX = (viewBox[2] + viewBox[0]) / 2; var centerY = (viewBox[3] + viewBox[1]) / 2; var rotateA, rotateB, rotateC, rotateD; - switch (rotate) { + switch (rotate % 360) { case -180: case 180: rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1; break; case -270: case 90: rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0; break; @@ -1950,17 +1957,18 @@ var WorkerTransport = (function WorkerTr var TextRenderingMode = { FILL: 0, STROKE: 1, FILL_STROKE: 2, INVISIBLE: 3, FILL_ADD_TO_PATH: 4, STROKE_ADD_TO_PATH: 5, FILL_STROKE_ADD_TO_PATH: 6, - ADD_TO_PATH: 7 + ADD_TO_PATH: 7, + ADD_TO_PATH_FLAG: 4 }; // Minimal font size that would be used during canvas fillText operations. var MIN_FONT_SIZE = 1; function createScratchCanvas(width, height) { var canvas = document.createElement('canvas'); canvas.width = width; @@ -2336,16 +2344,20 @@ var CanvasGraphics = (function CanvasGra }, save: function CanvasGraphics_save() { this.ctx.save(); var old = this.current; this.stateStack.push(old); this.current = old.clone(); }, restore: function CanvasGraphics_restore() { + if ('textClipLayers' in this) { + this.completeTextClipping(); + } + var prev = this.stateStack.pop(); if (prev) { this.current = prev; this.ctx.restore(); } }, transform: function CanvasGraphics_transform(a, b, c, d, e, f) { this.ctx.transform(a, b, c, d, e, f); @@ -2465,16 +2477,74 @@ var CanvasGraphics = (function CanvasGra // Text beginText: function CanvasGraphics_beginText() { this.current.textMatrix = IDENTITY_MATRIX; this.current.x = this.current.lineX = 0; this.current.y = this.current.lineY = 0; }, endText: function CanvasGraphics_endText() { + if ('textClipLayers' in this) { + this.swapImageForTextClipping(); + } + }, + getCurrentTextClipping: function CanvasGraphics_getCurrentTextClipping() { + var ctx = this.ctx; + var transform = ctx.mozCurrentTransform; + if ('textClipLayers' in this) { + // we need to reset only font and transform + var maskCtx = this.textClipLayers.maskCtx; + maskCtx.setTransform.apply(maskCtx, transform); + maskCtx.font = ctx.font; + return maskCtx; + } + + var canvasWidth = ctx.canvas.width; + var canvasHeight = ctx.canvas.height; + // keeping track of the text clipping of the separate canvas + var maskCanvas = createScratchCanvas(canvasWidth, canvasHeight); + var maskCtx = maskCanvas.getContext('2d'); + maskCtx.setTransform.apply(maskCtx, transform); + maskCtx.font = ctx.font; + var textClipLayers = { + maskCanvas: maskCanvas, + maskCtx: maskCtx + }; + this.textClipLayers = textClipLayers; + return maskCtx; + }, + swapImageForTextClipping: + function CanvasGraphics_swapImageForTextClipping() { + var ctx = this.ctx; + var canvasWidth = ctx.canvas.width; + var canvasHeight = ctx.canvas.height; + // saving current image content and clearing whole canvas + ctx.save(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + var data = ctx.getImageData(0, 0, canvasWidth, canvasHeight); + this.textClipLayers.imageData = data; + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + ctx.restore(); + }, + completeTextClipping: function CanvasGraphics_completeTextClipping() { + var ctx = this.ctx; + // applying mask to the image (result is saved in maskCanvas) + var maskCtx = this.textClipLayers.maskCtx; + maskCtx.setTransform(1, 0, 0, 1, 0, 0); + maskCtx.globalCompositeOperation = 'source-in'; + maskCtx.drawImage(ctx.canvas, 0, 0); + + // restoring image data and applying the result of masked drawing + ctx.save(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.putImageData(this.textClipLayers.imageData, 0, 0); + ctx.drawImage(this.textClipLayers.maskCanvas, 0, 0); + ctx.restore(); + + delete this.textClipLayers; }, setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) { this.current.charSpacing = spacing; }, setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) { this.current.wordSpacing = spacing; }, setHScale: function CanvasGraphics_setHScale(scale) { @@ -2532,18 +2602,16 @@ var CanvasGraphics = (function CanvasGra var browserFontSize = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE; this.current.fontSizeScale = browserFontSize != MIN_FONT_SIZE ? 1.0 : size / MIN_FONT_SIZE; var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface; this.ctx.font = rule; }, setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) { - if (mode >= TextRenderingMode.FILL_ADD_TO_PATH) - TODO('unsupported text rendering mode: ' + mode); this.current.textRenderingMode = mode; }, setTextRise: function CanvasGraphics_setTextRise(rise) { this.current.textRise = rise; }, moveText: function CanvasGraphics_moveText(x, y) { this.current.x = this.current.lineX += x; this.current.y = this.current.lineY += y; @@ -2568,30 +2636,33 @@ var CanvasGraphics = (function CanvasGra var fontMatrix = current.fontMatrix || IDENTITY_MATRIX; ctx.transform.apply(ctx, current.textMatrix); ctx.scale(1, -1); ctx.translate(current.x, -current.y - current.textRise); ctx.transform.apply(ctx, fontMatrix); ctx.scale(textHScale, 1); }, - getTextGeometry: function CanvasGraphics_getTextGeometry() { + createTextGeometry: function CanvasGraphics_createTextGeometry() { var geometry = {}; var ctx = this.ctx; var font = this.current.font; var ctxMatrix = ctx.mozCurrentTransform; if (ctxMatrix) { var bl = Util.applyTransform([0, 0], ctxMatrix); var tr = Util.applyTransform([1, 1], ctxMatrix); geometry.x = bl[0]; geometry.y = bl[1]; geometry.hScale = tr[0] - bl[0]; geometry.vScale = tr[1] - bl[1]; } geometry.spaceWidth = font.spaceWidth; + geometry.fontName = font.loadedName; + geometry.fontFamily = font.fallbackName; + geometry.fontSize = this.current.fontSize; return geometry; }, showText: function CanvasGraphics_showText(str, skipTextSelection) { var ctx = this.ctx; var current = this.current; var font = current.font; var glyphs = font.charsToGlyphs(str); @@ -2615,17 +2686,17 @@ var CanvasGraphics = (function CanvasGra ctx.transform.apply(ctx, current.textMatrix); ctx.translate(current.x, current.y); ctx.scale(textHScale, 1); if (textSelection) { this.save(); ctx.scale(1, -1); - geom = this.getTextGeometry(); + geom = this.createTextGeometry(); this.restore(); } for (var i = 0; i < glyphsLength; ++i) { var glyph = glyphs[i]; if (glyph === null) { // word break this.ctx.translate(wordSpacing, 0); @@ -2656,17 +2727,17 @@ var CanvasGraphics = (function CanvasGra var lineWidth = current.lineWidth; var scale = Math.abs(current.textMatrix[0] * fontMatrix[0]); if (scale == 0 || lineWidth == 0) lineWidth = this.getSinglePixelWidth(); else lineWidth /= scale; if (textSelection) - geom = this.getTextGeometry(); + geom = this.createTextGeometry(); if (fontSizeScale != 1.0) { ctx.scale(fontSizeScale, fontSizeScale); lineWidth /= fontSizeScale; } ctx.lineWidth = lineWidth; @@ -2696,35 +2767,40 @@ var CanvasGraphics = (function CanvasGra ctx.strokeText(character, scaledX, 0); break; case TextRenderingMode.FILL_STROKE: case TextRenderingMode.FILL_STROKE_ADD_TO_PATH: ctx.fillText(character, scaledX, 0); ctx.strokeText(character, scaledX, 0); break; case TextRenderingMode.INVISIBLE: + case TextRenderingMode.ADD_TO_PATH: break; } + if (textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) { + var clipCtx = this.getCurrentTextClipping(); + clipCtx.fillText(character, scaledX, 0); + } } x += charWidth; var glyphUnicode = glyph.unicode === ' ' ? '\u00A0' : glyph.unicode; if (glyphUnicode in NormalizedUnicodes) glyphUnicode = NormalizedUnicodes[glyphUnicode]; canvasWidth += charWidth; } current.x += x * textHScale2; ctx.restore(); } if (textSelection) { geom.canvasWidth = canvasWidth; - this.textLayer.appendText(font.fallbackName, fontSize, geom); + this.textLayer.appendText(geom); } return canvasWidth; }, showSpacedText: function CanvasGraphics_showSpacedText(arr) { var ctx = this.ctx; var current = this.current; var font = current.font; @@ -2743,17 +2819,17 @@ var CanvasGraphics = (function CanvasGra // Type3 fonts - each glyph is a "mini-PDF" (see also showText) if (font.coded) { ctx.transform.apply(ctx, current.textMatrix); ctx.scale(1, -1); ctx.translate(current.x, -1 * current.y); ctx.scale(textHScale, 1); } else this.applyTextTransforms(); - geom = this.getTextGeometry(); + geom = this.createTextGeometry(); ctx.restore(); } for (var i = 0; i < arrLength; ++i) { var e = arr[i]; if (isNum(e)) { var spacingLength = -e * 0.001 * fontSize * textHScale; current.x += spacingLength; @@ -2767,17 +2843,17 @@ var CanvasGraphics = (function CanvasGra canvasWidth += shownCanvasWidth; } else { error('TJ array element ' + e + ' is not string or num'); } } if (textSelection) { geom.canvasWidth = canvasWidth; - this.textLayer.appendText(font.fallbackName, fontSize, geom); + this.textLayer.appendText(geom); } }, nextLineShowText: function CanvasGraphics_nextLineShowText(text) { this.nextLine(); this.showText(text); }, nextLineSetSpacingShowText: function CanvasGraphics_nextLineSetSpacingShowText(wordSpacing, @@ -3108,18 +3184,32 @@ var CanvasGraphics = (function CanvasGra var tmpCanvas = createScratchCanvas(w, h); var tmpCtx = tmpCanvas.getContext('2d'); this.putBinaryImageData(tmpCtx, imgData, w, h); ctx.drawImage(tmpCanvas, 0, -h); this.restore(); }, - putBinaryImageData: function CanvasGraphics_putBinaryImageData() { - // + putBinaryImageData: function CanvasGraphics_putBinaryImageData(ctx, imgData, + w, h) { + var tmpImgData = 'createImageData' in ctx ? ctx.createImageData(w, h) : + ctx.getImageData(0, 0, w, h); + + var tmpImgDataPixels = tmpImgData.data; + var data = imgData.data; + if ('set' in tmpImgDataPixels) + tmpImgDataPixels.set(data); + else { + // Copy over the imageData pixel by pixel. + for (var i = 0, ii = tmpImgDataPixels.length; i < ii; i++) + tmpImgDataPixels[i] = data[i]; + } + + ctx.putImageData(tmpImgData, 0, 0); }, // Marked content markPoint: function CanvasGraphics_markPoint(tag) { // TODO Marked content. }, markPointProps: function CanvasGraphics_markPointProps(tag, properties) { @@ -3176,57 +3266,16 @@ var CanvasGraphics = (function CanvasGra var inverse = this.ctx.mozCurrentTransformInverse; return Math.abs(inverse[0] + inverse[2]); } }; return CanvasGraphics; })(); -function checkPutBinaryImageDataCompatibility() { - // Feature detection if the browser can use an Uint8Array directly as imgData. - var canvas = document.createElement('canvas'); - canvas.width = 1; - canvas.height = 1; - var ctx = canvas.getContext('2d'); - - try { - ctx.putImageData({ - width: 1, - height: 1, - data: new Uint8Array(4) - }, 0, 0); - - CanvasGraphics.prototype.putBinaryImageData = - function CanvasGraphicsPutBinaryImageDataNative(ctx, imgData) { - ctx.putImageData(imgData, 0, 0); - }; - } catch (e) { - CanvasGraphics.prototype.putBinaryImageData = - function CanvasGraphicsPutBinaryImageDataShim(ctx, imgData, w, h) { - var tmpImgData = 'createImageData' in ctx ? ctx.createImageData(w, h) : - ctx.getImageData(0, 0, w, h); - - var tmpImgDataPixels = tmpImgData.data; - var data = imgData.data; - if ('set' in tmpImgDataPixels) - tmpImgDataPixels.set(data); - else { - // Copy over the imageData pixel by pixel. - for (var i = 0, ii = tmpImgDataPixels.length; i < ii; i++) - tmpImgDataPixels[i] = data[i]; - } - - ctx.putImageData(tmpImgData, 0, 0); - }; - } -} -if (!isWorker) { - checkPutBinaryImageDataCompatibility(); -} var Name = (function NameClosure() { function Name(name) { this.name = name; } Name.prototype = {}; @@ -13265,33 +13314,39 @@ var PartialEvaluator = (function Partial PartialEvaluator.prototype = { loadFont: function PartialEvaluator_loadFont(fontName, font, xref, resources, dependency) { var fontRes = resources.get('Font'); assert(fontRes, 'fontRes not available'); + ++this.fontIdCounter; + font = xref.fetchIfRef(font) || fontRes.get(fontName); - assertWellFormed(isDict(font)); - - ++this.fontIdCounter; + if (!isDict(font)) { + return { + translated: new ErrorFont('Font ' + fontName + ' is not available'), + loadedName: 'font_' + this.uniquePrefix + this.fontIdCounter + }; + } + var loadedName = font.loadedName; if (!loadedName) { // keep track of each font we translated so the caller can // load them asynchronously before calling display on a page loadedName = 'font_' + this.uniquePrefix + this.fontIdCounter; font.loadedName = loadedName; var translated; try { translated = this.translateFont(font, xref, resources, dependency); } catch (e) { - translated = { error: e }; + translated = new ErrorFont(e instanceof Error ? e.message : e); } font.translated = translated; var data = translated; if (data.loadCharProcs) { delete data.loadCharProcs; var charProcs = font.get('CharProcs').getAll(); @@ -13329,20 +13384,17 @@ var PartialEvaluator = (function Partial } } function handleSetFont(fontName, font) { font = self.loadFont(fontName, font, xref, resources, dependency); var loadedName = font.loadedName; if (!font.sent) { - var data = font.translated; - - if (data instanceof Font) - data = data.exportData(); + var data = font.translated.exportData(); handler.send('obj', [ loadedName, 'Font', data ]); font.sent = true; } @@ -13630,16 +13682,18 @@ var PartialEvaluator = (function Partial } return queue; }, getTextContent: function partialEvaluatorGetIRQueue( stream, resources, state) { var bidiTexts; + var kSpaceFactor = 0.35; + var kMultipleSpaceFactor = 1.5; if (!state) { bidiTexts = []; state = { bidiTexts: bidiTexts }; } else { bidiTexts = state.bidiTexts; @@ -13671,18 +13725,23 @@ var PartialEvaluator = (function Partial font = handleSetFont(args[0].name).translated; break; case 'TJ': var items = args[0]; for (var j = 0, jj = items.length; j < jj; j++) { if (typeof items[j] === 'string') { chunk += fontCharsToUnicode(items[j], font); } else if (items[j] < 0 && font.spaceWidth > 0) { - var numFakeSpaces = Math.round(-items[j] / font.spaceWidth); - if (numFakeSpaces > 0) { + var fakeSpaces = -items[j] / font.spaceWidth; + if (fakeSpaces > kMultipleSpaceFactor) { + fakeSpaces = Math.round(fakeSpaces); + while (fakeSpaces--) { + chunk += ' '; + } + } else if (fakeSpaces > kSpaceFactor) { chunk += ' '; } } } break; case 'Tj': chunk += fontCharsToUnicode(args[0], font); break; @@ -16504,65 +16563,68 @@ var Font = (function FontClosure() { var numTables = 1; var cmap = '\x00\x00' + // version string16(numTables) + // numTables '\x00\x03' + // platformID '\x00\x01' + // encodingID string32(4 + numTables * 8); // start of the table record - var segCount = ranges.length + 1; + var trailingRangesCount = ranges[ranges.length - 1][1] < 0xFFFF ? 1 : 0; + var segCount = ranges.length + trailingRangesCount; var segCount2 = segCount * 2; var searchRange = getMaxPower2(segCount) * 2; var searchEntry = Math.log(segCount) / Math.log(2); var rangeShift = 2 * segCount - searchRange; // Fill up the 4 parallel arrays describing the segments. var startCount = ''; var endCount = ''; var idDeltas = ''; var idRangeOffsets = ''; var glyphsIds = ''; var bias = 0; if (deltas) { - for (var i = 0; i < segCount - 1; i++) { + for (var i = 0, ii = ranges.length; i < ii; i++) { var range = ranges[i]; var start = range[0]; var end = range[1]; var offset = (segCount - i) * 2 + bias * 2; bias += (end - start + 1); startCount += string16(start); endCount += string16(end); idDeltas += string16(0); idRangeOffsets += string16(offset); var codes = range[2]; for (var j = 0, jj = codes.length; j < jj; ++j) glyphsIds += string16(deltas[codes[j]]); } } else { - for (var i = 0; i < segCount - 1; i++) { + for (var i = 0, ii = ranges.length; i < ii; i++) { var range = ranges[i]; var start = range[0]; var end = range[1]; var startCode = range[2][0]; startCount += string16(start); endCount += string16(end); idDeltas += string16((startCode - start + 1) & 0xFFFF); idRangeOffsets += string16(0); } } - endCount += '\xFF\xFF'; - startCount += '\xFF\xFF'; - idDeltas += '\x00\x01'; - idRangeOffsets += '\x00\x00'; + if (trailingRangesCount > 0) { + endCount += '\xFF\xFF'; + startCount += '\xFF\xFF'; + idDeltas += '\x00\x01'; + idRangeOffsets += '\x00\x00'; + } var format314 = '\x00\x00' + // language string16(segCount2) + string16(searchRange) + string16(searchEntry) + string16(rangeShift) + endCount + '\x00\x00' + startCount + idDeltas + idRangeOffsets + glyphsIds; @@ -17233,16 +17295,19 @@ var Font = (function FontClosure() { return true; } // Check that required tables are present var requiredTables = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'post']; + var optionalTables = ['cvt ', 'fpgm', 'glyf', 'loca', 'prep', + 'CFF ', 'VORG', 'vhea', 'vmtx']; + var header = readOpenTypeHeader(font); var numTables = header.numTables; var cmap, post, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf, os2; var tables = []; for (var i = 0; i < numTables; i++) { var table = readTableEntry(font); var index = requiredTables.indexOf(table.tag); @@ -17258,16 +17323,19 @@ var Font = (function FontClosure() { else if (table.tag == 'hmtx') hmtx = table; else if (table.tag == 'head') head = table; else if (table.tag == 'OS/2') os2 = table; requiredTables.splice(index, 1); + } else if (optionalTables.indexOf(table.tag) < 0) { + // skipping table if it's not a required or optional table + continue; } else { if (table.tag == 'vmtx') vmtx = table; else if (table.tag == 'vhea') vhea = table; else if (table.tag == 'loca') loca = table; else if (table.tag == 'glyf') @@ -17563,16 +17631,22 @@ var Font = (function FontClosure() { } this.useToFontChar = true; } createGlyphNameMap(glyphs, ids, properties); this.glyphNameMap = properties.glyphNameMap; } + if (glyphs.length === 0) { + // defines at least one glyph + glyphs.push({ unicode: 0xF000, code: 0xF000, glyph: '.notdef' }); + ids.push(0); + } + // Converting glyphs and ids into font's cmap table cmap.data = createCMapTable(glyphs, ids); var unicodeIsEnabled = []; for (var i = 0, ii = glyphs.length; i < ii; i++) { unicodeIsEnabled[glyphs[i].unicode] = true; } this.unicodeIsEnabled = unicodeIsEnabled; @@ -18075,16 +18149,19 @@ var Font = (function FontClosure() { var ErrorFont = (function ErrorFontClosure() { function ErrorFont(error) { this.error = error; } ErrorFont.prototype = { charsToGlyphs: function ErrorFont_charsToGlyphs() { return []; + }, + exportData: function ErrorFont_exportData() { + return {error: this.error}; } }; return ErrorFont; })(); var CallothersubrCmd = (function CallothersubrCmdClosure() { function CallothersubrCmd(index) { @@ -18298,16 +18375,31 @@ var Type1Parser = function type1Parser() charstring.push(3); i++; continue; } assert(argc == 0, 'callothersubr with arguments is not supported'); charstring.push(new CallothersubrCmd(index)); continue; + } else if (escape == 7) { // sbw + var args = breakUpArgs(charstring, 4); + var arg0 = args[0]; + var arg1 = args[1]; + var arg2 = args[2]; + lsb = arg0.value; + width = arg2.value; + // To convert to type2 we have to move the width value to the first + // part of the charstring and then use rmoveto with (dx, dy). + // The height argument will not be used for vmtx and vhea tables + // reconstruction -- ignoring it. + charstring = arg2.arg; + charstring = charstring.concat(arg0.arg, arg1.arg); + charstring.push('rmoveto'); + continue; } else if (escape == 17 || escape == 33) { // pop or setcurrentpoint commands can be ignored // since we are not doing callothersubr continue; } else if (escape == 6) { // seac is like type 2's special endchar but it doesn't use the // first argument asb, so remove it. var args = breakUpArgs(charstring, 5); @@ -19105,20 +19197,19 @@ var CFFFont = (function CFFFontClosure() var unassignedUnicodeItems = []; var inverseEncoding = []; // CID fonts don't have an encoding. if (encoding !== null) for (var charcode in encoding) inverseEncoding[encoding[charcode]] = charcode | 0; else inverseEncoding = charsets; - for (var i = 0, ii = charsets.length; i < ii; i++) { + var i = charsets[0] == '.notdef' ? 1 : 0; + for (var ii = charsets.length; i < ii; i++) { var glyph = charsets[i]; - if (glyph == '.notdef') - continue; var code = inverseEncoding[i]; if (!code || isSpecialUnicode(code)) { unassignedUnicodeItems.push(i); continue; } charstrings.push({ unicode: code, @@ -31240,16 +31331,17 @@ function MessageHandler(name, comObj) { // If there's no console available, console_error in the // action handler will do nothing. if ('console' in globalScope) { ah['console_error'] = [function ahConsoleError(data) { globalScope['console'].error.apply(null, data); }]; } else { ah['console_error'] = [function ahConsoleError(data) { + log.apply(null, data); }]; } ah['_warn'] = [function ah_Warn(data) { warn(data); }]; comObj.onmessage = function messageHandlerComObjOnMessage(event) { var data = event.data; @@ -35955,45 +36047,55 @@ var JpegImage = (function jpegImage() { <div id="sidebarContainer"> <div id="toolbarSidebar" class="splitToolbarButton toggled"> <button id="viewThumbnail" class="toolbarButton group toggled" title="Show Thumbnails" tabindex="1" data-l10n-id="thumbs"> <span data-l10n-id="thumbs_label">Thumbnails</span> </button> <button id="viewOutline" class="toolbarButton group" title="Show Document Outline" tabindex="2" data-l10n-id="outline"> <span data-l10n-id="outline_label">Document Outline</span> </button> - <button id="viewSearch" class="toolbarButton group hidden" title="Search Document" tabindex="3" data-l10n-id="search_panel"> - <span data-l10n-id="search_panel_label">Search Document</span> - </button> </div> <div id="sidebarContent"> <div id="thumbnailView"> </div> <div id="outlineView" class="hidden"> </div> - <div id="searchView" class="hidden"> - <div id="searchToolbar"> - <input id="searchTermsInput" class="toolbarField"> - <button id="searchButton" class="textButton toolbarButton" data-l10n-id="search">Find</button> - </div> - <div id="searchResults"></div> - </div> </div> </div> <!-- sidebarContainer --> <div id="mainContainer"> + <div class="findbar hidden doorHanger" id="findbar"> + <label for="findInput" class="toolbarLabel" data-l10n-id="find_label">Find:</label> + <input id="findInput" class="toolbarField" tabindex="20"> + <div class="splitToolbarButton"> + <button class="toolbarButton findPrevious" title="" id="findPrevious" tabindex="21" data-l10n-id="find_previous"> + <span data-l10n-id="find_previous_label">Previous</span> + </button> + <div class="splitToolbarButtonSeparator"></div> + <button class="toolbarButton findNext" title="" id="findNext" tabindex="22" data-l10n-id="find_next"> + <span data-l10n-id="find_next_label">Next</span> + </button> + </div> + <input type="checkbox" id="findHighlightAll" class="toolbarField"> + <label for="findHighlightAll" class="toolbarLabel" tabindex="23" data-l10n-id="find_highlight">Highlight all</label> + <input type="checkbox" id="findMatchCase" class="toolbarField"> + <label for="findMatchCase" class="toolbarLabel" tabindex="24" data-l10n-id="find_match_case_label">Match case</label> + <span id="findMsg" class="toolbarLabel"></span> + </div> <div class="toolbar"> <div id="toolbarContainer"> - <div id="toolbarViewer"> <div id="toolbarViewerLeft"> - <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="4" data-l10n-id="toggle_slider"> + <button id="sidebarToggle" class="toolbarButton" title="Toggle Sidebar" tabindex="3" data-l10n-id="toggle_slider"> <span data-l10n-id="toggle_slider_label">Toggle Sidebar</span> </button> <div class="toolbarButtonSpacer"></div> + <button id="viewFind" class="toolbarButton group" title="Find in Document" tabindex="4" data-l10n-id="findbar"> + <span data-l10n-id="findbar_label">Find</span> + </button> <div class="splitToolbarButton"> <button class="toolbarButton pageUp" title="Previous Page" id="previous" tabindex="5" data-l10n-id="previous"> <span data-l10n-id="previous_label">Previous</span> </button> <div class="splitToolbarButtonSeparator"></div> <button class="toolbarButton pageDown" title="Next Page" id="next" tabindex="6" data-l10n-id="next"> <span data-l10n-id="next_label">Next</span> </button> @@ -36053,16 +36155,20 @@ var JpegImage = (function jpegImage() { </span> </div> </div> </div> </div> </div> <menu type="context" id="viewerContextMenu"> + <menuitem label="First Page" id="first_page" + data-l10n-id="first_page" ></menuitem> + <menuitem label="Last Page" id="last_page" + data-l10n-id="last_page" ></menuitem> <menuitem label="Rotate Counter-Clockwise" id="page_rotate_ccw" data-l10n-id="page_rotate_ccw" ></menuitem> <menuitem label="Rotate Clockwise" id="page_rotate_cw" data-l10n-id="page_rotate_cw" ></menuitem> </menu> <div id="viewerContainer"> <div id="viewer" contextmenu="viewerContextMenu"></div>
--- a/browser/extensions/pdfjs/content/web/viewer.js +++ b/browser/extensions/pdfjs/content/web/viewer.js @@ -19,26 +19,33 @@ 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; +var kVerticalPadding = 5; var kMinScale = 0.25; var kMaxScale = 4.0; var kImageDirectory = './images/'; var kSettingsMemory = 20; var RenderingStates = { INITIAL: 0, RUNNING: 1, PAUSED: 2, FINISHED: 3 }; +var FindStates = { + FIND_FOUND: 0, + FIND_NOTFOUND: 1, + FIND_WRAPPED: 2, + FIND_PENDING: 3 +}; var mozL10n = document.mozL10n || document.webL10n; function getFileName(url) { var anchor = url.indexOf('#'); var query = url.indexOf('?'); var end = Math.min( @@ -200,77 +207,479 @@ var FirefoxCom = (function FirefoxComClo } }; })(); // Settings Manager - This is a utility for saving settings // First we see if localStorage is available // If not, we use FUEL in FF +// Use asyncStorage for B2G var Settings = (function SettingsClosure() { var isLocalStorageEnabled = (function localStorageEnabledTest() { // Feature test as per http://diveintohtml5.info/storage.html // The additional localStorage call is to get around a FF quirk, see // bug #495747 in bugzilla try { return 'localStorage' in window && window['localStorage'] !== null && localStorage; } catch (e) { return false; } })(); function Settings(fingerprint) { - var database = null; - var index; - database = FirefoxCom.requestSync('getDatabase', null) || '{}'; - - database = JSON.parse(database); - if (!('files' in database)) - database.files = []; - if (database.files.length >= kSettingsMemory) - database.files.shift(); - for (var i = 0, length = database.files.length; i < length; i++) { - var branch = database.files[i]; - if (branch.fingerprint == fingerprint) { - index = i; - break; - } - } - if (typeof index != 'number') - index = database.files.push({fingerprint: fingerprint}) - 1; - this.file = database.files[index]; - this.database = database; + this.fingerprint = fingerprint; + this.initializedPromise = new PDFJS.Promise(); + + var resolvePromise = (function settingsResolvePromise(db) { + this.initialize(db || '{}'); + this.initializedPromise.resolve(); + }).bind(this); + + + resolvePromise(FirefoxCom.requestSync('getDatabase', null)); + } Settings.prototype = { + initialize: function settingsInitialize(database) { + database = JSON.parse(database); + if (!('files' in database)) + database.files = []; + if (database.files.length >= kSettingsMemory) + database.files.shift(); + var index; + for (var i = 0, length = database.files.length; i < length; i++) { + var branch = database.files[i]; + if (branch.fingerprint == this.fingerprint) { + index = i; + break; + } + } + if (typeof index != 'number') + index = database.files.push({fingerprint: this.fingerprint}) - 1; + this.file = database.files[index]; + this.database = database; + }, + set: function settingsSet(name, val) { - if (!('file' in this)) - return false; + if (!this.initializedPromise.isResolved) + return; var file = this.file; file[name] = val; var database = JSON.stringify(this.database); FirefoxCom.requestSync('setDatabase', database); }, get: function settingsGet(name, defaultValue) { - if (!('file' in this)) + if (!this.initializedPromise.isResolved) return defaultValue; return this.file[name] || defaultValue; } }; return Settings; })(); var cache = new Cache(kCacheSize); var currentPageNumber = 1; +var PDFFindController = { + extractTextPromise: null, + + // If active, find results will be highlighted. + active: false, + + // Stores the text for each page. + pageContents: [], + + pageMatches: [], + + selected: { + pageIdx: 0, + matchIdx: 0 + }, + + state: null, + + dirtyMatch: false, + + findTimeout: null, + + initialize: function() { + var events = [ + 'find', + 'findagain', + 'findhighlightallchange', + 'findcasesensitivitychange' + ]; + + this.handleEvent = this.handleEvent.bind(this); + + for (var i = 0; i < events.length; i++) { + window.addEventListener(events[i], this.handleEvent); + } + }, + + calcFindMatch: function(pageContent) { + var query = this.state.query; + var caseSensitive = this.state.caseSensitive; + var queryLen = query.length; + + if (queryLen === 0) + return []; + + if (!caseSensitive) { + pageContent = pageContent.toLowerCase(); + query = query.toLowerCase(); + } + + var matches = []; + + var matchIdx = -queryLen; + while (true) { + matchIdx = pageContent.indexOf(query, matchIdx + queryLen); + if (matchIdx === -1) { + break; + } + + matches.push(matchIdx); + } + return matches; + }, + + extractText: function() { + if (this.extractTextPromise) { + return this.extractTextPromise; + } + this.extractTextPromise = new PDFJS.Promise(); + + var self = this; + function extractPageText(pageIndex) { + PDFView.pages[pageIndex].getTextContent().then( + function textContentResolved(data) { + // Build the find string. + var bidiTexts = data.bidiTexts; + var str = ''; + + for (var i = 0; i < bidiTexts.length; i++) { + str += bidiTexts[i].str; + } + + // Store the pageContent as a string. + self.pageContents.push(str); + // Ensure there is a empty array of matches. + self.pageMatches.push([]); + + if ((pageIndex + 1) < PDFView.pages.length) + extractPageText(pageIndex + 1); + else + self.extractTextPromise.resolve(); + } + ); + } + extractPageText(0); + return this.extractTextPromise; + }, + + handleEvent: function(e) { + if (this.state === null || e.type !== 'findagain') { + this.dirtyMatch = true; + } + this.state = e.detail; + this.updateUIState(FindStates.FIND_PENDING); + + var promise = this.extractText(); + + clearTimeout(this.findTimeout); + if (e.type === 'find') { + // Only trigger the find action after 250ms of silence. + this.findTimeout = setTimeout(function() { + promise.then(this.performFind.bind(this)); + }.bind(this), 250); + } else { + promise.then(this.performFind.bind(this)); + } + }, + + updatePage: function(idx) { + var page = PDFView.pages[idx]; + + if (this.selected.pageIdx === idx) { + // If the page is selected, scroll the page into view, which triggers + // rendering the page, which adds the textLayer. Once the textLayer is + // build, it will scroll onto the selected match. + page.scrollIntoView(); + } + + if (page.textLayer) { + page.textLayer.updateMatches(); + } + }, + + performFind: function() { + // Recalculate all the matches. + // TODO: Make one match show up as the current match + + var pages = PDFView.pages; + var pageContents = this.pageContents; + var pageMatches = this.pageMatches; + + this.active = true; + + if (this.dirtyMatch) { + // Need to recalculate the matches. + this.dirtyMatch = false; + + this.selected = { + pageIdx: -1, + matchIdx: -1 + }; + + // TODO: Make this way more lasily (aka. efficient) - e.g. calculate only + // the matches for the current visible pages. + var firstMatch = true; + for (var i = 0; i < pageContents.length; i++) { + var matches = pageMatches[i] = this.calcFindMatch(pageContents[i]); + if (firstMatch && matches.length !== 0) { + firstMatch = false; + this.selected = { + pageIdx: i, + matchIdx: 0 + }; + } + this.updatePage(i, true); + } + if (!firstMatch || !this.state.query) { + this.updateUIState(FindStates.FIND_FOUND); + } else { + this.updateUIState(FindStates.FIND_NOTFOUND); + } + } else { + // If there is NO selection, then there is no match at all -> no sense to + // handle previous/next action. + if (this.selected.pageIdx === -1) { + this.updateUIState(FindStates.FIND_NOTFOUND); + return; + } + + // Handle findAgain case. + var previous = this.state.findPrevious; + var sPageIdx = this.selected.pageIdx; + var sMatchIdx = this.selected.matchIdx; + var findState = FindStates.FIND_FOUND; + + if (previous) { + // Select previous match. + + if (sMatchIdx !== 0) { + this.selected.matchIdx -= 1; + } else { + var len = pageMatches.length; + for (var i = sPageIdx - 1; i != sPageIdx; i--) { + if (i < 0) + i += len; + + if (pageMatches[i].length !== 0) { + this.selected = { + pageIdx: i, + matchIdx: pageMatches[i].length - 1 + }; + break; + } + } + // If pageIdx stayed the same, select last match on the page. + if (this.selected.pageIdx === sPageIdx) { + this.selected.matchIdx = pageMatches[sPageIdx].length - 1; + findState = FindStates.FIND_WRAPPED; + } else if (this.selected.pageIdx > sPageIdx) { + findState = FindStates.FIND_WRAPPED; + } + } + } else { + // Select next match. + + if (pageMatches[sPageIdx].length !== sMatchIdx + 1) { + this.selected.matchIdx += 1; + } else { + var len = pageMatches.length; + for (var i = sPageIdx + 1; i < len + sPageIdx; i++) { + if (pageMatches[i % len].length !== 0) { + this.selected = { + pageIdx: i % len, + matchIdx: 0 + }; + break; + } + } + + // If pageIdx stayed the same, select first match on the page. + if (this.selected.pageIdx === sPageIdx) { + this.selected.matchIdx = 0; + findState = FindStates.FIND_WRAPPED; + } else if (this.selected.pageIdx < sPageIdx) { + findState = FindStates.FIND_WRAPPED; + } + } + } + + this.updateUIState(findState, previous); + this.updatePage(sPageIdx, sPageIdx === this.selected.pageIdx); + if (sPageIdx !== this.selected.pageIdx) { + this.updatePage(this.selected.pageIdx, true); + } + } + }, + + updateUIState: function(state, previous) { + if (PDFView.supportsIntegratedFind) { + FirefoxCom.request('updateFindControlState', + {result: state, findPrevious: previous}); + return; + } + PDFFindBar.updateUIState(state, previous); + } +}; + +var PDFFindBar = { + // TODO: Enable the FindBar *AFTER* the pagesPromise in the load function + // got resolved + + opened: false, + + initialize: function() { + this.bar = document.getElementById('findbar'); + this.toggleButton = document.getElementById('viewFind'); + this.findField = document.getElementById('findInput'); + this.highlightAll = document.getElementById('findHighlightAll'); + this.caseSensitive = document.getElementById('findMatchCase'); + this.findMsg = document.getElementById('findMsg'); + this.findStatusIcon = document.getElementById('findStatusIcon'); + + var self = this; + this.toggleButton.addEventListener('click', function() { + self.toggle(); + }); + + this.findField.addEventListener('input', function() { + self.dispatchEvent(''); + }); + + this.bar.addEventListener('keydown', function(evt) { + switch (evt.keyCode) { + case 13: // Enter + if (evt.target === self.findField) { + self.dispatchEvent('again', evt.shiftKey); + } + break; + case 27: // Escape + self.close(); + break; + } + }); + + document.getElementById('findPrevious').addEventListener('click', + function() { self.dispatchEvent('again', true); } + ); + + document.getElementById('findNext').addEventListener('click', function() { + self.dispatchEvent('again', false); + }); + + this.highlightAll.addEventListener('click', function() { + self.dispatchEvent('highlightallchange'); + }); + + this.caseSensitive.addEventListener('click', function() { + self.dispatchEvent('casesensitivitychange'); + }); + }, + + dispatchEvent: function(aType, aFindPrevious) { + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('find' + aType, true, true, { + query: this.findField.value, + caseSensitive: this.caseSensitive.checked, + highlightAll: this.highlightAll.checked, + findPrevious: aFindPrevious + }); + return window.dispatchEvent(event); + }, + + updateUIState: function(state, previous) { + var notFound = false; + var findMsg = ''; + var status = ''; + + switch (state) { + case FindStates.FIND_FOUND: + break; + + case FindStates.FIND_PENDING: + status = 'pending'; + break; + + case FindStates.FIND_NOTFOUND: + findMsg = mozL10n.get('find_not_found', null, 'Phrase not found'); + notFound = true; + break; + + case FindStates.FIND_WRAPPED: + if (previous) { + findMsg = mozL10n.get('find_wrapped_to_bottom', null, + 'Reached end of page, continued from bottom'); + } else { + findMsg = mozL10n.get('find_wrapped_to_top', null, + 'Reached end of page, continued from top'); + } + break; + } + + if (notFound) { + this.findField.classList.add('notFound'); + } else { + this.findField.classList.remove('notFound'); + } + + this.findField.setAttribute('data-status', status); + this.findMsg.textContent = findMsg; + }, + + open: function() { + if (this.opened) return; + + this.opened = true; + this.toggleButton.classList.add('toggled'); + this.bar.classList.remove('hidden'); + this.findField.select(); + this.findField.focus(); + }, + + close: function() { + if (!this.opened) return; + + this.opened = false; + this.toggleButton.classList.remove('toggled'); + this.bar.classList.add('hidden'); + + PDFFindController.active = false; + }, + + toggle: function() { + if (this.opened) { + this.close(); + } else { + this.open(); + } + } +}; + var PDFView = { pages: [], thumbnails: [], currentScale: kUnknownScale, currentScaleValue: null, initialBookmark: document.location.hash.substring(1), startedTextExtraction: false, pageText: [], @@ -280,31 +689,36 @@ var PDFView = { fellback: false, pdfDocument: null, sidebarOpen: false, pageViewScroll: null, thumbnailViewScroll: null, isFullscreen: false, previousScale: null, pageRotation: 0, + mouseScrollTimeStamp: 0, + mouseScrollDelta: 0, lastScroll: 0, // called once when the document is loaded initialize: function pdfViewInitialize() { var self = this; var container = this.container = document.getElementById('viewerContainer'); this.pageViewScroll = {}; this.watchScroll(container, this.pageViewScroll, updateViewarea); var thumbnailContainer = this.thumbnailContainer = document.getElementById('thumbnailView'); this.thumbnailViewScroll = {}; this.watchScroll(thumbnailContainer, this.thumbnailViewScroll, this.renderHighestPriority.bind(this)); + PDFFindBar.initialize(); + PDFFindController.initialize(); + this.initialized = true; container.addEventListener('scroll', function() { self.lastScroll = Date.now(); }, false); }, // Helper function to keep track whether a div was scrolled up or down and // then call a callback. @@ -351,20 +765,23 @@ var PDFView = { this.currentScaleValue = value; if (scale) { this.setScale(scale, true, noScroll); return; } var container = this.container; var currentPage = this.pages[this.page - 1]; + if (!currentPage) { + return; + } var pageWidthScale = (container.clientWidth - kScrollbarPadding) / currentPage.width * currentPage.scale / kCssUnits; - var pageHeightScale = (container.clientHeight - kScrollbarPadding) / + var pageHeightScale = (container.clientHeight - kVerticalPadding) / currentPage.height * currentPage.scale / kCssUnits; switch (value) { case 'page-actual': scale = 1; break; case 'page-width': scale = pageWidthScale; break; @@ -445,16 +862,26 @@ var PDFView = { doc.webkitRequestFullScreen; Object.defineProperty(this, 'supportsFullScreen', { value: support, enumerable: true, configurable: true, writable: false }); return support; }, + get supportsIntegratedFind() { + var support = false; + support = FirefoxCom.requestSync('supportsIntegratedFind'); + Object.defineProperty(this, 'supportsIntegratedFind', { value: support, + enumerable: true, + configurable: true, + writable: false }); + return support; + }, + initPassiveLoading: function pdfViewInitPassiveLoading() { if (!PDFView.loadingBar) { PDFView.loadingBar = new ProgressBar('#loadingBar', {}); } window.addEventListener('message', function window_message(e) { var args = e.data; @@ -705,30 +1132,23 @@ var PDFView = { clearInterval(thumbsView._loadingInterval); var container = document.getElementById('viewer'); while (container.hasChildNodes()) container.removeChild(container.lastChild); var pagesCount = pdfDocument.numPages; var id = pdfDocument.fingerprint; - var storedHash = null; document.getElementById('numPages').textContent = mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}'); document.getElementById('pageNumber').max = pagesCount; + PDFView.documentFingerprint = id; var store = PDFView.store = new Settings(id); - if (store.get('exists', false)) { - var page = store.get('page', '1'); - var zoom = store.get('zoom', PDFView.currentScale); - var left = store.get('scrollLeft', '0'); - var top = store.get('scrollTop', '0'); - - storedHash = 'page=' + page + '&zoom=' + zoom + ',' + left + ',' + top; - } + var storePromise = store.initializedPromise; this.pageRotation = 0; var pages = this.pages = []; this.pageText = []; this.startedTextExtraction = false; var pagesRefMap = {}; var thumbnails = this.thumbnails = []; @@ -755,21 +1175,32 @@ var PDFView = { }); var destinationsPromise = pdfDocument.getDestinations(); destinationsPromise.then(function(destinations) { self.destinations = destinations; }); // outline and initial view depends on destinations and pagesRefMap - PDFJS.Promise.all([pagesPromise, destinationsPromise]).then(function() { + var promises = [pagesPromise, destinationsPromise, storePromise]; + PDFJS.Promise.all(promises).then(function() { pdfDocument.getOutline().then(function(outline) { self.outline = new DocumentOutlineView(outline); }); + var storedHash = null; + if (store.get('exists', false)) { + var page = store.get('page', '1'); + var zoom = store.get('zoom', PDFView.currentScale); + var left = store.get('scrollLeft', '0'); + var top = store.get('scrollTop', '0'); + + storedHash = 'page=' + page + '&zoom=' + zoom + ',' + left + ',' + top; + } + self.setInitialView(storedHash, scale); }); pdfDocument.getMetadata().then(function(data) { var info = data.info, metadata = data.metadata; self.documentInfo = info; self.metadata = metadata; @@ -887,82 +1318,16 @@ var PDFView = { case RenderingStates.INITIAL: PDFView.highestPriorityPage = type + view.id; view.draw(this.renderHighestPriority.bind(this)); break; } return true; }, - search: function pdfViewStartSearch() { - // Limit this function to run every <SEARCH_TIMEOUT>ms. - var SEARCH_TIMEOUT = 250; - var lastSearch = this.lastSearch; - var now = Date.now(); - if (lastSearch && (now - lastSearch) < SEARCH_TIMEOUT) { - if (!this.searchTimer) { - this.searchTimer = setTimeout(function resumeSearch() { - PDFView.search(); - }, - SEARCH_TIMEOUT - (now - lastSearch) - ); - } - return; - } - this.searchTimer = null; - this.lastSearch = now; - - function bindLink(link, pageNumber) { - link.href = '#' + pageNumber; - link.onclick = function searchBindLink() { - PDFView.page = pageNumber; - return false; - }; - } - - var searchResults = document.getElementById('searchResults'); - - var searchTermsInput = document.getElementById('searchTermsInput'); - searchResults.removeAttribute('hidden'); - searchResults.textContent = ''; - - var terms = searchTermsInput.value; - - if (!terms) - return; - - // simple search: removing spaces and hyphens, then scanning every - terms = terms.replace(/\s-/g, '').toLowerCase(); - var index = PDFView.pageText; - var pageFound = false; - for (var i = 0, ii = index.length; i < ii; i++) { - var pageText = index[i].replace(/\s-/g, '').toLowerCase(); - var j = pageText.indexOf(terms); - if (j < 0) - continue; - - var pageNumber = i + 1; - var textSample = index[i].substr(j, 50); - var link = document.createElement('a'); - bindLink(link, pageNumber); - link.textContent = 'Page ' + pageNumber + ': ' + textSample; - searchResults.appendChild(link); - - pageFound = true; - } - if (!pageFound) { - searchResults.textContent = ''; - var noResults = document.createElement('div'); - noResults.classList.add('noResults'); - noResults.textContent = mozL10n.get('search_terms_not_found', null, - '(Not found)'); - searchResults.appendChild(noResults); - } - }, - setHash: function pdfViewSetHash(hash) { if (!hash) return; if (hash.indexOf('=') >= 0) { var params = PDFView.parseQueryString(hash); // borrowing syntax from "Parameters for Opening PDF Files" if ('nameddest' in params) { @@ -994,80 +1359,42 @@ var PDFView = { this.page = hash; else // named destination PDFView.navigateTo(unescape(hash)); }, switchSidebarView: function pdfViewSwitchSidebarView(view) { var thumbsView = document.getElementById('thumbnailView'); var outlineView = document.getElementById('outlineView'); - var searchView = document.getElementById('searchView'); var thumbsButton = document.getElementById('viewThumbnail'); var outlineButton = document.getElementById('viewOutline'); - var searchButton = document.getElementById('viewSearch'); switch (view) { case 'thumbs': thumbsButton.classList.add('toggled'); outlineButton.classList.remove('toggled'); - searchButton.classList.remove('toggled'); thumbsView.classList.remove('hidden'); outlineView.classList.add('hidden'); - searchView.classList.add('hidden'); PDFView.renderHighestPriority(); break; case 'outline': thumbsButton.classList.remove('toggled'); outlineButton.classList.add('toggled'); - searchButton.classList.remove('toggled'); thumbsView.classList.add('hidden'); outlineView.classList.remove('hidden'); - searchView.classList.add('hidden'); if (outlineButton.getAttribute('disabled')) return; break; - - case 'search': - thumbsButton.classList.remove('toggled'); - outlineButton.classList.remove('toggled'); - searchButton.classList.add('toggled'); - thumbsView.classList.add('hidden'); - outlineView.classList.add('hidden'); - searchView.classList.remove('hidden'); - - var searchTermsInput = document.getElementById('searchTermsInput'); - searchTermsInput.focus(); - // Start text extraction as soon as the search gets displayed. - this.extractText(); - break; } }, - extractText: function() { - if (this.startedTextExtraction) - return; - this.startedTextExtraction = true; - var self = this; - function extractPageText(pageIndex) { - self.pages[pageIndex].pdfPage.getTextContent().then( - function textContentResolved(textContent) { - self.pageText[pageIndex] = textContent.join(''); - self.search(); - if ((pageIndex + 1) < self.pages.length) - extractPageText(pageIndex + 1); - } - ); - } - extractPageText(0); - }, - getVisiblePages: function pdfViewGetVisiblePages() { return this.getVisibleElements(this.container, this.pages, true); }, getVisibleThumbs: function pdfViewGetVisibleThumbs() { return this.getVisibleElements(this.thumbnailContainer, this.thumbnails); @@ -1188,23 +1515,51 @@ var PDFView = { this.previousScale = this.currentScaleValue; this.parseScale('page-fit', true); // Wait for fullscreen to take effect setTimeout(function() { currentPage.scrollIntoView(); }, 0); + this.showPresentationControls(); return true; }, exitFullscreen: function pdfViewExitFullscreen() { this.isFullscreen = false; this.parseScale(this.previousScale); this.page = this.page; + this.clearMouseScrollState(); + this.hidePresentationControls(); + }, + + showPresentationControls: function pdfViewShowPresentationControls() { + var DELAY_BEFORE_HIDING_CONTROLS = 3000; + var wrapper = document.getElementById('viewerContainer'); + if (this.presentationControlsTimeout) { + clearTimeout(this.presentationControlsTimeout); + } else { + wrapper.classList.add('presentationControls'); + } + this.presentationControlsTimeout = setTimeout(function hideControls() { + wrapper.classList.remove('presentationControls'); + delete PDFView.presentationControlsTimeout; + }, DELAY_BEFORE_HIDING_CONTROLS); + }, + + hidePresentationControls: function pdfViewShowPresentationControls() { + if (!this.presentationControlsTimeout) { + return; + } + clearTimeout(this.presentationControlsTimeout); + delete this.presentationControlsTimeout; + + var wrapper = document.getElementById('viewerContainer'); + wrapper.classList.remove('presentationControls'); }, rotatePages: function pdfViewPageRotation(delta) { this.pageRotation = (this.pageRotation + 360 + delta) % 360; for (var i = 0, l = this.pages.length; i < l; i++) { var page = this.pages[i]; @@ -1221,41 +1576,107 @@ var PDFView = { this.parseScale(this.currentScaleValue, true); this.renderHighestPriority(); // Wait for fullscreen to take effect setTimeout(function() { currentPage.scrollIntoView(); }, 0); + }, + + /** + * This function flips the page in presentation mode if the user scrolls up + * or down with large enough motion and prevents page flipping too often. + * + * @this {PDFView} + * @param {number} mouseScrollDelta The delta value from the mouse event. + */ + mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) { + var MOUSE_SCROLL_COOLDOWN_TIME = 50; + + var currentTime = (new Date()).getTime(); + var storedTime = this.mouseScrollTimeStamp; + + // In case one page has already been flipped there is a cooldown time + // which has to expire before next page can be scrolled on to. + if (currentTime > storedTime && + currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) + return; + + // In case the user decides to scroll to the opposite direction than before + // clear the accumulated delta. + if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) || + (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) + this.clearMouseScrollState(); + + this.mouseScrollDelta += mouseScrollDelta; + + var PAGE_FLIP_THRESHOLD = 120; + if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) { + + var PageFlipDirection = { + UP: -1, + DOWN: 1 + }; + + // In fullscreen mode scroll one page at a time. + var pageFlipDirection = (this.mouseScrollDelta > 0) ? + PageFlipDirection.UP : + PageFlipDirection.DOWN; + this.clearMouseScrollState(); + var currentPage = this.page; + + // In case we are already on the first or the last page there is no need + // to do anything. + if ((currentPage == 1 && pageFlipDirection == PageFlipDirection.UP) || + (currentPage == this.pages.length && + pageFlipDirection == PageFlipDirection.DOWN)) + return; + + this.page += pageFlipDirection; + this.mouseScrollTimeStamp = currentTime; + } + }, + + /** + * This function clears the member attributes used with mouse scrolling in + * presentation mode. + * + * @this {PDFView} + */ + clearMouseScrollState: function pdfViewClearMouseScrollState() { + this.mouseScrollTimeStamp = 0; + this.mouseScrollDelta = 0; } }; var PageView = function pageView(container, pdfPage, id, scale, stats, navigateTo) { this.id = id; this.pdfPage = pdfPage; this.rotation = 0; this.scale = scale || 1.0; this.viewport = this.pdfPage.getViewport(this.scale, this.pdfPage.rotate); this.renderingState = RenderingStates.INITIAL; this.resume = null; this.textContent = null; + this.textLayer = null; var anchor = document.createElement('a'); anchor.name = '' + this.id; var div = this.el = document.createElement('div'); div.id = 'pageContainer' + this.id; div.className = 'page'; - div.style.width = this.viewport.width + 'px'; - div.style.height = this.viewport.height + 'px'; + div.style.width = Math.floor(this.viewport.width) + 'px'; + div.style.height = Math.floor(this.viewport.height) + 'px'; container.appendChild(anchor); container.appendChild(div); this.destroy = function pageViewDestroy() { this.update(); this.pdfPage.destroy(); }; @@ -1269,18 +1690,18 @@ var PageView = function pageView(contain } this.scale = scale || this.scale; var totalRotation = (this.rotation + this.pdfPage.rotate) % 360; var viewport = this.pdfPage.getViewport(this.scale, totalRotation); this.viewport = viewport; - div.style.width = viewport.width + 'px'; - div.style.height = viewport.height + 'px'; + div.style.width = Math.floor(viewport.width) + 'px'; + div.style.height = Math.floor(viewport.height) + 'px'; while (div.hasChildNodes()) div.removeChild(div.lastChild); div.removeAttribute('data-loaded'); delete this.canvas; this.loadingIconDiv = document.createElement('div'); @@ -1483,21 +1904,22 @@ var PageView = function pageView(contain this.canvas = canvas; var textLayerDiv = null; if (!PDFJS.disableTextLayer) { textLayerDiv = document.createElement('div'); textLayerDiv.className = 'textLayer'; div.appendChild(textLayerDiv); } - var textLayer = textLayerDiv ? new TextLayerBuilder(textLayerDiv) : null; + var textLayer = this.textLayer = + textLayerDiv ? new TextLayerBuilder(textLayerDiv, this.id - 1) : null; var scale = this.scale, viewport = this.viewport; - canvas.width = viewport.width; - canvas.height = viewport.height; + canvas.width = Math.floor(viewport.width); + canvas.height = Math.floor(viewport.height); var ctx = canvas.getContext('2d'); ctx.save(); ctx.fillStyle = 'rgb(255, 255, 255)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); // Rendering area @@ -1842,60 +2264,70 @@ var CustomStyle = (function CustomStyleC var prop = this.getProp(propName); if (prop != 'undefined') element.style[prop] = str; }; return CustomStyle; })(); -var TextLayerBuilder = function textLayerBuilder(textLayerDiv) { +var TextLayerBuilder = function textLayerBuilder(textLayerDiv, pageIdx) { var textLayerFrag = document.createDocumentFragment(); + this.textLayerDiv = textLayerDiv; this.layoutDone = false; this.divContentDone = false; + this.pageIdx = pageIdx; + this.matches = []; this.beginLayout = function textLayerBuilderBeginLayout() { this.textDivs = []; this.textLayerQueue = []; + this.renderingDone = false; }; this.endLayout = function textLayerBuilderEndLayout() { this.layoutDone = true; this.insertDivContent(); - }, + }; this.renderLayer = function textLayerBuilderRenderLayer() { var self = this; var textDivs = this.textDivs; var textLayerDiv = this.textLayerDiv; var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); // No point in rendering so many divs as it'd make the browser unusable // even after the divs are rendered - if (textDivs.length > 100000) + var MAX_TEXT_DIVS_TO_RENDER = 100000; + if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) return; - while (textDivs.length > 0) { - var textDiv = textDivs.shift(); + for (var i = 0, ii = textDivs.length; i < ii; i++) { + var textDiv = textDivs[i]; textLayerFrag.appendChild(textDiv); ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily; var width = ctx.measureText(textDiv.textContent).width; if (width > 0) { var textScale = textDiv.dataset.canvasWidth / width; CustomStyle.setProp('transform' , textDiv, 'scale(' + textScale + ', 1)'); CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%'); + + textLayerDiv.appendChild(textDiv); } } + this.renderingDone = true; + this.updateMatches(); + textLayerDiv.appendChild(textLayerFrag); }; this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() { // Schedule renderLayout() if user has been scrolling, otherwise // run it right away var kRenderDelay = 200; // in ms var self = this; @@ -1907,27 +2339,26 @@ var TextLayerBuilder = function textLaye if (this.renderTimer) clearTimeout(this.renderTimer); this.renderTimer = setTimeout(function() { self.setupRenderLayoutTimer(); }, kRenderDelay); } }; - this.appendText = function textLayerBuilderAppendText(fontName, fontSize, - geom) { + this.appendText = function textLayerBuilderAppendText(geom) { var textDiv = document.createElement('div'); // vScale and hScale already contain the scaling to pixel units - var fontHeight = fontSize * geom.vScale; + var fontHeight = geom.fontSize * geom.vScale; textDiv.dataset.canvasWidth = geom.canvasWidth * geom.hScale; - textDiv.dataset.fontName = fontName; + textDiv.dataset.fontName = geom.fontName; textDiv.style.fontSize = fontHeight + 'px'; - textDiv.style.fontFamily = fontName; + textDiv.style.fontFamily = geom.fontFamily; textDiv.style.left = geom.x + 'px'; textDiv.style.top = (geom.y - fontHeight) + 'px'; // The content of the div is set in the `setTextContent` function. this.textDivs.push(textDiv); }; @@ -1952,16 +2383,211 @@ var TextLayerBuilder = function textLaye this.setupRenderLayoutTimer(); }; this.setTextContent = function textLayerBuilderSetTextContent(textContent) { this.textContent = textContent; this.insertDivContent(); }; + + this.convertMatches = function textLayerBuilderConvertMatches(matches) { + var i = 0; + var iIndex = 0; + var bidiTexts = this.textContent.bidiTexts; + var end = bidiTexts.length - 1; + var queryLen = PDFFindController.state.query.length; + + var lastDivIdx = -1; + var pos; + + var ret = []; + + // Loop over all the matches. + for (var m = 0; m < matches.length; m++) { + var matchIdx = matches[m]; + // # Calculate the begin position. + + // Loop over the divIdxs. + while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) { + iIndex += bidiTexts[i].str.length; + i++; + } + + // TODO: Do proper handling here if something goes wrong. + if (i == bidiTexts.length) { + console.error('Could not find matching mapping'); + } + + var match = { + begin: { + divIdx: i, + offset: matchIdx - iIndex + } + }; + + // # Calculate the end position. + matchIdx += queryLen; + + // Somewhat same array as above, but use a > instead of >= to get the end + // position right. + while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) { + iIndex += bidiTexts[i].str.length; + i++; + } + + match.end = { + divIdx: i, + offset: matchIdx - iIndex + }; + ret.push(match); + } + + return ret; + }; + + this.renderMatches = function textLayerBuilder_renderMatches(matches) { + // Early exit if there is nothing to render. + if (matches.length === 0) { + return; + } + + var bidiTexts = this.textContent.bidiTexts; + var textDivs = this.textDivs; + var prevEnd = null; + var isSelectedPage = this.pageIdx === PDFFindController.selected.pageIdx; + var selectedMatchIdx = PDFFindController.selected.matchIdx; + var highlightAll = PDFFindController.state.highlightAll; + + var infty = { + divIdx: -1, + offset: undefined + }; + + function beginText(begin, className) { + var divIdx = begin.divIdx; + var div = textDivs[divIdx]; + div.innerHTML = ''; + + var content = bidiTexts[divIdx].str.substring(0, begin.offset); + var node = document.createTextNode(content); + if (className) { + var isSelected = isSelectedPage && + divIdx === selectedMatchIdx; + var span = document.createElement('span'); + span.className = className + (isSelected ? ' selected' : ''); + span.appendChild(node); + div.appendChild(span); + return; + } + div.appendChild(node); + } + + function appendText(from, to, className) { + var divIdx = from.divIdx; + var div = textDivs[divIdx]; + + var content = bidiTexts[divIdx].str.substring(from.offset, to.offset); + var node = document.createTextNode(content); + if (className) { + var span = document.createElement('span'); + span.className = className; + span.appendChild(node); + div.appendChild(span); + return; + } + div.appendChild(node); + } + + function highlightDiv(divIdx, className) { + textDivs[divIdx].className = className; + } + + var i0 = selectedMatchIdx, i1 = i0 + 1, i; + + if (highlightAll) { + i0 = 0; + i1 = matches.length; + } else if (!isSelectedPage) { + // Not highlighting all and this isn't the selected page, so do nothing. + return; + } + + for (i = i0; i < i1; i++) { + var match = matches[i]; + var begin = match.begin; + var end = match.end; + + var isSelected = isSelectedPage && i === selectedMatchIdx; + var highlightSuffix = (isSelected ? ' selected' : ''); + if (isSelected) + scrollIntoView(textDivs[begin.divIdx], {top: -50}); + + // Match inside new div. + if (!prevEnd || begin.divIdx !== prevEnd.divIdx) { + // If there was a previous div, then add the text at the end + if (prevEnd !== null) { + appendText(prevEnd, infty); + } + // clears the divs and set the content until the begin point. + beginText(begin); + } else { + appendText(prevEnd, begin); + } + + if (begin.divIdx === end.divIdx) { + appendText(begin, end, 'highlight' + highlightSuffix); + } else { + appendText(begin, infty, 'highlight begin' + highlightSuffix); + for (var n = begin.divIdx + 1; n < end.divIdx; n++) { + highlightDiv(n, 'highlight middle' + highlightSuffix); + } + beginText(end, 'highlight end' + highlightSuffix); + } + prevEnd = end; + } + + if (prevEnd) { + appendText(prevEnd, infty); + } + }; + + this.updateMatches = function textLayerUpdateMatches() { + // Only show matches, once all rendering is done. + if (!this.renderingDone) + return; + + // Clear out all matches. + var matches = this.matches; + var textDivs = this.textDivs; + var bidiTexts = this.textContent.bidiTexts; + var clearedUntilDivIdx = -1; + + // Clear out all current matches. + for (var i = 0; i < matches.length; i++) { + var match = matches[i]; + var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx); + for (var n = begin; n <= match.end.divIdx; n++) { + var div = textDivs[n]; + div.textContent = bidiTexts[n].str; + div.className = ''; + } + clearedUntilDivIdx = match.end.divIdx + 1; + } + + if (!PDFFindController.active) + return; + + // Convert the matches on the page controller into the match format used + // for the textLayer. + this.matches = matches = + this.convertMatches(PDFFindController.pageMatches[this.pageIdx] || []); + + this.renderMatches(this.matches); + }; }; document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) { PDFView.initialize(); var params = PDFView.parseQueryString(document.location.search.substring(1)); var file = window.location.toString() @@ -1992,28 +2618,28 @@ document.addEventListener('DOMContentLoa if ('pdfBug' in hashParams && FirefoxCom.requestSync('pdfBugEnabled')) { PDFJS.pdfBug = true; var pdfBug = hashParams['pdfBug']; var enabled = pdfBug.split(','); PDFBug.enable(enabled); PDFBug.init(); } - if (FirefoxCom.requestSync('searchEnabled')) { - document.querySelector('#viewSearch').classList.remove('hidden'); - } - if (!PDFView.supportsPrinting) { document.getElementById('print').classList.add('hidden'); } if (!PDFView.supportsFullscreen) { document.getElementById('fullscreen').classList.add('hidden'); } + if (PDFView.supportsIntegratedFind) { + document.querySelector('#viewFind').classList.add('hidden'); + } + // Listen for warnings to trigger the fallback UI. Errors should be caught // and call PDFView.error() so we don't need to listen for those. PDFJS.LogManager.addLogger({ warn: function() { PDFView.fallback(); } }); @@ -2042,26 +2668,16 @@ document.addEventListener('DOMContentLoa PDFView.switchSidebarView('thumbs'); }); document.getElementById('viewOutline').addEventListener('click', function() { PDFView.switchSidebarView('outline'); }); - document.getElementById('viewSearch').addEventListener('click', - function() { - PDFView.switchSidebarView('search'); - }); - - document.getElementById('searchButton').addEventListener('click', - function() { - PDFView.search(); - }); - document.getElementById('previous').addEventListener('click', function() { PDFView.page--; }); document.getElementById('next').addEventListener('click', function() { PDFView.page++; @@ -2092,58 +2708,64 @@ document.addEventListener('DOMContentLoa window.print(); }); document.getElementById('download').addEventListener('click', function() { PDFView.download(); }); - document.getElementById('searchTermsInput').addEventListener('keydown', - function(event) { - if (event.keyCode == 13) { - PDFView.search(); - } - }); - document.getElementById('pageNumber').addEventListener('change', function() { PDFView.page = this.value; }); document.getElementById('scaleSelect').addEventListener('change', function() { PDFView.parseScale(this.value); }); + document.getElementById('first_page').addEventListener('click', + function() { + PDFView.page = 1; + }); + + document.getElementById('last_page').addEventListener('click', + function() { + PDFView.page = PDFView.pdfDocument.numPages; + }); + document.getElementById('page_rotate_ccw').addEventListener('click', - function() { - PDFView.rotatePages(-90); - }); + function() { + PDFView.rotatePages(-90); + }); document.getElementById('page_rotate_cw').addEventListener('click', - function() { - PDFView.rotatePages(90); - }); + function() { + PDFView.rotatePages(90); + }); if (FirefoxCom.requestSync('getLoadingType') == 'passive') { PDFView.setTitleUsingUrl(file); PDFView.initPassiveLoading(); return; } PDFView.open(file, 0); }, true); function updateViewarea() { if (!PDFView.initialized) return; var visible = PDFView.getVisiblePages(); var visiblePages = visible.views; + if (visiblePages.length === 0) { + return; + } PDFView.renderHighestPriority(); var currentId = PDFView.page; var firstPage = visible.first; for (var i = 0, ii = visiblePages.length, stillFullyVisible = false; i < ii; ++i) { @@ -2177,21 +2799,23 @@ function updateViewarea() { var pdfOpenParams = '#page=' + pageNumber; pdfOpenParams += '&zoom=' + normalizedScaleValue; var currentPage = PDFView.pages[pageNumber - 1]; var topLeft = currentPage.getPagePoint(PDFView.container.scrollLeft, (PDFView.container.scrollTop - firstPage.y)); pdfOpenParams += ',' + Math.round(topLeft[0]) + ',' + Math.round(topLeft[1]); var store = PDFView.store; - store.set('exists', true); - store.set('page', pageNumber); - store.set('zoom', normalizedScaleValue); - store.set('scrollLeft', Math.round(topLeft[0])); - store.set('scrollTop', Math.round(topLeft[1])); + store.initializedPromise.then(function() { + store.set('exists', true); + store.set('page', pageNumber); + store.set('zoom', normalizedScaleValue); + store.set('scrollLeft', Math.round(topLeft[0])); + store.set('scrollTop', Math.round(topLeft[1])); + }); var href = PDFView.getAnchorUrl(pdfOpenParams); document.getElementById('viewBookmark').href = href; } window.addEventListener('resize', function webViewerResize(evt) { if (PDFView.initialized && (document.getElementById('pageWidthOption').selected || document.getElementById('pageFitOption').selected || @@ -2296,30 +2920,54 @@ window.addEventListener('pagechange', fu window.addEventListener('DOMMouseScroll', function(evt) { if (evt.ctrlKey) { evt.preventDefault(); var ticks = evt.detail; var direction = (ticks > 0) ? 'zoomOut' : 'zoomIn'; for (var i = 0, length = Math.abs(ticks); i < length; i++) PDFView[direction](); + } else if (PDFView.isFullscreen) { + var FIREFOX_DELTA_FACTOR = -40; + PDFView.mouseScroll(evt.detail * FIREFOX_DELTA_FACTOR); + } +}, false); + +window.addEventListener('mousemove', function keydown(evt) { + if (PDFView.isFullscreen) { + PDFView.showPresentationControls(); + } +}, false); + +window.addEventListener('mousedown', function mousedown(evt) { + if (PDFView.isFullscreen && evt.button === 0) { + // Mouse click in fullmode advances a page + evt.preventDefault(); + + PDFView.page++; } }, false); window.addEventListener('keydown', function keydown(evt) { var handled = false; var cmd = (evt.ctrlKey ? 1 : 0) | (evt.altKey ? 2 : 0) | (evt.shiftKey ? 4 : 0) | (evt.metaKey ? 8 : 0); // First, handle the key bindings that are independent whether an input // control is selected or not. if (cmd == 1 || cmd == 8) { // either CTRL or META key. switch (evt.keyCode) { + case 70: + if (!PDFView.supportsIntegratedFind) { + PDFFindBar.toggle(); + handled = true; + } + break; case 61: // FF/Mac '=' case 107: // FF '+' and '=' case 187: // Chrome '+' PDFView.zoomIn(); handled = true; break; case 173: // FF/Mac '-' case 109: // FF '-' @@ -2329,51 +2977,85 @@ window.addEventListener('keydown', funct break; case 48: // '0' PDFView.parseScale(kDefaultScale, true); handled = true; break; } } + // CTRL or META with or without SHIFT. + if (cmd == 1 || cmd == 8 || cmd == 5 || cmd == 12) { + switch (evt.keyCode) { + case 71: // g + if (!PDFView.supportsIntegratedFind) { + PDFFindBar.dispatchEvent('again', cmd == 5 || cmd == 12); + handled = true; + } + break; + } + } + if (handled) { evt.preventDefault(); return; } // Some shortcuts should not get handled if a control/input element // is selected. var curElement = document.activeElement; - if (curElement && curElement.tagName == 'INPUT') + if (curElement && (curElement.tagName == 'INPUT' || + curElement.tagName == 'SELECT')) { return; - var controlsElement = document.getElementById('controls'); + } + var controlsElement = document.getElementById('toolbar'); while (curElement) { if (curElement === controlsElement && !PDFView.isFullscreen) - return; // ignoring if the 'controls' element is focused + return; // ignoring if the 'toolbar' element is focused curElement = curElement.parentNode; } if (cmd == 0) { // no control key pressed at all. switch (evt.keyCode) { + case 38: // up arrow + case 33: // pg up + case 8: // backspace + if (!PDFView.isFullscreen) { + break; + } + // in fullscreen mode falls throw here case 37: // left arrow case 75: // 'k' case 80: // 'p' PDFView.page--; handled = true; break; + case 40: // down arrow + case 34: // pg down + case 32: // spacebar + if (!PDFView.isFullscreen) { + break; + } + // in fullscreen mode falls throw here case 39: // right arrow case 74: // 'j' case 78: // 'n' PDFView.page++; handled = true; break; - case 32: // spacebar + case 36: // home if (PDFView.isFullscreen) { - PDFView.page++; + PDFView.page = 1; + handled = true; + } + break; + case 35: // end + if (PDFView.isFullscreen) { + PDFView.page = PDFView.pdfDocument.numPages; handled = true; } break; case 82: // 'r' PDFView.rotatePages(90); break; } @@ -2384,16 +3066,17 @@ window.addEventListener('keydown', funct case 82: // 'r' PDFView.rotatePages(-90); break; } } if (handled) { evt.preventDefault(); + PDFView.clearMouseScrollState(); } }); window.addEventListener('beforeprint', function beforePrint(evt) { PDFView.beforePrint(); }); window.addEventListener('afterprint', function afterPrint(evt) {
--- a/browser/extensions/pdfjs/extension-files +++ b/browser/extensions/pdfjs/extension-files @@ -1,16 +1,21 @@ chrome.manifest components/PdfStreamConverter.js content/PdfJs.jsm content/web/debugger.js content/web/images/annotation-check.svg content/web/images/annotation-comment.svg content/web/images/annotation-text.svg +content/web/images/findbarButton-next-rtl.png +content/web/images/findbarButton-next.png +content/web/images/findbarButton-previous-rtl.png +content/web/images/findbarButton-previous.png content/web/images/loading-icon.gif +content/web/images/loading-small.png content/web/images/texture.png content/web/images/toolbarButton-bookmark.png content/web/images/toolbarButton-download.png content/web/images/toolbarButton-fullscreen.png content/web/images/toolbarButton-menuArrows.png content/web/images/toolbarButton-openFile.png content/web/images/toolbarButton-pageDown-rtl.png content/web/images/toolbarButton-pageDown.png
--- a/browser/locales/en-US/pdfviewer/chrome.properties +++ b/browser/locales/en-US/pdfviewer/chrome.properties @@ -1,8 +1,4 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - # Chrome notification bar messages and buttons unsupported_feature=This PDF document might not be displayed correctly. open_with_different_viewer=Open With Different Viewer open_with_different_viewer.accessKey=o
--- a/browser/locales/en-US/pdfviewer/viewer.properties +++ b/browser/locales/en-US/pdfviewer/viewer.properties @@ -1,12 +1,8 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - # Main toolbar buttons (tooltips and alt text for images) previous.title=Previous Page previous_label=Previous next.title=Next Page next_label=Next # LOCALIZATION NOTE (page_label, page_of): # These strings are concatenated to form the "Page: X of Y" string. @@ -35,37 +31,47 @@ bookmark_label=Current View # (the _label strings are alt text for the buttons, the .title strings are # tooltips) toggle_slider.title=Toggle Slider toggle_slider_label=Toggle Slider outline.title=Show Document Outline outline_label=Document Outline thumbs.title=Show Thumbnails thumbs_label=Thumbnails -search_panel.title=Search Document -search_panel_label=Search +findbar.title=Find in Document +findbar_label=Find # Document outline messages no_outline=No Outline Available # Thumbnails panel item (tooltip and alt text for images) # LOCALIZATION NOTE (thumb_page_title): "{{page}}" will be replaced by the page # number. thumb_page_title=Page {{page}} # LOCALIZATION NOTE (thumb_page_canvas): "{{page}}" will be replaced by the page # number. thumb_page_canvas=Thumbnail of Page {{page}} # Context menu +first_page.label=Go to First Page +last_page.label=Go to Last Page page_rotate_cw.label=Rotate Clockwise -page_rotate_ccw.label=Rotate Counter-Clockwise +page_rotate_ccw.label=Rotate Counterclockwise -# Search panel button title and messages -search=Find -search_terms_not_found=(Not found) +# Find panel button title and messages +find_label=Find: +find_previous.title=Find the previous occurrence of the phrase +find_previous_label=Previous +find_next.title=Find the next occurrence of the phrase +find_next_label=Next +find_highlight=Highlight all +find_match_case_label=Match case +find_wrapped_to_bottom=Reached end of page, continued from bottom +find_wrapped_to_top=Reached end of page, continued from top +find_not_found=Phrase not found # Error panel labels error_more_info=More Information error_less_info=Less Information error_close=Close # LOCALIZATION NOTE (error_build): "{{build}}" will be replaced by the PDF.JS # build ID. error_build=PDF.JS Build: {{build}}