|
@@ -875,6 +875,7 @@ exports.binarySearchFirstItem = binarySearchFirstItem;
|
|
|
var event = document.createEvent('CustomEvent');
|
|
|
event.initCustomEvent('find' + e.type, true, true, {
|
|
|
query: e.query,
|
|
|
+ phraseSearch: e.phraseSearch,
|
|
|
caseSensitive: e.caseSensitive,
|
|
|
highlightAll: e.highlightAll,
|
|
|
findPrevious: e.findPrevious
|
|
@@ -999,6 +1000,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
|
|
|
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,
|
|
@@ -1025,10 +1027,114 @@ var PDFFindController = (function PDFFindControllerClosure() {
|
|
|
});
|
|
|
},
|
|
|
|
|
|
+ // 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.
|
|
|
+
|
|
|
+ _prepareMatches: function PDFFindController_prepareMatches(
|
|
|
+ matchesWithLength, matches, matchesLength) {
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ calcFindPhraseMatch: function PDFFindController_calcFindPhraseMatch(
|
|
|
+ query, pageIndex, pageContent) {
|
|
|
+ var matches = [];
|
|
|
+ var queryLen = query.length;
|
|
|
+ var matchIdx = -queryLen;
|
|
|
+ while (true) {
|
|
|
+ matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
|
|
|
+ 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) {
|
|
@@ -1041,16 +1147,12 @@ var PDFFindController = (function PDFFindControllerClosure() {
|
|
|
query = query.toLowerCase();
|
|
|
}
|
|
|
|
|
|
- var matches = [];
|
|
|
- var matchIdx = -queryLen;
|
|
|
- while (true) {
|
|
|
- matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
|
|
|
- if (matchIdx === -1) {
|
|
|
- break;
|
|
|
- }
|
|
|
- matches.push(matchIdx);
|
|
|
+ if (phraseSearch) {
|
|
|
+ this.calcFindPhraseMatch(query, pageIndex, pageContent);
|
|
|
+ } else {
|
|
|
+ this.calcFindWordMatch(query, pageIndex, pageContent);
|
|
|
}
|
|
|
- this.pageMatches[pageIndex] = matches;
|
|
|
+
|
|
|
this.updatePage(pageIndex);
|
|
|
if (this.resumePageIdx === pageIndex) {
|
|
|
this.resumePageIdx = null;
|
|
@@ -1058,8 +1160,8 @@ var PDFFindController = (function PDFFindControllerClosure() {
|
|
|
}
|
|
|
|
|
|
// Update the matches count
|
|
|
- if (matches.length > 0) {
|
|
|
- this.matchCount += matches.length;
|
|
|
+ if (this.pageMatches[pageIndex].length > 0) {
|
|
|
+ this.matchCount += this.pageMatches[pageIndex].length;
|
|
|
this.updateUIResultsCount();
|
|
|
}
|
|
|
},
|
|
@@ -1154,6 +1256,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
|
|
|
this.resumePageIdx = null;
|
|
|
this.pageMatches = [];
|
|
|
this.matchCount = 0;
|
|
|
+ this.pageMatchesLength = null;
|
|
|
var self = this;
|
|
|
|
|
|
for (var i = 0; i < numPages; i++) {
|
|
@@ -1901,6 +2004,13 @@ var PDFLinkService = (function () {
|
|
|
setHash: function PDFLinkService_setHash(hash) {
|
|
|
if (hash.indexOf('=') >= 0) {
|
|
|
var params = parseQueryString(hash);
|
|
|
+ if ('search' in params) {
|
|
|
+ this.eventBus.dispatch('findfromurlhash', {
|
|
|
+ source: this,
|
|
|
+ query: params['search'].replace(/"/g, ''),
|
|
|
+ phraseSearch: (params['phrase'] === 'true')
|
|
|
+ });
|
|
|
+ }
|
|
|
// borrowing syntax from "Parameters for Opening PDF Files"
|
|
|
if ('nameddest' in params) {
|
|
|
if (this.pdfHistory) {
|
|
@@ -2749,7 +2859,8 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
|
|
|
this.divContentDone = true;
|
|
|
},
|
|
|
|
|
|
- convertMatches: function TextLayerBuilder_convertMatches(matches) {
|
|
|
+ convertMatches: function TextLayerBuilder_convertMatches(matches,
|
|
|
+ matchesLength) {
|
|
|
var i = 0;
|
|
|
var iIndex = 0;
|
|
|
var bidiTexts = this.textContent.items;
|
|
@@ -2757,7 +2868,9 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
|
|
|
var queryLen = (this.findController === null ?
|
|
|
0 : this.findController.state.query.length);
|
|
|
var ret = [];
|
|
|
-
|
|
|
+ if (!matches) {
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
for (var m = 0, len = matches.length; m < len; m++) {
|
|
|
// Calculate the start position.
|
|
|
var matchIdx = matches[m];
|
|
@@ -2780,7 +2893,11 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
|
|
|
};
|
|
|
|
|
|
// Calculate the end position.
|
|
|
- matchIdx += queryLen;
|
|
|
+ if (matchesLength) { // multiterm search
|
|
|
+ matchIdx += matchesLength[m];
|
|
|
+ } else { // phrase search
|
|
|
+ matchIdx += queryLen;
|
|
|
+ }
|
|
|
|
|
|
// Somewhat the same array as above, but use > instead of >= to get
|
|
|
// the end position right.
|
|
@@ -2922,8 +3039,14 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
|
|
|
|
|
|
// Convert the matches on the page controller into the match format
|
|
|
// used for the textLayer.
|
|
|
- this.matches = this.convertMatches(this.findController === null ?
|
|
|
- [] : (this.findController.pageMatches[this.pageIdx] || []));
|
|
|
+ var pageMatches, pageMatchesLength;
|
|
|
+ if (this.findController !== null) {
|
|
|
+ pageMatches = this.findController.pageMatches[this.pageIdx] || null;
|
|
|
+ pageMatchesLength = (this.findController.pageMatchesLength) ?
|
|
|
+ this.findController.pageMatchesLength[this.pageIdx] || null : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.matches = this.convertMatches(pageMatches, pageMatchesLength);
|
|
|
this.renderMatches(this.matches);
|
|
|
},
|
|
|
|