2
0

pdf_find_controller.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. /* Copyright 2017 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. 'use strict';
  16. Object.defineProperty(exports, "__esModule", {
  17. value: true
  18. });
  19. exports.PDFFindController = exports.FindStates = undefined;
  20. var _pdfjs = require('./pdfjs');
  21. var _ui_utils = require('./ui_utils');
  22. var FindStates = {
  23. FIND_FOUND: 0,
  24. FIND_NOTFOUND: 1,
  25. FIND_WRAPPED: 2,
  26. FIND_PENDING: 3
  27. };
  28. var FIND_SCROLL_OFFSET_TOP = -50;
  29. var FIND_SCROLL_OFFSET_LEFT = -400;
  30. var CHARACTERS_TO_NORMALIZE = {
  31. '\u2018': '\'',
  32. '\u2019': '\'',
  33. '\u201A': '\'',
  34. '\u201B': '\'',
  35. '\u201C': '"',
  36. '\u201D': '"',
  37. '\u201E': '"',
  38. '\u201F': '"',
  39. '\xBC': '1/4',
  40. '\xBD': '1/2',
  41. '\xBE': '3/4'
  42. };
  43. var PDFFindController = function PDFFindControllerClosure() {
  44. function PDFFindController(options) {
  45. this.pdfViewer = options.pdfViewer || null;
  46. this.onUpdateResultsCount = null;
  47. this.onUpdateState = null;
  48. this.reset();
  49. var replace = Object.keys(CHARACTERS_TO_NORMALIZE).join('');
  50. this.normalizationRegex = new RegExp('[' + replace + ']', 'g');
  51. }
  52. PDFFindController.prototype = {
  53. reset: function PDFFindController_reset() {
  54. var _this = this;
  55. this.startedTextExtraction = false;
  56. this.extractTextPromises = [];
  57. this.pendingFindMatches = Object.create(null);
  58. this.active = false;
  59. this.pageContents = [];
  60. this.pageMatches = [];
  61. this.pageMatchesLength = null;
  62. this.matchCount = 0;
  63. this.selected = {
  64. pageIdx: -1,
  65. matchIdx: -1
  66. };
  67. this.offset = {
  68. pageIdx: null,
  69. matchIdx: null
  70. };
  71. this.pagesToSearch = null;
  72. this.resumePageIdx = null;
  73. this.state = null;
  74. this.dirtyMatch = false;
  75. this.findTimeout = null;
  76. this._firstPagePromise = new Promise(function (resolve) {
  77. _this.resolveFirstPage = resolve;
  78. });
  79. },
  80. normalize: function PDFFindController_normalize(text) {
  81. return text.replace(this.normalizationRegex, function (ch) {
  82. return CHARACTERS_TO_NORMALIZE[ch];
  83. });
  84. },
  85. _prepareMatches: function PDFFindController_prepareMatches(matchesWithLength, matches, matchesLength) {
  86. function isSubTerm(matchesWithLength, currentIndex) {
  87. var currentElem, prevElem, nextElem;
  88. currentElem = matchesWithLength[currentIndex];
  89. nextElem = matchesWithLength[currentIndex + 1];
  90. if (currentIndex < matchesWithLength.length - 1 && currentElem.match === nextElem.match) {
  91. currentElem.skipped = true;
  92. return true;
  93. }
  94. for (var i = currentIndex - 1; i >= 0; i--) {
  95. prevElem = matchesWithLength[i];
  96. if (prevElem.skipped) {
  97. continue;
  98. }
  99. if (prevElem.match + prevElem.matchLength < currentElem.match) {
  100. break;
  101. }
  102. if (prevElem.match + prevElem.matchLength >= currentElem.match + currentElem.matchLength) {
  103. currentElem.skipped = true;
  104. return true;
  105. }
  106. }
  107. return false;
  108. }
  109. var i, len;
  110. matchesWithLength.sort(function (a, b) {
  111. return a.match === b.match ? a.matchLength - b.matchLength : a.match - b.match;
  112. });
  113. for (i = 0, len = matchesWithLength.length; i < len; i++) {
  114. if (isSubTerm(matchesWithLength, i)) {
  115. continue;
  116. }
  117. matches.push(matchesWithLength[i].match);
  118. matchesLength.push(matchesWithLength[i].matchLength);
  119. }
  120. },
  121. calcFindPhraseMatch: function PDFFindController_calcFindPhraseMatch(query, pageIndex, pageContent) {
  122. var matches = [];
  123. var queryLen = query.length;
  124. var matchIdx = -queryLen;
  125. while (true) {
  126. matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
  127. if (matchIdx === -1) {
  128. break;
  129. }
  130. matches.push(matchIdx);
  131. }
  132. this.pageMatches[pageIndex] = matches;
  133. },
  134. calcFindWordMatch: function PDFFindController_calcFindWordMatch(query, pageIndex, pageContent) {
  135. var matchesWithLength = [];
  136. var queryArray = query.match(/\S+/g);
  137. var subquery, subqueryLen, matchIdx;
  138. for (var i = 0, len = queryArray.length; i < len; i++) {
  139. subquery = queryArray[i];
  140. subqueryLen = subquery.length;
  141. matchIdx = -subqueryLen;
  142. while (true) {
  143. matchIdx = pageContent.indexOf(subquery, matchIdx + subqueryLen);
  144. if (matchIdx === -1) {
  145. break;
  146. }
  147. matchesWithLength.push({
  148. match: matchIdx,
  149. matchLength: subqueryLen,
  150. skipped: false
  151. });
  152. }
  153. }
  154. if (!this.pageMatchesLength) {
  155. this.pageMatchesLength = [];
  156. }
  157. this.pageMatchesLength[pageIndex] = [];
  158. this.pageMatches[pageIndex] = [];
  159. this._prepareMatches(matchesWithLength, this.pageMatches[pageIndex], this.pageMatchesLength[pageIndex]);
  160. },
  161. calcFindMatch: function PDFFindController_calcFindMatch(pageIndex) {
  162. var pageContent = this.normalize(this.pageContents[pageIndex]);
  163. var query = this.normalize(this.state.query);
  164. var caseSensitive = this.state.caseSensitive;
  165. var phraseSearch = this.state.phraseSearch;
  166. var queryLen = query.length;
  167. if (queryLen === 0) {
  168. return;
  169. }
  170. if (!caseSensitive) {
  171. pageContent = pageContent.toLowerCase();
  172. query = query.toLowerCase();
  173. }
  174. if (phraseSearch) {
  175. this.calcFindPhraseMatch(query, pageIndex, pageContent);
  176. } else {
  177. this.calcFindWordMatch(query, pageIndex, pageContent);
  178. }
  179. this.updatePage(pageIndex);
  180. if (this.resumePageIdx === pageIndex) {
  181. this.resumePageIdx = null;
  182. this.nextPageMatch();
  183. }
  184. if (this.pageMatches[pageIndex].length > 0) {
  185. this.matchCount += this.pageMatches[pageIndex].length;
  186. this.updateUIResultsCount();
  187. }
  188. },
  189. extractText: function extractText() {
  190. var _this2 = this;
  191. if (this.startedTextExtraction) {
  192. return;
  193. }
  194. this.startedTextExtraction = true;
  195. this.pageContents.length = 0;
  196. var promise = Promise.resolve();
  197. var _loop = function _loop(i, ii) {
  198. var extractTextCapability = (0, _pdfjs.createPromiseCapability)();
  199. _this2.extractTextPromises[i] = extractTextCapability.promise;
  200. promise = promise.then(function () {
  201. return _this2.pdfViewer.getPageTextContent(i).then(function (textContent) {
  202. var textItems = textContent.items;
  203. var strBuf = [];
  204. for (var j = 0, jj = textItems.length; j < jj; j++) {
  205. strBuf.push(textItems[j].str);
  206. }
  207. _this2.pageContents[i] = strBuf.join('');
  208. extractTextCapability.resolve(i);
  209. });
  210. });
  211. };
  212. for (var i = 0, ii = this.pdfViewer.pagesCount; i < ii; i++) {
  213. _loop(i, ii);
  214. }
  215. },
  216. executeCommand: function PDFFindController_executeCommand(cmd, state) {
  217. var _this3 = this;
  218. if (this.state === null || cmd !== 'findagain') {
  219. this.dirtyMatch = true;
  220. }
  221. this.state = state;
  222. this.updateUIState(FindStates.FIND_PENDING);
  223. this._firstPagePromise.then(function () {
  224. _this3.extractText();
  225. clearTimeout(_this3.findTimeout);
  226. if (cmd === 'find') {
  227. _this3.findTimeout = setTimeout(_this3.nextMatch.bind(_this3), 250);
  228. } else {
  229. _this3.nextMatch();
  230. }
  231. });
  232. },
  233. updatePage: function PDFFindController_updatePage(index) {
  234. if (this.selected.pageIdx === index) {
  235. this.pdfViewer.currentPageNumber = index + 1;
  236. }
  237. var page = this.pdfViewer.getPageView(index);
  238. if (page.textLayer) {
  239. page.textLayer.updateMatches();
  240. }
  241. },
  242. nextMatch: function PDFFindController_nextMatch() {
  243. var _this4 = this;
  244. var previous = this.state.findPrevious;
  245. var currentPageIndex = this.pdfViewer.currentPageNumber - 1;
  246. var numPages = this.pdfViewer.pagesCount;
  247. this.active = true;
  248. if (this.dirtyMatch) {
  249. this.dirtyMatch = false;
  250. this.selected.pageIdx = this.selected.matchIdx = -1;
  251. this.offset.pageIdx = currentPageIndex;
  252. this.offset.matchIdx = null;
  253. this.hadMatch = false;
  254. this.resumePageIdx = null;
  255. this.pageMatches = [];
  256. this.matchCount = 0;
  257. this.pageMatchesLength = null;
  258. for (var i = 0; i < numPages; i++) {
  259. this.updatePage(i);
  260. if (!(i in this.pendingFindMatches)) {
  261. this.pendingFindMatches[i] = true;
  262. this.extractTextPromises[i].then(function (pageIdx) {
  263. delete _this4.pendingFindMatches[pageIdx];
  264. _this4.calcFindMatch(pageIdx);
  265. });
  266. }
  267. }
  268. }
  269. if (this.state.query === '') {
  270. this.updateUIState(FindStates.FIND_FOUND);
  271. return;
  272. }
  273. if (this.resumePageIdx) {
  274. return;
  275. }
  276. var offset = this.offset;
  277. this.pagesToSearch = numPages;
  278. if (offset.matchIdx !== null) {
  279. var numPageMatches = this.pageMatches[offset.pageIdx].length;
  280. if (!previous && offset.matchIdx + 1 < numPageMatches || previous && offset.matchIdx > 0) {
  281. this.hadMatch = true;
  282. offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
  283. this.updateMatch(true);
  284. return;
  285. }
  286. this.advanceOffsetPage(previous);
  287. }
  288. this.nextPageMatch();
  289. },
  290. matchesReady: function PDFFindController_matchesReady(matches) {
  291. var offset = this.offset;
  292. var numMatches = matches.length;
  293. var previous = this.state.findPrevious;
  294. if (numMatches) {
  295. this.hadMatch = true;
  296. offset.matchIdx = previous ? numMatches - 1 : 0;
  297. this.updateMatch(true);
  298. return true;
  299. }
  300. this.advanceOffsetPage(previous);
  301. if (offset.wrapped) {
  302. offset.matchIdx = null;
  303. if (this.pagesToSearch < 0) {
  304. this.updateMatch(false);
  305. return true;
  306. }
  307. }
  308. return false;
  309. },
  310. updateMatchPosition: function PDFFindController_updateMatchPosition(pageIndex, index, elements, beginIdx) {
  311. if (this.selected.matchIdx === index && this.selected.pageIdx === pageIndex) {
  312. var spot = {
  313. top: FIND_SCROLL_OFFSET_TOP,
  314. left: FIND_SCROLL_OFFSET_LEFT
  315. };
  316. (0, _ui_utils.scrollIntoView)(elements[beginIdx], spot, true);
  317. }
  318. },
  319. nextPageMatch: function PDFFindController_nextPageMatch() {
  320. if (this.resumePageIdx !== null) {
  321. console.error('There can only be one pending page.');
  322. }
  323. do {
  324. var pageIdx = this.offset.pageIdx;
  325. var matches = this.pageMatches[pageIdx];
  326. if (!matches) {
  327. this.resumePageIdx = pageIdx;
  328. break;
  329. }
  330. } while (!this.matchesReady(matches));
  331. },
  332. advanceOffsetPage: function PDFFindController_advanceOffsetPage(previous) {
  333. var offset = this.offset;
  334. var numPages = this.extractTextPromises.length;
  335. offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
  336. offset.matchIdx = null;
  337. this.pagesToSearch--;
  338. if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
  339. offset.pageIdx = previous ? numPages - 1 : 0;
  340. offset.wrapped = true;
  341. }
  342. },
  343. updateMatch: function PDFFindController_updateMatch(found) {
  344. var state = FindStates.FIND_NOTFOUND;
  345. var wrapped = this.offset.wrapped;
  346. this.offset.wrapped = false;
  347. if (found) {
  348. var previousPage = this.selected.pageIdx;
  349. this.selected.pageIdx = this.offset.pageIdx;
  350. this.selected.matchIdx = this.offset.matchIdx;
  351. state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND;
  352. if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
  353. this.updatePage(previousPage);
  354. }
  355. }
  356. this.updateUIState(state, this.state.findPrevious);
  357. if (this.selected.pageIdx !== -1) {
  358. this.updatePage(this.selected.pageIdx);
  359. }
  360. },
  361. updateUIResultsCount: function PDFFindController_updateUIResultsCount() {
  362. if (this.onUpdateResultsCount) {
  363. this.onUpdateResultsCount(this.matchCount);
  364. }
  365. },
  366. updateUIState: function PDFFindController_updateUIState(state, previous) {
  367. if (this.onUpdateState) {
  368. this.onUpdateState(state, previous, this.matchCount);
  369. }
  370. }
  371. };
  372. return PDFFindController;
  373. }();
  374. exports.FindStates = FindStates;
  375. exports.PDFFindController = PDFFindController;