You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
386 lines
13 KiB
386 lines
13 KiB
/* Copyright 2017 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 |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
'use strict'; |
|
|
|
Object.defineProperty(exports, "__esModule", { |
|
value: true |
|
}); |
|
exports.PDFFindController = exports.FindStates = undefined; |
|
|
|
var _pdf = require('../pdf'); |
|
|
|
var _ui_utils = require('./ui_utils'); |
|
|
|
var FindStates = { |
|
FIND_FOUND: 0, |
|
FIND_NOTFOUND: 1, |
|
FIND_WRAPPED: 2, |
|
FIND_PENDING: 3 |
|
}; |
|
var FIND_SCROLL_OFFSET_TOP = -50; |
|
var FIND_SCROLL_OFFSET_LEFT = -400; |
|
var CHARACTERS_TO_NORMALIZE = { |
|
'\u2018': '\'', |
|
'\u2019': '\'', |
|
'\u201A': '\'', |
|
'\u201B': '\'', |
|
'\u201C': '"', |
|
'\u201D': '"', |
|
'\u201E': '"', |
|
'\u201F': '"', |
|
'\xBC': '1/4', |
|
'\xBD': '1/2', |
|
'\xBE': '3/4' |
|
}; |
|
var PDFFindController = function PDFFindControllerClosure() { |
|
function PDFFindController(options) { |
|
this.pdfViewer = options.pdfViewer || null; |
|
this.onUpdateResultsCount = null; |
|
this.onUpdateState = null; |
|
this.reset(); |
|
var replace = Object.keys(CHARACTERS_TO_NORMALIZE).join(''); |
|
this.normalizationRegex = new RegExp('[' + replace + ']', 'g'); |
|
} |
|
PDFFindController.prototype = { |
|
reset: function PDFFindController_reset() { |
|
var _this = this; |
|
|
|
this.startedTextExtraction = false; |
|
this.extractTextPromises = []; |
|
this.pendingFindMatches = Object.create(null); |
|
this.active = false; |
|
this.pageContents = []; |
|
this.pageMatches = []; |
|
this.pageMatchesLength = null; |
|
this.matchCount = 0; |
|
this.selected = { |
|
pageIdx: -1, |
|
matchIdx: -1 |
|
}; |
|
this.offset = { |
|
pageIdx: null, |
|
matchIdx: null |
|
}; |
|
this.pagesToSearch = null; |
|
this.resumePageIdx = null; |
|
this.state = null; |
|
this.dirtyMatch = false; |
|
this.findTimeout = null; |
|
this._firstPagePromise = new Promise(function (resolve) { |
|
_this.resolveFirstPage = resolve; |
|
}); |
|
}, |
|
normalize: function PDFFindController_normalize(text) { |
|
return text.replace(this.normalizationRegex, function (ch) { |
|
return CHARACTERS_TO_NORMALIZE[ch]; |
|
}); |
|
}, |
|
_prepareMatches: function PDFFindController_prepareMatches(matchesWithLength, matches, matchesLength) { |
|
function isSubTerm(matchesWithLength, currentIndex) { |
|
var currentElem, prevElem, nextElem; |
|
currentElem = matchesWithLength[currentIndex]; |
|
nextElem = matchesWithLength[currentIndex + 1]; |
|
if (currentIndex < matchesWithLength.length - 1 && currentElem.match === nextElem.match) { |
|
currentElem.skipped = true; |
|
return true; |
|
} |
|
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; |
|
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 = []; |
|
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; |
|
} |
|
matchesWithLength.push({ |
|
match: matchIdx, |
|
matchLength: subqueryLen, |
|
skipped: false |
|
}); |
|
} |
|
} |
|
if (!this.pageMatchesLength) { |
|
this.pageMatchesLength = []; |
|
} |
|
this.pageMatchesLength[pageIndex] = []; |
|
this.pageMatches[pageIndex] = []; |
|
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) { |
|
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(); |
|
} |
|
if (this.pageMatches[pageIndex].length > 0) { |
|
this.matchCount += this.pageMatches[pageIndex].length; |
|
this.updateUIResultsCount(); |
|
} |
|
}, |
|
extractText: function extractText() { |
|
var _this2 = this; |
|
|
|
if (this.startedTextExtraction) { |
|
return; |
|
} |
|
this.startedTextExtraction = true; |
|
this.pageContents.length = 0; |
|
var promise = Promise.resolve(); |
|
|
|
var _loop = function _loop(i, ii) { |
|
var extractTextCapability = (0, _pdf.createPromiseCapability)(); |
|
_this2.extractTextPromises[i] = extractTextCapability.promise; |
|
promise = promise.then(function () { |
|
return _this2.pdfViewer.getPageTextContent(i).then(function (textContent) { |
|
var textItems = textContent.items; |
|
var strBuf = []; |
|
for (var j = 0, jj = textItems.length; j < jj; j++) { |
|
strBuf.push(textItems[j].str); |
|
} |
|
_this2.pageContents[i] = strBuf.join(''); |
|
extractTextCapability.resolve(i); |
|
}); |
|
}); |
|
}; |
|
|
|
for (var i = 0, ii = this.pdfViewer.pagesCount; i < ii; i++) { |
|
_loop(i, ii); |
|
} |
|
}, |
|
|
|
executeCommand: function PDFFindController_executeCommand(cmd, state) { |
|
var _this3 = this; |
|
|
|
if (this.state === null || cmd !== 'findagain') { |
|
this.dirtyMatch = true; |
|
} |
|
this.state = state; |
|
this.updateUIState(FindStates.FIND_PENDING); |
|
this._firstPagePromise.then(function () { |
|
_this3.extractText(); |
|
clearTimeout(_this3.findTimeout); |
|
if (cmd === 'find') { |
|
_this3.findTimeout = setTimeout(_this3.nextMatch.bind(_this3), 250); |
|
} else { |
|
_this3.nextMatch(); |
|
} |
|
}); |
|
}, |
|
updatePage: function PDFFindController_updatePage(index) { |
|
if (this.selected.pageIdx === index) { |
|
this.pdfViewer.currentPageNumber = index + 1; |
|
} |
|
var page = this.pdfViewer.getPageView(index); |
|
if (page.textLayer) { |
|
page.textLayer.updateMatches(); |
|
} |
|
}, |
|
nextMatch: function PDFFindController_nextMatch() { |
|
var _this4 = this; |
|
|
|
var previous = this.state.findPrevious; |
|
var currentPageIndex = this.pdfViewer.currentPageNumber - 1; |
|
var numPages = this.pdfViewer.pagesCount; |
|
this.active = true; |
|
if (this.dirtyMatch) { |
|
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 (var i = 0; i < numPages; i++) { |
|
this.updatePage(i); |
|
if (!(i in this.pendingFindMatches)) { |
|
this.pendingFindMatches[i] = true; |
|
this.extractTextPromises[i].then(function (pageIdx) { |
|
delete _this4.pendingFindMatches[pageIdx]; |
|
_this4.calcFindMatch(pageIdx); |
|
}); |
|
} |
|
} |
|
} |
|
if (this.state.query === '') { |
|
this.updateUIState(FindStates.FIND_FOUND); |
|
return; |
|
} |
|
if (this.resumePageIdx) { |
|
return; |
|
} |
|
var offset = this.offset; |
|
this.pagesToSearch = numPages; |
|
if (offset.matchIdx !== null) { |
|
var numPageMatches = this.pageMatches[offset.pageIdx].length; |
|
if (!previous && offset.matchIdx + 1 < numPageMatches || previous && offset.matchIdx > 0) { |
|
this.hadMatch = true; |
|
offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1; |
|
this.updateMatch(true); |
|
return; |
|
} |
|
this.advanceOffsetPage(previous); |
|
} |
|
this.nextPageMatch(); |
|
}, |
|
matchesReady: function PDFFindController_matchesReady(matches) { |
|
var offset = this.offset; |
|
var numMatches = matches.length; |
|
var previous = this.state.findPrevious; |
|
if (numMatches) { |
|
this.hadMatch = true; |
|
offset.matchIdx = previous ? numMatches - 1 : 0; |
|
this.updateMatch(true); |
|
return true; |
|
} |
|
this.advanceOffsetPage(previous); |
|
if (offset.wrapped) { |
|
offset.matchIdx = null; |
|
if (this.pagesToSearch < 0) { |
|
this.updateMatch(false); |
|
return true; |
|
} |
|
} |
|
return false; |
|
}, |
|
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 |
|
}; |
|
(0, _ui_utils.scrollIntoView)(elements[beginIdx], spot, true); |
|
} |
|
}, |
|
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) { |
|
this.resumePageIdx = pageIdx; |
|
break; |
|
} |
|
} while (!this.matchesReady(matches)); |
|
}, |
|
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; |
|
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; |
|
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; |
|
}(); |
|
exports.FindStates = FindStates; |
|
exports.PDFFindController = PDFFindController; |