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.
435 lines
14 KiB
435 lines
14 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.FindState = undefined; |
|
|
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); |
|
|
|
var _pdf = require('../pdf'); |
|
|
|
var _ui_utils = require('./ui_utils'); |
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } |
|
|
|
var FindState = { |
|
FOUND: 0, |
|
NOT_FOUND: 1, |
|
WRAPPED: 2, |
|
PENDING: 3 |
|
}; |
|
var FIND_SCROLL_OFFSET_TOP = -50; |
|
var FIND_SCROLL_OFFSET_LEFT = -400; |
|
var FIND_TIMEOUT = 250; |
|
var CHARACTERS_TO_NORMALIZE = { |
|
'\u2018': '\'', |
|
'\u2019': '\'', |
|
'\u201A': '\'', |
|
'\u201B': '\'', |
|
'\u201C': '"', |
|
'\u201D': '"', |
|
'\u201E': '"', |
|
'\u201F': '"', |
|
'\xBC': '1/4', |
|
'\xBD': '1/2', |
|
'\xBE': '3/4' |
|
}; |
|
|
|
var PDFFindController = function () { |
|
function PDFFindController(_ref) { |
|
var pdfViewer = _ref.pdfViewer; |
|
|
|
_classCallCheck(this, PDFFindController); |
|
|
|
this.pdfViewer = pdfViewer; |
|
this.onUpdateResultsCount = null; |
|
this.onUpdateState = null; |
|
this.reset(); |
|
var replace = Object.keys(CHARACTERS_TO_NORMALIZE).join(''); |
|
this.normalizationRegex = new RegExp('[' + replace + ']', 'g'); |
|
} |
|
|
|
_createClass(PDFFindController, [{ |
|
key: 'reset', |
|
value: function 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; |
|
}); |
|
} |
|
}, { |
|
key: 'normalize', |
|
value: function normalize(text) { |
|
return text.replace(this.normalizationRegex, function (ch) { |
|
return CHARACTERS_TO_NORMALIZE[ch]; |
|
}); |
|
} |
|
}, { |
|
key: '_prepareMatches', |
|
value: function _prepareMatches(matchesWithLength, matches, matchesLength) { |
|
function isSubTerm(matchesWithLength, currentIndex) { |
|
var currentElem = matchesWithLength[currentIndex]; |
|
var 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--) { |
|
var 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; |
|
} |
|
matchesWithLength.sort(function (a, b) { |
|
return a.match === b.match ? a.matchLength - b.matchLength : a.match - b.match; |
|
}); |
|
for (var i = 0, len = matchesWithLength.length; i < len; i++) { |
|
if (isSubTerm(matchesWithLength, i)) { |
|
continue; |
|
} |
|
matches.push(matchesWithLength[i].match); |
|
matchesLength.push(matchesWithLength[i].matchLength); |
|
} |
|
} |
|
}, { |
|
key: 'calcFindPhraseMatch', |
|
value: function 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; |
|
} |
|
}, { |
|
key: 'calcFindWordMatch', |
|
value: function calcFindWordMatch(query, pageIndex, pageContent) { |
|
var matchesWithLength = []; |
|
var queryArray = query.match(/\S+/g); |
|
for (var i = 0, len = queryArray.length; i < len; i++) { |
|
var subquery = queryArray[i]; |
|
var subqueryLen = subquery.length; |
|
var 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]); |
|
} |
|
}, { |
|
key: 'calcFindMatch', |
|
value: function 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(); |
|
} |
|
} |
|
}, { |
|
key: 'extractText', |
|
value: 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); |
|
}, function (reason) { |
|
console.error('Unable to get page ' + (i + 1) + ' text content', reason); |
|
_this2.pageContents[i] = ''; |
|
extractTextCapability.resolve(i); |
|
}); |
|
}); |
|
}; |
|
|
|
for (var i = 0, ii = this.pdfViewer.pagesCount; i < ii; i++) { |
|
_loop(i, ii); |
|
} |
|
} |
|
}, { |
|
key: 'executeCommand', |
|
value: function executeCommand(cmd, state) { |
|
var _this3 = this; |
|
|
|
if (this.state === null || cmd !== 'findagain') { |
|
this.dirtyMatch = true; |
|
} |
|
this.state = state; |
|
this.updateUIState(FindState.PENDING); |
|
this._firstPagePromise.then(function () { |
|
_this3.extractText(); |
|
clearTimeout(_this3.findTimeout); |
|
if (cmd === 'find') { |
|
_this3.findTimeout = setTimeout(_this3.nextMatch.bind(_this3), FIND_TIMEOUT); |
|
} else { |
|
_this3.nextMatch(); |
|
} |
|
}); |
|
} |
|
}, { |
|
key: 'updatePage', |
|
value: function updatePage(index) { |
|
if (this.selected.pageIdx === index) { |
|
this.pdfViewer.currentPageNumber = index + 1; |
|
} |
|
var page = this.pdfViewer.getPageView(index); |
|
if (page.textLayer) { |
|
page.textLayer.updateMatches(); |
|
} |
|
} |
|
}, { |
|
key: 'nextMatch', |
|
value: function 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(FindState.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(); |
|
} |
|
}, { |
|
key: 'matchesReady', |
|
value: function 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; |
|
} |
|
}, { |
|
key: 'updateMatchPosition', |
|
value: function updateMatchPosition(pageIndex, matchIndex, elements, beginIdx) { |
|
if (this.selected.matchIdx === matchIndex && this.selected.pageIdx === pageIndex) { |
|
var spot = { |
|
top: FIND_SCROLL_OFFSET_TOP, |
|
left: FIND_SCROLL_OFFSET_LEFT |
|
}; |
|
(0, _ui_utils.scrollIntoView)(elements[beginIdx], spot, true); |
|
} |
|
} |
|
}, { |
|
key: 'nextPageMatch', |
|
value: function nextPageMatch() { |
|
if (this.resumePageIdx !== null) { |
|
console.error('There can only be one pending page.'); |
|
} |
|
var matches = null; |
|
do { |
|
var pageIdx = this.offset.pageIdx; |
|
matches = this.pageMatches[pageIdx]; |
|
if (!matches) { |
|
this.resumePageIdx = pageIdx; |
|
break; |
|
} |
|
} while (!this.matchesReady(matches)); |
|
} |
|
}, { |
|
key: 'advanceOffsetPage', |
|
value: function 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; |
|
} |
|
} |
|
}, { |
|
key: 'updateMatch', |
|
value: function updateMatch() { |
|
var found = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; |
|
|
|
var state = FindState.NOT_FOUND; |
|
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 ? FindState.WRAPPED : FindState.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); |
|
} |
|
} |
|
}, { |
|
key: 'updateUIResultsCount', |
|
value: function updateUIResultsCount() { |
|
if (this.onUpdateResultsCount) { |
|
this.onUpdateResultsCount(this.matchCount); |
|
} |
|
} |
|
}, { |
|
key: 'updateUIState', |
|
value: function updateUIState(state, previous) { |
|
if (this.onUpdateState) { |
|
this.onUpdateState(state, previous, this.matchCount); |
|
} |
|
} |
|
}]); |
|
|
|
return PDFFindController; |
|
}(); |
|
|
|
exports.FindState = FindState; |
|
exports.PDFFindController = PDFFindController; |