Merge pull request #8483 from timvandermeij/es6-find-controller
Convert the find controller to ES6 syntax
This commit is contained in:
		
						commit
						fccb0b5cf8
					
				| @ -13,7 +13,7 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import { FindStates } from './pdf_find_controller'; | import { FindState } from './pdf_find_controller'; | ||||||
| import { NullL10n } from './ui_utils'; | import { NullL10n } from './ui_utils'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -108,19 +108,19 @@ class PDFFindBar { | |||||||
|     var status = ''; |     var status = ''; | ||||||
| 
 | 
 | ||||||
|     switch (state) { |     switch (state) { | ||||||
|       case FindStates.FIND_FOUND: |       case FindState.FOUND: | ||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|       case FindStates.FIND_PENDING: |       case FindState.PENDING: | ||||||
|         status = 'pending'; |         status = 'pending'; | ||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|       case FindStates.FIND_NOTFOUND: |       case FindState.NOT_FOUND: | ||||||
|         findMsg = this.l10n.get('find_not_found', null, 'Phrase not found'); |         findMsg = this.l10n.get('find_not_found', null, 'Phrase not found'); | ||||||
|         notFound = true; |         notFound = true; | ||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|       case FindStates.FIND_WRAPPED: |       case FindState.WRAPPED: | ||||||
|         if (previous) { |         if (previous) { | ||||||
|           findMsg = this.l10n.get('find_reached_top', null, |           findMsg = this.l10n.get('find_reached_top', null, | ||||||
|             'Reached top of document, continued from bottom'); |             'Reached top of document, continued from bottom'); | ||||||
|  | |||||||
| @ -16,17 +16,18 @@ | |||||||
| import { createPromiseCapability } from 'pdfjs-lib'; | import { createPromiseCapability } from 'pdfjs-lib'; | ||||||
| import { scrollIntoView } from './ui_utils'; | import { scrollIntoView } from './ui_utils'; | ||||||
| 
 | 
 | ||||||
| var FindStates = { | const FindState = { | ||||||
|   FIND_FOUND: 0, |   FOUND: 0, | ||||||
|   FIND_NOTFOUND: 1, |   NOT_FOUND: 1, | ||||||
|   FIND_WRAPPED: 2, |   WRAPPED: 2, | ||||||
|   FIND_PENDING: 3, |   PENDING: 3, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| var FIND_SCROLL_OFFSET_TOP = -50; | const FIND_SCROLL_OFFSET_TOP = -50; | ||||||
| var FIND_SCROLL_OFFSET_LEFT = -400; | const FIND_SCROLL_OFFSET_LEFT = -400; | ||||||
|  | const FIND_TIMEOUT = 250; // ms
 | ||||||
| 
 | 
 | ||||||
| var CHARACTERS_TO_NORMALIZE = { | const CHARACTERS_TO_NORMALIZE = { | ||||||
|   '\u2018': '\'', // Left single quotation mark
 |   '\u2018': '\'', // Left single quotation mark
 | ||||||
|   '\u2019': '\'', // Right single quotation mark
 |   '\u2019': '\'', // Right single quotation mark
 | ||||||
|   '\u201A': '\'', // Single low-9 quotation mark
 |   '\u201A': '\'', // Single low-9 quotation mark
 | ||||||
| @ -41,12 +42,11 @@ var CHARACTERS_TO_NORMALIZE = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Provides "search" or "find" functionality for the PDF. |  * Provides search functionality to find a given string in a PDF document. | ||||||
|  * This object actually performs the search for a given string. |  | ||||||
|  */ |  */ | ||||||
| var PDFFindController = (function PDFFindControllerClosure() { | class PDFFindController { | ||||||
|   function PDFFindController(options) { |   constructor({ pdfViewer, }) { | ||||||
|     this.pdfViewer = options.pdfViewer || null; |     this.pdfViewer = pdfViewer; | ||||||
| 
 | 
 | ||||||
|     this.onUpdateResultsCount = null; |     this.onUpdateResultsCount = null; | ||||||
|     this.onUpdateState = null; |     this.onUpdateState = null; | ||||||
| @ -54,434 +54,430 @@ var PDFFindController = (function PDFFindControllerClosure() { | |||||||
|     this.reset(); |     this.reset(); | ||||||
| 
 | 
 | ||||||
|     // Compile the regular expression for text normalization once.
 |     // Compile the regular expression for text normalization once.
 | ||||||
|     var replace = Object.keys(CHARACTERS_TO_NORMALIZE).join(''); |     let replace = Object.keys(CHARACTERS_TO_NORMALIZE).join(''); | ||||||
|     this.normalizationRegex = new RegExp('[' + replace + ']', 'g'); |     this.normalizationRegex = new RegExp('[' + replace + ']', 'g'); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   PDFFindController.prototype = { |   reset() { | ||||||
|     reset: function PDFFindController_reset() { |     this.startedTextExtraction = false; | ||||||
|       this.startedTextExtraction = false; |     this.extractTextPromises = []; | ||||||
|       this.extractTextPromises = []; |     this.pendingFindMatches = Object.create(null); | ||||||
|       this.pendingFindMatches = Object.create(null); |     this.active = false; // If active, find results will be highlighted.
 | ||||||
|       this.active = false; // If active, find results will be highlighted.
 |     this.pageContents = []; // Stores the text for each page.
 | ||||||
|       this.pageContents = []; // Stores the text for each page.
 |     this.pageMatches = []; | ||||||
|       this.pageMatches = []; |     this.pageMatchesLength = null; | ||||||
|       this.pageMatchesLength = null; |     this.matchCount = 0; | ||||||
|       this.matchCount = 0; |     this.selected = { // Currently selected match.
 | ||||||
|       this.selected = { // Currently selected match.
 |       pageIdx: -1, | ||||||
|         pageIdx: -1, |       matchIdx: -1, | ||||||
|         matchIdx: -1, |     }; | ||||||
|       }; |     this.offset = { // Where the find algorithm currently is in the document.
 | ||||||
|       this.offset = { // Where the find algorithm currently is in the document.
 |       pageIdx: null, | ||||||
|         pageIdx: null, |       matchIdx: null, | ||||||
|         matchIdx: null, |     }; | ||||||
|       }; |     this.pagesToSearch = null; | ||||||
|       this.pagesToSearch = null; |     this.resumePageIdx = null; | ||||||
|       this.resumePageIdx = null; |     this.state = null; | ||||||
|       this.state = null; |     this.dirtyMatch = false; | ||||||
|       this.dirtyMatch = false; |     this.findTimeout = null; | ||||||
|       this.findTimeout = null; |  | ||||||
| 
 | 
 | ||||||
|       this._firstPagePromise = new Promise((resolve) => { |     this._firstPagePromise = new Promise((resolve) => { | ||||||
|         this.resolveFirstPage = resolve; |       this.resolveFirstPage = resolve; | ||||||
|       }); |     }); | ||||||
|     }, |   } | ||||||
| 
 | 
 | ||||||
|     normalize: function PDFFindController_normalize(text) { |   normalize(text) { | ||||||
|       return text.replace(this.normalizationRegex, function (ch) { |     return text.replace(this.normalizationRegex, function (ch) { | ||||||
|         return CHARACTERS_TO_NORMALIZE[ch]; |       return CHARACTERS_TO_NORMALIZE[ch]; | ||||||
|       }); |     }); | ||||||
|     }, |   } | ||||||
| 
 | 
 | ||||||
|     // Helper for multiple search - fills matchesWithLength array
 |   /** | ||||||
|     // and takes into account cases when one search term
 |    * Helper for multi-term search that fills the `matchesWithLength` array | ||||||
|     // include another search term (for example, "tamed tame" or "this is").
 |    * and handles cases where one search term includes another search term (for | ||||||
|     // Looking for intersecting terms in the 'matches' and
 |    * example, "tamed tame" or "this is"). It looks for intersecting terms in | ||||||
|     // leave elements with a longer match-length.
 |    * the `matches` and keeps elements with a longer match length. | ||||||
|  |    */ | ||||||
|  |   _prepareMatches(matchesWithLength, matches, matchesLength) { | ||||||
|  |     function isSubTerm(matchesWithLength, currentIndex) { | ||||||
|  |       let currentElem = matchesWithLength[currentIndex]; | ||||||
|  |       let nextElem = matchesWithLength[currentIndex + 1]; | ||||||
| 
 | 
 | ||||||
|     _prepareMatches: function PDFFindController_prepareMatches( |       // Check for cases like "TAMEd TAME".
 | ||||||
|         matchesWithLength, matches, matchesLength) { |       if (currentIndex < matchesWithLength.length - 1 && | ||||||
|  |           currentElem.match === nextElem.match) { | ||||||
|  |         currentElem.skipped = true; | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
| 
 | 
 | ||||||
|       function isSubTerm(matchesWithLength, currentIndex) { |       // Check for cases like "thIS IS".
 | ||||||
|         var currentElem, prevElem, nextElem; |       for (let i = currentIndex - 1; i >= 0; i--) { | ||||||
|         currentElem = matchesWithLength[currentIndex]; |         let prevElem = matchesWithLength[i]; | ||||||
|         nextElem = matchesWithLength[currentIndex + 1]; |         if (prevElem.skipped) { | ||||||
|         // checking for cases like "TAMEd TAME"
 |           continue; | ||||||
|         if (currentIndex < matchesWithLength.length - 1 && |         } | ||||||
|             currentElem.match === nextElem.match) { |         if (prevElem.match + prevElem.matchLength < currentElem.match) { | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |         if (prevElem.match + prevElem.matchLength >= | ||||||
|  |             currentElem.match + currentElem.matchLength) { | ||||||
|           currentElem.skipped = true; |           currentElem.skipped = true; | ||||||
|           return true; |           return true; | ||||||
|         } |         } | ||||||
|         // checking for cases like "thIS IS"
 |  | ||||||
|         for (var i = currentIndex - 1; i >= 0; i--) { |  | ||||||
|           prevElem = matchesWithLength[i]; |  | ||||||
|           if (prevElem.skipped) { |  | ||||||
|             continue; |  | ||||||
|           } |  | ||||||
|           if (prevElem.match + prevElem.matchLength < currentElem.match) { |  | ||||||
|             break; |  | ||||||
|           } |  | ||||||
|           if (prevElem.match + prevElem.matchLength >= |  | ||||||
|               currentElem.match + currentElem.matchLength) { |  | ||||||
|             currentElem.skipped = true; |  | ||||||
|             return true; |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|       } |       } | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|       var i, len; |     // Sort the array of `{ match: <match>, matchLength: <matchLength> }`
 | ||||||
|       // Sorting array of objects { match: <match>, matchLength: <matchLength> }
 |     // objects on increasing index first and on the length otherwise.
 | ||||||
|       // in increasing index first and then the lengths.
 |     matchesWithLength.sort(function(a, b) { | ||||||
|       matchesWithLength.sort(function(a, b) { |       return a.match === b.match ? a.matchLength - b.matchLength : | ||||||
|         return a.match === b.match ? |                                    a.match - b.match; | ||||||
|         a.matchLength - b.matchLength : a.match - b.match; |     }); | ||||||
|       }); |     for (let i = 0, len = matchesWithLength.length; i < len; i++) { | ||||||
|       for (i = 0, len = matchesWithLength.length; i < len; i++) { |       if (isSubTerm(matchesWithLength, i)) { | ||||||
|         if (isSubTerm(matchesWithLength, i)) { |         continue; | ||||||
|           continue; |  | ||||||
|         } |  | ||||||
|         matches.push(matchesWithLength[i].match); |  | ||||||
|         matchesLength.push(matchesWithLength[i].matchLength); |  | ||||||
|       } |       } | ||||||
|     }, |       matches.push(matchesWithLength[i].match); | ||||||
|  |       matchesLength.push(matchesWithLength[i].matchLength); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|     calcFindPhraseMatch: function PDFFindController_calcFindPhraseMatch( |   calcFindPhraseMatch(query, pageIndex, pageContent) { | ||||||
|       query, pageIndex, pageContent) { |     let matches = []; | ||||||
|       var matches = []; |     let queryLen = query.length; | ||||||
|       var queryLen = query.length; |     let matchIdx = -queryLen; | ||||||
|       var matchIdx = -queryLen; |     while (true) { | ||||||
|  |       matchIdx = pageContent.indexOf(query, matchIdx + queryLen); | ||||||
|  |       if (matchIdx === -1) { | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       matches.push(matchIdx); | ||||||
|  |     } | ||||||
|  |     this.pageMatches[pageIndex] = matches; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   calcFindWordMatch(query, pageIndex, pageContent) { | ||||||
|  |     let matchesWithLength = []; | ||||||
|  |     // Divide the query into pieces and search for text in each piece.
 | ||||||
|  |     let queryArray = query.match(/\S+/g); | ||||||
|  |     for (let i = 0, len = queryArray.length; i < len; i++) { | ||||||
|  |       let subquery = queryArray[i]; | ||||||
|  |       let subqueryLen = subquery.length; | ||||||
|  |       let matchIdx = -subqueryLen; | ||||||
|       while (true) { |       while (true) { | ||||||
|         matchIdx = pageContent.indexOf(query, matchIdx + queryLen); |         matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen); | ||||||
|         if (matchIdx === -1) { |         if (matchIdx === -1) { | ||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|         matches.push(matchIdx); |         // Other searches do not, so we store the length.
 | ||||||
|       } |         matchesWithLength.push({ | ||||||
|       this.pageMatches[pageIndex] = matches; |           match: matchIdx, | ||||||
|     }, |           matchLength: subqueryLen, | ||||||
| 
 |           skipped: false, | ||||||
|     calcFindWordMatch: function PDFFindController_calcFindWordMatch( |  | ||||||
|       query, pageIndex, pageContent) { |  | ||||||
|       var matchesWithLength = []; |  | ||||||
|       // Divide the query into pieces and search for text on each piece.
 |  | ||||||
|       var queryArray = query.match(/\S+/g); |  | ||||||
|       var subquery, subqueryLen, matchIdx; |  | ||||||
|       for (var i = 0, len = queryArray.length; i < len; i++) { |  | ||||||
|         subquery = queryArray[i]; |  | ||||||
|         subqueryLen = subquery.length; |  | ||||||
|         matchIdx = -subqueryLen; |  | ||||||
|         while (true) { |  | ||||||
|           matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen); |  | ||||||
|           if (matchIdx === -1) { |  | ||||||
|             break; |  | ||||||
|           } |  | ||||||
|           // Other searches do not, so we store the length.
 |  | ||||||
|           matchesWithLength.push({ |  | ||||||
|             match: matchIdx, |  | ||||||
|             matchLength: subqueryLen, |  | ||||||
|             skipped: false, |  | ||||||
|           }); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       // Prepare arrays for store the matches.
 |  | ||||||
|       if (!this.pageMatchesLength) { |  | ||||||
|         this.pageMatchesLength = []; |  | ||||||
|       } |  | ||||||
|       this.pageMatchesLength[pageIndex] = []; |  | ||||||
|       this.pageMatches[pageIndex] = []; |  | ||||||
|       // Sort matchesWithLength, clean up intersecting terms
 |  | ||||||
|       // and put the result into the two arrays.
 |  | ||||||
|       this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex], |  | ||||||
|         this.pageMatchesLength[pageIndex]); |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     calcFindMatch: function PDFFindController_calcFindMatch(pageIndex) { |  | ||||||
|       var pageContent = this.normalize(this.pageContents[pageIndex]); |  | ||||||
|       var query = this.normalize(this.state.query); |  | ||||||
|       var caseSensitive = this.state.caseSensitive; |  | ||||||
|       var phraseSearch = this.state.phraseSearch; |  | ||||||
|       var queryLen = query.length; |  | ||||||
| 
 |  | ||||||
|       if (queryLen === 0) { |  | ||||||
|         // Do nothing: the matches should be wiped out already.
 |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (!caseSensitive) { |  | ||||||
|         pageContent = pageContent.toLowerCase(); |  | ||||||
|         query = query.toLowerCase(); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (phraseSearch) { |  | ||||||
|         this.calcFindPhraseMatch(query, pageIndex, pageContent); |  | ||||||
|       } else { |  | ||||||
|         this.calcFindWordMatch(query, pageIndex, pageContent); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       this.updatePage(pageIndex); |  | ||||||
|       if (this.resumePageIdx === pageIndex) { |  | ||||||
|         this.resumePageIdx = null; |  | ||||||
|         this.nextPageMatch(); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       // Update the matches count
 |  | ||||||
|       if (this.pageMatches[pageIndex].length > 0) { |  | ||||||
|         this.matchCount += this.pageMatches[pageIndex].length; |  | ||||||
|         this.updateUIResultsCount(); |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     extractText() { |  | ||||||
|       if (this.startedTextExtraction) { |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       this.startedTextExtraction = true; |  | ||||||
|       this.pageContents.length = 0; |  | ||||||
| 
 |  | ||||||
|       let promise = Promise.resolve(); |  | ||||||
|       for (let i = 0, ii = this.pdfViewer.pagesCount; i < ii; i++) { |  | ||||||
|         let extractTextCapability = createPromiseCapability(); |  | ||||||
|         this.extractTextPromises[i] = extractTextCapability.promise; |  | ||||||
| 
 |  | ||||||
|         promise = promise.then(() => { |  | ||||||
|           return this.pdfViewer.getPageTextContent(i).then((textContent) => { |  | ||||||
|             let textItems = textContent.items; |  | ||||||
|             let strBuf = []; |  | ||||||
| 
 |  | ||||||
|             for (let j = 0, jj = textItems.length; j < jj; j++) { |  | ||||||
|               strBuf.push(textItems[j].str); |  | ||||||
|             } |  | ||||||
|             // Store the pageContent as a string.
 |  | ||||||
|             this.pageContents[i] = strBuf.join(''); |  | ||||||
|             extractTextCapability.resolve(i); |  | ||||||
|           }); |  | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|     }, |     } | ||||||
| 
 | 
 | ||||||
|     executeCommand: function PDFFindController_executeCommand(cmd, state) { |     // Prepare arrays for storing the matches.
 | ||||||
|       if (this.state === null || cmd !== 'findagain') { |     if (!this.pageMatchesLength) { | ||||||
|         this.dirtyMatch = true; |       this.pageMatchesLength = []; | ||||||
|       } |     } | ||||||
|       this.state = state; |     this.pageMatchesLength[pageIndex] = []; | ||||||
|       this.updateUIState(FindStates.FIND_PENDING); |     this.pageMatches[pageIndex] = []; | ||||||
| 
 | 
 | ||||||
|       this._firstPagePromise.then(() => { |     // Sort `matchesWithLength`, remove intersecting terms and put the result
 | ||||||
|         this.extractText(); |     // into the two arrays.
 | ||||||
|  |     this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex], | ||||||
|  |       this.pageMatchesLength[pageIndex]); | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|         clearTimeout(this.findTimeout); |   calcFindMatch(pageIndex) { | ||||||
|         if (cmd === 'find') { |     let pageContent = this.normalize(this.pageContents[pageIndex]); | ||||||
|           // Only trigger the find action after 250ms of silence.
 |     let query = this.normalize(this.state.query); | ||||||
|           this.findTimeout = setTimeout(this.nextMatch.bind(this), 250); |     let caseSensitive = this.state.caseSensitive; | ||||||
|         } else { |     let phraseSearch = this.state.phraseSearch; | ||||||
|           this.nextMatch(); |     let queryLen = query.length; | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|     }, |  | ||||||
| 
 | 
 | ||||||
|     updatePage: function PDFFindController_updatePage(index) { |     if (queryLen === 0) { | ||||||
|       if (this.selected.pageIdx === index) { |       // Do nothing: the matches should be wiped out already.
 | ||||||
|         // If the page is selected, scroll the page into view, which triggers
 |       return; | ||||||
|         // rendering the page, which adds the textLayer. Once the textLayer is
 |     } | ||||||
|         // build, it will scroll onto the selected match.
 |  | ||||||
|         this.pdfViewer.currentPageNumber = index + 1; |  | ||||||
|       } |  | ||||||
| 
 | 
 | ||||||
|       var page = this.pdfViewer.getPageView(index); |     if (!caseSensitive) { | ||||||
|       if (page.textLayer) { |       pageContent = pageContent.toLowerCase(); | ||||||
|         page.textLayer.updateMatches(); |       query = query.toLowerCase(); | ||||||
|       } |     } | ||||||
|     }, |  | ||||||
| 
 | 
 | ||||||
|     nextMatch: function PDFFindController_nextMatch() { |     if (phraseSearch) { | ||||||
|       var previous = this.state.findPrevious; |       this.calcFindPhraseMatch(query, pageIndex, pageContent); | ||||||
|       var currentPageIndex = this.pdfViewer.currentPageNumber - 1; |     } else { | ||||||
|       var numPages = this.pdfViewer.pagesCount; |       this.calcFindWordMatch(query, pageIndex, pageContent); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|       this.active = true; |     this.updatePage(pageIndex); | ||||||
| 
 |     if (this.resumePageIdx === pageIndex) { | ||||||
|       if (this.dirtyMatch) { |       this.resumePageIdx = null; | ||||||
|         // Need to recalculate the matches, reset everything.
 |  | ||||||
|         this.dirtyMatch = false; |  | ||||||
|         this.selected.pageIdx = this.selected.matchIdx = -1; |  | ||||||
|         this.offset.pageIdx = currentPageIndex; |  | ||||||
|         this.offset.matchIdx = null; |  | ||||||
|         this.hadMatch = false; |  | ||||||
|         this.resumePageIdx = null; |  | ||||||
|         this.pageMatches = []; |  | ||||||
|         this.matchCount = 0; |  | ||||||
|         this.pageMatchesLength = null; |  | ||||||
| 
 |  | ||||||
|         for (let i = 0; i < numPages; i++) { |  | ||||||
|           // Wipe out any previous highlighted matches.
 |  | ||||||
|           this.updatePage(i); |  | ||||||
| 
 |  | ||||||
|           // As soon as the text is extracted start finding the matches.
 |  | ||||||
|           if (!(i in this.pendingFindMatches)) { |  | ||||||
|             this.pendingFindMatches[i] = true; |  | ||||||
|             this.extractTextPromises[i].then((pageIdx) => { |  | ||||||
|               delete this.pendingFindMatches[pageIdx]; |  | ||||||
|               this.calcFindMatch(pageIdx); |  | ||||||
|             }); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       // If there's no query there's no point in searching.
 |  | ||||||
|       if (this.state.query === '') { |  | ||||||
|         this.updateUIState(FindStates.FIND_FOUND); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       // If we're waiting on a page, we return since we can't do anything else.
 |  | ||||||
|       if (this.resumePageIdx) { |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       var offset = this.offset; |  | ||||||
|       // Keep track of how many pages we should maximally iterate through.
 |  | ||||||
|       this.pagesToSearch = numPages; |  | ||||||
|       // If there's already a matchIdx that means we are iterating through a
 |  | ||||||
|       // page's matches.
 |  | ||||||
|       if (offset.matchIdx !== null) { |  | ||||||
|         var numPageMatches = this.pageMatches[offset.pageIdx].length; |  | ||||||
|         if ((!previous && offset.matchIdx + 1 < numPageMatches) || |  | ||||||
|             (previous && offset.matchIdx > 0)) { |  | ||||||
|           // The simple case; we just have advance the matchIdx to select
 |  | ||||||
|           // the next match on the page.
 |  | ||||||
|           this.hadMatch = true; |  | ||||||
|           offset.matchIdx = (previous ? offset.matchIdx - 1 : |  | ||||||
|                                         offset.matchIdx + 1); |  | ||||||
|           this.updateMatch(true); |  | ||||||
|           return; |  | ||||||
|         } |  | ||||||
|         // We went beyond the current page's matches, so we advance to
 |  | ||||||
|         // the next page.
 |  | ||||||
|         this.advanceOffsetPage(previous); |  | ||||||
|       } |  | ||||||
|       // Start searching through the page.
 |  | ||||||
|       this.nextPageMatch(); |       this.nextPageMatch(); | ||||||
|     }, |     } | ||||||
| 
 | 
 | ||||||
|     matchesReady: function PDFFindController_matchesReady(matches) { |     // Update the match count.
 | ||||||
|       var offset = this.offset; |     if (this.pageMatches[pageIndex].length > 0) { | ||||||
|       var numMatches = matches.length; |       this.matchCount += this.pageMatches[pageIndex].length; | ||||||
|       var previous = this.state.findPrevious; |       this.updateUIResultsCount(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|       if (numMatches) { |   extractText() { | ||||||
|         // There were matches for the page, so initialize the matchIdx.
 |     if (this.startedTextExtraction) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this.startedTextExtraction = true; | ||||||
|  |     this.pageContents.length = 0; | ||||||
|  | 
 | ||||||
|  |     let promise = Promise.resolve(); | ||||||
|  |     for (let i = 0, ii = this.pdfViewer.pagesCount; i < ii; i++) { | ||||||
|  |       let extractTextCapability = createPromiseCapability(); | ||||||
|  |       this.extractTextPromises[i] = extractTextCapability.promise; | ||||||
|  | 
 | ||||||
|  |       promise = promise.then(() => { | ||||||
|  |         return this.pdfViewer.getPageTextContent(i).then((textContent) => { | ||||||
|  |           let textItems = textContent.items; | ||||||
|  |           let strBuf = []; | ||||||
|  | 
 | ||||||
|  |           for (let j = 0, jj = textItems.length; j < jj; j++) { | ||||||
|  |             strBuf.push(textItems[j].str); | ||||||
|  |           } | ||||||
|  |           // Store the pageContent as a string.
 | ||||||
|  |           this.pageContents[i] = strBuf.join(''); | ||||||
|  |           extractTextCapability.resolve(i); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   executeCommand(cmd, state) { | ||||||
|  |     if (this.state === null || cmd !== 'findagain') { | ||||||
|  |       this.dirtyMatch = true; | ||||||
|  |     } | ||||||
|  |     this.state = state; | ||||||
|  |     this.updateUIState(FindState.PENDING); | ||||||
|  | 
 | ||||||
|  |     this._firstPagePromise.then(() => { | ||||||
|  |       this.extractText(); | ||||||
|  | 
 | ||||||
|  |       clearTimeout(this.findTimeout); | ||||||
|  |       if (cmd === 'find') { | ||||||
|  |         // Trigger the find action with a small delay to avoid starting the
 | ||||||
|  |         // search when the user is still typing (saving resources).
 | ||||||
|  |         this.findTimeout = setTimeout(this.nextMatch.bind(this), FIND_TIMEOUT); | ||||||
|  |       } else { | ||||||
|  |         this.nextMatch(); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   updatePage(index) { | ||||||
|  |     if (this.selected.pageIdx === index) { | ||||||
|  |       // 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.
 | ||||||
|  |       this.pdfViewer.currentPageNumber = index + 1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let page = this.pdfViewer.getPageView(index); | ||||||
|  |     if (page.textLayer) { | ||||||
|  |       page.textLayer.updateMatches(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   nextMatch() { | ||||||
|  |     let previous = this.state.findPrevious; | ||||||
|  |     let currentPageIndex = this.pdfViewer.currentPageNumber - 1; | ||||||
|  |     let numPages = this.pdfViewer.pagesCount; | ||||||
|  | 
 | ||||||
|  |     this.active = true; | ||||||
|  | 
 | ||||||
|  |     if (this.dirtyMatch) { | ||||||
|  |       // Need to recalculate the matches, reset everything.
 | ||||||
|  |       this.dirtyMatch = false; | ||||||
|  |       this.selected.pageIdx = this.selected.matchIdx = -1; | ||||||
|  |       this.offset.pageIdx = currentPageIndex; | ||||||
|  |       this.offset.matchIdx = null; | ||||||
|  |       this.hadMatch = false; | ||||||
|  |       this.resumePageIdx = null; | ||||||
|  |       this.pageMatches = []; | ||||||
|  |       this.matchCount = 0; | ||||||
|  |       this.pageMatchesLength = null; | ||||||
|  | 
 | ||||||
|  |       for (let i = 0; i < numPages; i++) { | ||||||
|  |         // Wipe out any previously highlighted matches.
 | ||||||
|  |         this.updatePage(i); | ||||||
|  | 
 | ||||||
|  |         // Start finding the matches as soon as the text is extracted.
 | ||||||
|  |         if (!(i in this.pendingFindMatches)) { | ||||||
|  |           this.pendingFindMatches[i] = true; | ||||||
|  |           this.extractTextPromises[i].then((pageIdx) => { | ||||||
|  |             delete this.pendingFindMatches[pageIdx]; | ||||||
|  |             this.calcFindMatch(pageIdx); | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // If there's no query there's no point in searching.
 | ||||||
|  |     if (this.state.query === '') { | ||||||
|  |       this.updateUIState(FindState.FOUND); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // If we're waiting on a page, we return since we can't do anything else.
 | ||||||
|  |     if (this.resumePageIdx) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let offset = this.offset; | ||||||
|  |     // Keep track of how many pages we should maximally iterate through.
 | ||||||
|  |     this.pagesToSearch = numPages; | ||||||
|  |     // If there's already a `matchIdx` that means we are iterating through a
 | ||||||
|  |     // page's matches.
 | ||||||
|  |     if (offset.matchIdx !== null) { | ||||||
|  |       let numPageMatches = this.pageMatches[offset.pageIdx].length; | ||||||
|  |       if ((!previous && offset.matchIdx + 1 < numPageMatches) || | ||||||
|  |           (previous && offset.matchIdx > 0)) { | ||||||
|  |         // The simple case; we just have advance the matchIdx to select
 | ||||||
|  |         // the next match on the page.
 | ||||||
|         this.hadMatch = true; |         this.hadMatch = true; | ||||||
|         offset.matchIdx = (previous ? numMatches - 1 : 0); |         offset.matchIdx = (previous ? offset.matchIdx - 1 : | ||||||
|  |                                       offset.matchIdx + 1); | ||||||
|         this.updateMatch(true); |         this.updateMatch(true); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       // We went beyond the current page's matches, so we advance to
 | ||||||
|  |       // the next page.
 | ||||||
|  |       this.advanceOffsetPage(previous); | ||||||
|  |     } | ||||||
|  |     // Start searching through the page.
 | ||||||
|  |     this.nextPageMatch(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   matchesReady(matches) { | ||||||
|  |     let offset = this.offset; | ||||||
|  |     let numMatches = matches.length; | ||||||
|  |     let previous = this.state.findPrevious; | ||||||
|  | 
 | ||||||
|  |     if (numMatches) { | ||||||
|  |       // There were matches for the page, so initialize `matchIdx`.
 | ||||||
|  |       this.hadMatch = true; | ||||||
|  |       offset.matchIdx = (previous ? numMatches - 1 : 0); | ||||||
|  |       this.updateMatch(true); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     // No matches, so attempt to search the next page.
 | ||||||
|  |     this.advanceOffsetPage(previous); | ||||||
|  |     if (offset.wrapped) { | ||||||
|  |       offset.matchIdx = null; | ||||||
|  |       if (this.pagesToSearch < 0) { | ||||||
|  |         // No point in wrapping again, there were no matches.
 | ||||||
|  |         this.updateMatch(false); | ||||||
|  |         // While matches were not found, searching for a page
 | ||||||
|  |         // with matches should nevertheless halt.
 | ||||||
|         return true; |         return true; | ||||||
|       } |       } | ||||||
|       // No matches, so attempt to search the next page.
 |     } | ||||||
|       this.advanceOffsetPage(previous); |     // Matches were not found (and searching is not done).
 | ||||||
|       if (offset.wrapped) { |     return false; | ||||||
|         offset.matchIdx = null; |   } | ||||||
|         if (this.pagesToSearch < 0) { | 
 | ||||||
|           // No point in wrapping again, there were no matches.
 |   /** | ||||||
|           this.updateMatch(false); |    * Called from the text layer when match presentation is updated. | ||||||
|           // while matches were not found, searching for a page
 |    * | ||||||
|           // with matches should nevertheless halt.
 |    * @param {number} pageIndex - The index of the page. | ||||||
|           return true; |    * @param {number} matchIndex - The index of the match. | ||||||
|         } |    * @param {Array} elements - Text layer `div` elements. | ||||||
|  |    * @param {number} beginIdx - Start index of the `div` array for the match. | ||||||
|  |    */ | ||||||
|  |   updateMatchPosition(pageIndex, matchIndex, elements, beginIdx) { | ||||||
|  |     if (this.selected.matchIdx === matchIndex && | ||||||
|  |         this.selected.pageIdx === pageIndex) { | ||||||
|  |       let spot = { | ||||||
|  |         top: FIND_SCROLL_OFFSET_TOP, | ||||||
|  |         left: FIND_SCROLL_OFFSET_LEFT, | ||||||
|  |       }; | ||||||
|  |       scrollIntoView(elements[beginIdx], spot, | ||||||
|  |                      /* skipOverflowHiddenElements = */ true); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   nextPageMatch() { | ||||||
|  |     if (this.resumePageIdx !== null) { | ||||||
|  |       console.error('There can only be one pending page.'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let matches = null; | ||||||
|  |     do { | ||||||
|  |       let pageIdx = this.offset.pageIdx; | ||||||
|  |       matches = this.pageMatches[pageIdx]; | ||||||
|  |       if (!matches) { | ||||||
|  |         // The matches don't exist yet for processing by `matchesReady`,
 | ||||||
|  |         // so set a resume point for when they do exist.
 | ||||||
|  |         this.resumePageIdx = pageIdx; | ||||||
|  |         break; | ||||||
|       } |       } | ||||||
|       // Matches were not found (and searching is not done).
 |     } while (!this.matchesReady(matches)); | ||||||
|       return false; |   } | ||||||
|     }, |  | ||||||
| 
 | 
 | ||||||
|     /** |   advanceOffsetPage(previous) { | ||||||
|      * The method is called back from the text layer when match presentation |     let offset = this.offset; | ||||||
|      * is updated. |     let numPages = this.extractTextPromises.length; | ||||||
|      * @param {number} pageIndex - page index. |     offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1); | ||||||
|      * @param {number} index - match index. |     offset.matchIdx = null; | ||||||
|      * @param {Array} elements - text layer div elements array. | 
 | ||||||
|      * @param {number} beginIdx - start index of the div array for the match. |     this.pagesToSearch--; | ||||||
|      */ | 
 | ||||||
|     updateMatchPosition: function PDFFindController_updateMatchPosition( |     if (offset.pageIdx >= numPages || offset.pageIdx < 0) { | ||||||
|         pageIndex, index, elements, beginIdx) { |       offset.pageIdx = (previous ? numPages - 1 : 0); | ||||||
|       if (this.selected.matchIdx === index && |       offset.wrapped = true; | ||||||
|           this.selected.pageIdx === pageIndex) { |     } | ||||||
|         var spot = { |   } | ||||||
|           top: FIND_SCROLL_OFFSET_TOP, | 
 | ||||||
|           left: FIND_SCROLL_OFFSET_LEFT, |   updateMatch(found = false) { | ||||||
|         }; |     let state = FindState.NOT_FOUND; | ||||||
|         scrollIntoView(elements[beginIdx], spot, |     let wrapped = this.offset.wrapped; | ||||||
|                        /* skipOverflowHiddenElements = */ true); |     this.offset.wrapped = false; | ||||||
|  | 
 | ||||||
|  |     if (found) { | ||||||
|  |       let previousPage = this.selected.pageIdx; | ||||||
|  |       this.selected.pageIdx = this.offset.pageIdx; | ||||||
|  |       this.selected.matchIdx = this.offset.matchIdx; | ||||||
|  |       state = (wrapped ? FindState.WRAPPED : FindState.FOUND); | ||||||
|  | 
 | ||||||
|  |       // Update the currently selected page to wipe out any selected matches.
 | ||||||
|  |       if (previousPage !== -1 && previousPage !== this.selected.pageIdx) { | ||||||
|  |         this.updatePage(previousPage); | ||||||
|       } |       } | ||||||
|     }, |     } | ||||||
| 
 | 
 | ||||||
|     nextPageMatch: function PDFFindController_nextPageMatch() { |     this.updateUIState(state, this.state.findPrevious); | ||||||
|       if (this.resumePageIdx !== null) { |     if (this.selected.pageIdx !== -1) { | ||||||
|         console.error('There can only be one pending page.'); |       this.updatePage(this.selected.pageIdx); | ||||||
|       } |     } | ||||||
|       do { |   } | ||||||
|         var pageIdx = this.offset.pageIdx; |  | ||||||
|         var matches = this.pageMatches[pageIdx]; |  | ||||||
|         if (!matches) { |  | ||||||
|           // The matches don't exist yet for processing by "matchesReady",
 |  | ||||||
|           // so set a resume point for when they do exist.
 |  | ||||||
|           this.resumePageIdx = pageIdx; |  | ||||||
|           break; |  | ||||||
|         } |  | ||||||
|       } while (!this.matchesReady(matches)); |  | ||||||
|     }, |  | ||||||
| 
 | 
 | ||||||
|     advanceOffsetPage: function PDFFindController_advanceOffsetPage(previous) { |   updateUIResultsCount() { | ||||||
|       var offset = this.offset; |     if (this.onUpdateResultsCount) { | ||||||
|       var numPages = this.extractTextPromises.length; |       this.onUpdateResultsCount(this.matchCount); | ||||||
|       offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1); |     } | ||||||
|       offset.matchIdx = null; |   } | ||||||
| 
 | 
 | ||||||
|       this.pagesToSearch--; |   updateUIState(state, previous) { | ||||||
| 
 |     if (this.onUpdateState) { | ||||||
|       if (offset.pageIdx >= numPages || offset.pageIdx < 0) { |       this.onUpdateState(state, previous, this.matchCount); | ||||||
|         offset.pageIdx = (previous ? numPages - 1 : 0); |     } | ||||||
|         offset.wrapped = true; |   } | ||||||
|       } | } | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     updateMatch: function PDFFindController_updateMatch(found) { |  | ||||||
|       var state = FindStates.FIND_NOTFOUND; |  | ||||||
|       var wrapped = this.offset.wrapped; |  | ||||||
|       this.offset.wrapped = false; |  | ||||||
| 
 |  | ||||||
|       if (found) { |  | ||||||
|         var previousPage = this.selected.pageIdx; |  | ||||||
|         this.selected.pageIdx = this.offset.pageIdx; |  | ||||||
|         this.selected.matchIdx = this.offset.matchIdx; |  | ||||||
|         state = (wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND); |  | ||||||
|         // Update the currently selected page to wipe out any selected matches.
 |  | ||||||
|         if (previousPage !== -1 && previousPage !== this.selected.pageIdx) { |  | ||||||
|           this.updatePage(previousPage); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       this.updateUIState(state, this.state.findPrevious); |  | ||||||
|       if (this.selected.pageIdx !== -1) { |  | ||||||
|         this.updatePage(this.selected.pageIdx); |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     updateUIResultsCount: |  | ||||||
|         function PDFFindController_updateUIResultsCount() { |  | ||||||
|       if (this.onUpdateResultsCount) { |  | ||||||
|         this.onUpdateResultsCount(this.matchCount); |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
| 
 |  | ||||||
|     updateUIState: function PDFFindController_updateUIState(state, previous) { |  | ||||||
|       if (this.onUpdateState) { |  | ||||||
|         this.onUpdateState(state, previous, this.matchCount); |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|   }; |  | ||||||
|   return PDFFindController; |  | ||||||
| })(); |  | ||||||
| 
 | 
 | ||||||
| export { | export { | ||||||
|   FindStates, |   FindState, | ||||||
|   PDFFindController, |   PDFFindController, | ||||||
| }; | }; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user