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. | ||||
|  */ | ||||
| 
 | ||||
| import { FindStates } from './pdf_find_controller'; | ||||
| import { FindState } from './pdf_find_controller'; | ||||
| import { NullL10n } from './ui_utils'; | ||||
| 
 | ||||
| /** | ||||
| @ -108,19 +108,19 @@ class PDFFindBar { | ||||
|     var status = ''; | ||||
| 
 | ||||
|     switch (state) { | ||||
|       case FindStates.FIND_FOUND: | ||||
|       case FindState.FOUND: | ||||
|         break; | ||||
| 
 | ||||
|       case FindStates.FIND_PENDING: | ||||
|       case FindState.PENDING: | ||||
|         status = 'pending'; | ||||
|         break; | ||||
| 
 | ||||
|       case FindStates.FIND_NOTFOUND: | ||||
|       case FindState.NOT_FOUND: | ||||
|         findMsg = this.l10n.get('find_not_found', null, 'Phrase not found'); | ||||
|         notFound = true; | ||||
|         break; | ||||
| 
 | ||||
|       case FindStates.FIND_WRAPPED: | ||||
|       case FindState.WRAPPED: | ||||
|         if (previous) { | ||||
|           findMsg = this.l10n.get('find_reached_top', null, | ||||
|             'Reached top of document, continued from bottom'); | ||||
|  | ||||
| @ -16,17 +16,18 @@ | ||||
| import { createPromiseCapability } from 'pdfjs-lib'; | ||||
| import { scrollIntoView } from './ui_utils'; | ||||
| 
 | ||||
| var FindStates = { | ||||
|   FIND_FOUND: 0, | ||||
|   FIND_NOTFOUND: 1, | ||||
|   FIND_WRAPPED: 2, | ||||
|   FIND_PENDING: 3, | ||||
| const FindState = { | ||||
|   FOUND: 0, | ||||
|   NOT_FOUND: 1, | ||||
|   WRAPPED: 2, | ||||
|   PENDING: 3, | ||||
| }; | ||||
| 
 | ||||
| var FIND_SCROLL_OFFSET_TOP = -50; | ||||
| var FIND_SCROLL_OFFSET_LEFT = -400; | ||||
| const FIND_SCROLL_OFFSET_TOP = -50; | ||||
| const FIND_SCROLL_OFFSET_LEFT = -400; | ||||
| const FIND_TIMEOUT = 250; // ms
 | ||||
| 
 | ||||
| var CHARACTERS_TO_NORMALIZE = { | ||||
| const CHARACTERS_TO_NORMALIZE = { | ||||
|   '\u2018': '\'', // Left single quotation mark
 | ||||
|   '\u2019': '\'', // Right single quotation mark
 | ||||
|   '\u201A': '\'', // Single low-9 quotation mark
 | ||||
| @ -41,12 +42,11 @@ var CHARACTERS_TO_NORMALIZE = { | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Provides "search" or "find" functionality for the PDF. | ||||
|  * This object actually performs the search for a given string. | ||||
|  * Provides search functionality to find a given string in a PDF document. | ||||
|  */ | ||||
| var PDFFindController = (function PDFFindControllerClosure() { | ||||
|   function PDFFindController(options) { | ||||
|     this.pdfViewer = options.pdfViewer || null; | ||||
| class PDFFindController { | ||||
|   constructor({ pdfViewer, }) { | ||||
|     this.pdfViewer = pdfViewer; | ||||
| 
 | ||||
|     this.onUpdateResultsCount = null; | ||||
|     this.onUpdateState = null; | ||||
| @ -54,434 +54,430 @@ var PDFFindController = (function PDFFindControllerClosure() { | ||||
|     this.reset(); | ||||
| 
 | ||||
|     // 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'); | ||||
|   } | ||||
| 
 | ||||
|   PDFFindController.prototype = { | ||||
|     reset: function PDFFindController_reset() { | ||||
|       this.startedTextExtraction = false; | ||||
|       this.extractTextPromises = []; | ||||
|       this.pendingFindMatches = Object.create(null); | ||||
|       this.active = false; // If active, find results will be highlighted.
 | ||||
|       this.pageContents = []; // Stores the text for each page.
 | ||||
|       this.pageMatches = []; | ||||
|       this.pageMatchesLength = null; | ||||
|       this.matchCount = 0; | ||||
|       this.selected = { // Currently selected match.
 | ||||
|         pageIdx: -1, | ||||
|         matchIdx: -1, | ||||
|       }; | ||||
|       this.offset = { // Where the find algorithm currently is in the document.
 | ||||
|         pageIdx: null, | ||||
|         matchIdx: null, | ||||
|       }; | ||||
|       this.pagesToSearch = null; | ||||
|       this.resumePageIdx = null; | ||||
|       this.state = null; | ||||
|       this.dirtyMatch = false; | ||||
|       this.findTimeout = null; | ||||
|   reset() { | ||||
|     this.startedTextExtraction = false; | ||||
|     this.extractTextPromises = []; | ||||
|     this.pendingFindMatches = Object.create(null); | ||||
|     this.active = false; // If active, find results will be highlighted.
 | ||||
|     this.pageContents = []; // Stores the text for each page.
 | ||||
|     this.pageMatches = []; | ||||
|     this.pageMatchesLength = null; | ||||
|     this.matchCount = 0; | ||||
|     this.selected = { // Currently selected match.
 | ||||
|       pageIdx: -1, | ||||
|       matchIdx: -1, | ||||
|     }; | ||||
|     this.offset = { // Where the find algorithm currently is in the document.
 | ||||
|       pageIdx: null, | ||||
|       matchIdx: null, | ||||
|     }; | ||||
|     this.pagesToSearch = null; | ||||
|     this.resumePageIdx = null; | ||||
|     this.state = null; | ||||
|     this.dirtyMatch = false; | ||||
|     this.findTimeout = null; | ||||
| 
 | ||||
|       this._firstPagePromise = new Promise((resolve) => { | ||||
|         this.resolveFirstPage = resolve; | ||||
|       }); | ||||
|     }, | ||||
|     this._firstPagePromise = new Promise((resolve) => { | ||||
|       this.resolveFirstPage = resolve; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|     normalize: function PDFFindController_normalize(text) { | ||||
|       return text.replace(this.normalizationRegex, function (ch) { | ||||
|         return CHARACTERS_TO_NORMALIZE[ch]; | ||||
|       }); | ||||
|     }, | ||||
|   normalize(text) { | ||||
|     return text.replace(this.normalizationRegex, function (ch) { | ||||
|       return CHARACTERS_TO_NORMALIZE[ch]; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|     // Helper for multiple search - fills matchesWithLength array
 | ||||
|     // and takes into account cases when one search term
 | ||||
|     // include another search term (for example, "tamed tame" or "this is").
 | ||||
|     // Looking for intersecting terms in the 'matches' and
 | ||||
|     // leave elements with a longer match-length.
 | ||||
|   /** | ||||
|    * Helper for multi-term search that fills the `matchesWithLength` array | ||||
|    * and handles cases where one search term includes another search term (for | ||||
|    * example, "tamed tame" or "this is"). It looks for intersecting terms in | ||||
|    * 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( | ||||
|         matchesWithLength, matches, matchesLength) { | ||||
|       // Check for cases like "TAMEd TAME".
 | ||||
|       if (currentIndex < matchesWithLength.length - 1 && | ||||
|           currentElem.match === nextElem.match) { | ||||
|         currentElem.skipped = true; | ||||
|         return true; | ||||
|       } | ||||
| 
 | ||||
|       function isSubTerm(matchesWithLength, currentIndex) { | ||||
|         var currentElem, prevElem, nextElem; | ||||
|         currentElem = matchesWithLength[currentIndex]; | ||||
|         nextElem = matchesWithLength[currentIndex + 1]; | ||||
|         // checking for cases like "TAMEd TAME"
 | ||||
|         if (currentIndex < matchesWithLength.length - 1 && | ||||
|             currentElem.match === nextElem.match) { | ||||
|       // Check for cases like "thIS IS".
 | ||||
|       for (let i = currentIndex - 1; i >= 0; i--) { | ||||
|         let 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; | ||||
|         } | ||||
|         // 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; | ||||
|       // Sorting array of objects { match: <match>, matchLength: <matchLength> }
 | ||||
|       // in increasing index first and then the lengths.
 | ||||
|       matchesWithLength.sort(function(a, b) { | ||||
|         return a.match === b.match ? | ||||
|         a.matchLength - b.matchLength : a.match - b.match; | ||||
|       }); | ||||
|       for (i = 0, len = matchesWithLength.length; i < len; i++) { | ||||
|         if (isSubTerm(matchesWithLength, i)) { | ||||
|           continue; | ||||
|         } | ||||
|         matches.push(matchesWithLength[i].match); | ||||
|         matchesLength.push(matchesWithLength[i].matchLength); | ||||
|     // Sort the array of `{ match: <match>, matchLength: <matchLength> }`
 | ||||
|     // objects on increasing index first and on the length otherwise.
 | ||||
|     matchesWithLength.sort(function(a, b) { | ||||
|       return a.match === b.match ? a.matchLength - b.matchLength : | ||||
|                                    a.match - b.match; | ||||
|     }); | ||||
|     for (let i = 0, len = matchesWithLength.length; i < len; i++) { | ||||
|       if (isSubTerm(matchesWithLength, i)) { | ||||
|         continue; | ||||
|       } | ||||
|     }, | ||||
|       matches.push(matchesWithLength[i].match); | ||||
|       matchesLength.push(matchesWithLength[i].matchLength); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|     calcFindPhraseMatch: function PDFFindController_calcFindPhraseMatch( | ||||
|       query, pageIndex, pageContent) { | ||||
|       var matches = []; | ||||
|       var queryLen = query.length; | ||||
|       var matchIdx = -queryLen; | ||||
|   calcFindPhraseMatch(query, pageIndex, pageContent) { | ||||
|     let matches = []; | ||||
|     let queryLen = query.length; | ||||
|     let 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) { | ||||
|         matchIdx = pageContent.indexOf(query, matchIdx + queryLen); | ||||
|         matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen); | ||||
|         if (matchIdx === -1) { | ||||
|           break; | ||||
|         } | ||||
|         matches.push(matchIdx); | ||||
|       } | ||||
|       this.pageMatches[pageIndex] = matches; | ||||
|     }, | ||||
| 
 | ||||
|     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); | ||||
|           }); | ||||
|         // Other searches do not, so we store the length.
 | ||||
|         matchesWithLength.push({ | ||||
|           match: matchIdx, | ||||
|           matchLength: subqueryLen, | ||||
|           skipped: false, | ||||
|         }); | ||||
|       } | ||||
|     }, | ||||
|     } | ||||
| 
 | ||||
|     executeCommand: function PDFFindController_executeCommand(cmd, state) { | ||||
|       if (this.state === null || cmd !== 'findagain') { | ||||
|         this.dirtyMatch = true; | ||||
|       } | ||||
|       this.state = state; | ||||
|       this.updateUIState(FindStates.FIND_PENDING); | ||||
|     // Prepare arrays for storing the matches.
 | ||||
|     if (!this.pageMatchesLength) { | ||||
|       this.pageMatchesLength = []; | ||||
|     } | ||||
|     this.pageMatchesLength[pageIndex] = []; | ||||
|     this.pageMatches[pageIndex] = []; | ||||
| 
 | ||||
|       this._firstPagePromise.then(() => { | ||||
|         this.extractText(); | ||||
|     // Sort `matchesWithLength`, remove intersecting terms and put the result
 | ||||
|     // into the two arrays.
 | ||||
|     this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex], | ||||
|       this.pageMatchesLength[pageIndex]); | ||||
|   } | ||||
| 
 | ||||
|         clearTimeout(this.findTimeout); | ||||
|         if (cmd === 'find') { | ||||
|           // Only trigger the find action after 250ms of silence.
 | ||||
|           this.findTimeout = setTimeout(this.nextMatch.bind(this), 250); | ||||
|         } else { | ||||
|           this.nextMatch(); | ||||
|         } | ||||
|       }); | ||||
|     }, | ||||
|   calcFindMatch(pageIndex) { | ||||
|     let pageContent = this.normalize(this.pageContents[pageIndex]); | ||||
|     let query = this.normalize(this.state.query); | ||||
|     let caseSensitive = this.state.caseSensitive; | ||||
|     let phraseSearch = this.state.phraseSearch; | ||||
|     let queryLen = query.length; | ||||
| 
 | ||||
|     updatePage: function PDFFindController_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; | ||||
|       } | ||||
|     if (queryLen === 0) { | ||||
|       // Do nothing: the matches should be wiped out already.
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|       var page = this.pdfViewer.getPageView(index); | ||||
|       if (page.textLayer) { | ||||
|         page.textLayer.updateMatches(); | ||||
|       } | ||||
|     }, | ||||
|     if (!caseSensitive) { | ||||
|       pageContent = pageContent.toLowerCase(); | ||||
|       query = query.toLowerCase(); | ||||
|     } | ||||
| 
 | ||||
|     nextMatch: function PDFFindController_nextMatch() { | ||||
|       var previous = this.state.findPrevious; | ||||
|       var currentPageIndex = this.pdfViewer.currentPageNumber - 1; | ||||
|       var numPages = this.pdfViewer.pagesCount; | ||||
|     if (phraseSearch) { | ||||
|       this.calcFindPhraseMatch(query, pageIndex, pageContent); | ||||
|     } else { | ||||
|       this.calcFindWordMatch(query, pageIndex, pageContent); | ||||
|     } | ||||
| 
 | ||||
|       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 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.updatePage(pageIndex); | ||||
|     if (this.resumePageIdx === pageIndex) { | ||||
|       this.resumePageIdx = null; | ||||
|       this.nextPageMatch(); | ||||
|     }, | ||||
|     } | ||||
| 
 | ||||
|     matchesReady: function PDFFindController_matchesReady(matches) { | ||||
|       var offset = this.offset; | ||||
|       var numMatches = matches.length; | ||||
|       var previous = this.state.findPrevious; | ||||
|     // Update the match count.
 | ||||
|     if (this.pageMatches[pageIndex].length > 0) { | ||||
|       this.matchCount += this.pageMatches[pageIndex].length; | ||||
|       this.updateUIResultsCount(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|       if (numMatches) { | ||||
|         // There were matches for the page, so initialize the matchIdx.
 | ||||
|   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(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; | ||||
|         offset.matchIdx = (previous ? numMatches - 1 : 0); | ||||
|         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(); | ||||
|   } | ||||
| 
 | ||||
|   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; | ||||
|       } | ||||
|       // 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; | ||||
|         } | ||||
|     } | ||||
|     // Matches were not found (and searching is not done).
 | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Called from the text layer when match presentation is updated. | ||||
|    * | ||||
|    * @param {number} pageIndex - The index of the page. | ||||
|    * @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).
 | ||||
|       return false; | ||||
|     }, | ||||
|     } while (!this.matchesReady(matches)); | ||||
|   } | ||||
| 
 | ||||
|     /** | ||||
|      * The method is called back from the text layer when match presentation | ||||
|      * is updated. | ||||
|      * @param {number} pageIndex - page index. | ||||
|      * @param {number} index - match index. | ||||
|      * @param {Array} elements - text layer div elements array. | ||||
|      * @param {number} beginIdx - start index of the div array for the match. | ||||
|      */ | ||||
|     updateMatchPosition: function PDFFindController_updateMatchPosition( | ||||
|         pageIndex, index, elements, beginIdx) { | ||||
|       if (this.selected.matchIdx === index && | ||||
|           this.selected.pageIdx === pageIndex) { | ||||
|         var spot = { | ||||
|           top: FIND_SCROLL_OFFSET_TOP, | ||||
|           left: FIND_SCROLL_OFFSET_LEFT, | ||||
|         }; | ||||
|         scrollIntoView(elements[beginIdx], spot, | ||||
|                        /* skipOverflowHiddenElements = */ true); | ||||
|   advanceOffsetPage(previous) { | ||||
|     let offset = this.offset; | ||||
|     let numPages = this.extractTextPromises.length; | ||||
|     offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1); | ||||
|     offset.matchIdx = null; | ||||
| 
 | ||||
|     this.pagesToSearch--; | ||||
| 
 | ||||
|     if (offset.pageIdx >= numPages || offset.pageIdx < 0) { | ||||
|       offset.pageIdx = (previous ? numPages - 1 : 0); | ||||
|       offset.wrapped = true; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   updateMatch(found = false) { | ||||
|     let state = FindState.NOT_FOUND; | ||||
|     let wrapped = this.offset.wrapped; | ||||
|     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() { | ||||
|       if (this.resumePageIdx !== null) { | ||||
|         console.error('There can only be one pending page.'); | ||||
|       } | ||||
|       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)); | ||||
|     }, | ||||
|     this.updateUIState(state, this.state.findPrevious); | ||||
|     if (this.selected.pageIdx !== -1) { | ||||
|       this.updatePage(this.selected.pageIdx); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|     advanceOffsetPage: function PDFFindController_advanceOffsetPage(previous) { | ||||
|       var offset = this.offset; | ||||
|       var numPages = this.extractTextPromises.length; | ||||
|       offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1); | ||||
|       offset.matchIdx = null; | ||||
|   updateUIResultsCount() { | ||||
|     if (this.onUpdateResultsCount) { | ||||
|       this.onUpdateResultsCount(this.matchCount); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|       this.pagesToSearch--; | ||||
| 
 | ||||
|       if (offset.pageIdx >= numPages || offset.pageIdx < 0) { | ||||
|         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; | ||||
| })(); | ||||
|   updateUIState(state, previous) { | ||||
|     if (this.onUpdateState) { | ||||
|       this.onUpdateState(state, previous, this.matchCount); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export { | ||||
|   FindStates, | ||||
|   FindState, | ||||
|   PDFFindController, | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user