pdf_find_controller.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * JavaScript code in this page
  4. *
  5. * Copyright 2022 Mozilla Foundation
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @licend The above is the entire license notice for the
  20. * JavaScript code in this page
  21. */
  22. "use strict";
  23. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.PDFFindController = exports.FindState = void 0;
  27. var _ui_utils = require("./ui_utils.js");
  28. var _pdf = require("../pdf");
  29. var _pdf_find_utils = require("./pdf_find_utils.js");
  30. const FindState = {
  31. FOUND: 0,
  32. NOT_FOUND: 1,
  33. WRAPPED: 2,
  34. PENDING: 3
  35. };
  36. exports.FindState = FindState;
  37. const FIND_TIMEOUT = 250;
  38. const MATCH_SCROLL_OFFSET_TOP = -50;
  39. const MATCH_SCROLL_OFFSET_LEFT = -400;
  40. const CHARACTERS_TO_NORMALIZE = {
  41. "\u2010": "-",
  42. "\u2018": "'",
  43. "\u2019": "'",
  44. "\u201A": "'",
  45. "\u201B": "'",
  46. "\u201C": '"',
  47. "\u201D": '"',
  48. "\u201E": '"',
  49. "\u201F": '"',
  50. "\u00BC": "1/4",
  51. "\u00BD": "1/2",
  52. "\u00BE": "3/4"
  53. };
  54. const DIACRITICS_EXCEPTION = new Set([0x3099, 0x309a, 0x094d, 0x09cd, 0x0a4d, 0x0acd, 0x0b4d, 0x0bcd, 0x0c4d, 0x0ccd, 0x0d3b, 0x0d3c, 0x0d4d, 0x0dca, 0x0e3a, 0x0eba, 0x0f84, 0x1039, 0x103a, 0x1714, 0x1734, 0x17d2, 0x1a60, 0x1b44, 0x1baa, 0x1bab, 0x1bf2, 0x1bf3, 0x2d7f, 0xa806, 0xa82c, 0xa8c4, 0xa953, 0xa9c0, 0xaaf6, 0xabed, 0x0c56, 0x0f71, 0x0f72, 0x0f7a, 0x0f7b, 0x0f7c, 0x0f7d, 0x0f80, 0x0f74]);
  55. const DIACRITICS_EXCEPTION_STR = [...DIACRITICS_EXCEPTION.values()].map(x => String.fromCharCode(x)).join("");
  56. const DIACRITICS_REG_EXP = /\p{M}+/gu;
  57. const SPECIAL_CHARS_REG_EXP = /([.*+?^${}()|[\]\\])|(\p{P})|(\s+)|(\p{M})|(\p{L})/gu;
  58. const NOT_DIACRITIC_FROM_END_REG_EXP = /([^\p{M}])\p{M}*$/u;
  59. const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*([^\p{M}])/u;
  60. let normalizationRegex = null;
  61. function normalize(text) {
  62. if (!normalizationRegex) {
  63. const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join("");
  64. normalizationRegex = new RegExp(`([${replace}])|(\\p{M}+(?:-\\n)?)|(\\S-\\n)|(\\n)`, "gum");
  65. }
  66. const rawDiacriticsPositions = [];
  67. let m;
  68. while ((m = DIACRITICS_REG_EXP.exec(text)) !== null) {
  69. rawDiacriticsPositions.push([m[0].length, m.index]);
  70. }
  71. let normalized = text.normalize("NFD");
  72. const positions = [[0, 0]];
  73. let k = 0;
  74. let shift = 0;
  75. let shiftOrigin = 0;
  76. let eol = 0;
  77. let hasDiacritics = false;
  78. normalized = normalized.replace(normalizationRegex, (match, p1, p2, p3, p4, i) => {
  79. i -= shiftOrigin;
  80. if (p1) {
  81. const replacement = CHARACTERS_TO_NORMALIZE[match];
  82. const jj = replacement.length;
  83. for (let j = 1; j < jj; j++) {
  84. positions.push([i - shift + j, shift - j]);
  85. }
  86. shift -= jj - 1;
  87. return replacement;
  88. }
  89. if (p2) {
  90. const hasTrailingDashEOL = p2.endsWith("\n");
  91. const len = hasTrailingDashEOL ? p2.length - 2 : p2.length;
  92. hasDiacritics = true;
  93. let jj = len;
  94. if (i + eol === rawDiacriticsPositions[k]?.[1]) {
  95. jj -= rawDiacriticsPositions[k][0];
  96. ++k;
  97. }
  98. for (let j = 1; j < jj + 1; j++) {
  99. positions.push([i - 1 - shift + j, shift - j]);
  100. }
  101. shift -= jj;
  102. shiftOrigin += jj;
  103. if (hasTrailingDashEOL) {
  104. i += len - 1;
  105. positions.push([i - shift + 1, 1 + shift]);
  106. shift += 1;
  107. shiftOrigin += 1;
  108. eol += 1;
  109. return p2.slice(0, len);
  110. }
  111. return p2;
  112. }
  113. if (p3) {
  114. positions.push([i - shift + 1, 1 + shift]);
  115. shift += 1;
  116. shiftOrigin += 1;
  117. eol += 1;
  118. return p3.charAt(0);
  119. }
  120. positions.push([i - shift + 1, shift - 1]);
  121. shift -= 1;
  122. shiftOrigin += 1;
  123. eol += 1;
  124. return " ";
  125. });
  126. positions.push([normalized.length, shift]);
  127. return [normalized, positions, hasDiacritics];
  128. }
  129. function getOriginalIndex(diffs, pos, len) {
  130. if (!diffs) {
  131. return [pos, len];
  132. }
  133. const start = pos;
  134. const end = pos + len;
  135. let i = (0, _ui_utils.binarySearchFirstItem)(diffs, x => x[0] >= start);
  136. if (diffs[i][0] > start) {
  137. --i;
  138. }
  139. let j = (0, _ui_utils.binarySearchFirstItem)(diffs, x => x[0] >= end, i);
  140. if (diffs[j][0] > end) {
  141. --j;
  142. }
  143. return [start + diffs[i][1], len + diffs[j][1] - diffs[i][1]];
  144. }
  145. class PDFFindController {
  146. constructor({
  147. linkService,
  148. eventBus
  149. }) {
  150. this._linkService = linkService;
  151. this._eventBus = eventBus;
  152. this.#reset();
  153. eventBus._on("find", this.#onFind.bind(this));
  154. eventBus._on("findbarclose", this.#onFindBarClose.bind(this));
  155. }
  156. get highlightMatches() {
  157. return this._highlightMatches;
  158. }
  159. get pageMatches() {
  160. return this._pageMatches;
  161. }
  162. get pageMatchesLength() {
  163. return this._pageMatchesLength;
  164. }
  165. get selected() {
  166. return this._selected;
  167. }
  168. get state() {
  169. return this._state;
  170. }
  171. setDocument(pdfDocument) {
  172. if (this._pdfDocument) {
  173. this.#reset();
  174. }
  175. if (!pdfDocument) {
  176. return;
  177. }
  178. this._pdfDocument = pdfDocument;
  179. this._firstPageCapability.resolve();
  180. }
  181. #onFind(state) {
  182. if (!state) {
  183. return;
  184. }
  185. const pdfDocument = this._pdfDocument;
  186. const {
  187. type
  188. } = state;
  189. if (this._state === null || this.#shouldDirtyMatch(state)) {
  190. this._dirtyMatch = true;
  191. }
  192. this._state = state;
  193. if (type !== "highlightallchange") {
  194. this.#updateUIState(FindState.PENDING);
  195. }
  196. this._firstPageCapability.promise.then(() => {
  197. if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
  198. return;
  199. }
  200. this.#extractText();
  201. const findbarClosed = !this._highlightMatches;
  202. const pendingTimeout = !!this._findTimeout;
  203. if (this._findTimeout) {
  204. clearTimeout(this._findTimeout);
  205. this._findTimeout = null;
  206. }
  207. if (!type) {
  208. this._findTimeout = setTimeout(() => {
  209. this.#nextMatch();
  210. this._findTimeout = null;
  211. }, FIND_TIMEOUT);
  212. } else if (this._dirtyMatch) {
  213. this.#nextMatch();
  214. } else if (type === "again") {
  215. this.#nextMatch();
  216. if (findbarClosed && this._state.highlightAll) {
  217. this.#updateAllPages();
  218. }
  219. } else if (type === "highlightallchange") {
  220. if (pendingTimeout) {
  221. this.#nextMatch();
  222. } else {
  223. this._highlightMatches = true;
  224. }
  225. this.#updateAllPages();
  226. } else {
  227. this.#nextMatch();
  228. }
  229. });
  230. }
  231. scrollMatchIntoView({
  232. element = null,
  233. selectedLeft = 0,
  234. pageIndex = -1,
  235. matchIndex = -1
  236. }) {
  237. if (!this._scrollMatches || !element) {
  238. return;
  239. } else if (matchIndex === -1 || matchIndex !== this._selected.matchIdx) {
  240. return;
  241. } else if (pageIndex === -1 || pageIndex !== this._selected.pageIdx) {
  242. return;
  243. }
  244. this._scrollMatches = false;
  245. const spot = {
  246. top: MATCH_SCROLL_OFFSET_TOP,
  247. left: selectedLeft + MATCH_SCROLL_OFFSET_LEFT
  248. };
  249. (0, _ui_utils.scrollIntoView)(element, spot, true);
  250. }
  251. #reset() {
  252. this._highlightMatches = false;
  253. this._scrollMatches = false;
  254. this._pdfDocument = null;
  255. this._pageMatches = [];
  256. this._pageMatchesLength = [];
  257. this._state = null;
  258. this._selected = {
  259. pageIdx: -1,
  260. matchIdx: -1
  261. };
  262. this._offset = {
  263. pageIdx: null,
  264. matchIdx: null,
  265. wrapped: false
  266. };
  267. this._extractTextPromises = [];
  268. this._pageContents = [];
  269. this._pageDiffs = [];
  270. this._hasDiacritics = [];
  271. this._matchesCountTotal = 0;
  272. this._pagesToSearch = null;
  273. this._pendingFindMatches = new Set();
  274. this._resumePageIdx = null;
  275. this._dirtyMatch = false;
  276. clearTimeout(this._findTimeout);
  277. this._findTimeout = null;
  278. this._firstPageCapability = (0, _pdf.createPromiseCapability)();
  279. }
  280. get #query() {
  281. if (this._state.query !== this._rawQuery) {
  282. this._rawQuery = this._state.query;
  283. [this._normalizedQuery] = normalize(this._state.query);
  284. }
  285. return this._normalizedQuery;
  286. }
  287. #shouldDirtyMatch(state) {
  288. if (state.query !== this._state.query) {
  289. return true;
  290. }
  291. switch (state.type) {
  292. case "again":
  293. const pageNumber = this._selected.pageIdx + 1;
  294. const linkService = this._linkService;
  295. if (pageNumber >= 1 && pageNumber <= linkService.pagesCount && pageNumber !== linkService.page && !linkService.isPageVisible(pageNumber)) {
  296. return true;
  297. }
  298. return false;
  299. case "highlightallchange":
  300. return false;
  301. }
  302. return true;
  303. }
  304. #isEntireWord(content, startIdx, length) {
  305. let match = content.slice(0, startIdx).match(NOT_DIACRITIC_FROM_END_REG_EXP);
  306. if (match) {
  307. const first = content.charCodeAt(startIdx);
  308. const limit = match[1].charCodeAt(0);
  309. if ((0, _pdf_find_utils.getCharacterType)(first) === (0, _pdf_find_utils.getCharacterType)(limit)) {
  310. return false;
  311. }
  312. }
  313. match = content.slice(startIdx + length).match(NOT_DIACRITIC_FROM_START_REG_EXP);
  314. if (match) {
  315. const last = content.charCodeAt(startIdx + length - 1);
  316. const limit = match[1].charCodeAt(0);
  317. if ((0, _pdf_find_utils.getCharacterType)(last) === (0, _pdf_find_utils.getCharacterType)(limit)) {
  318. return false;
  319. }
  320. }
  321. return true;
  322. }
  323. #calculateRegExpMatch(query, entireWord, pageIndex, pageContent) {
  324. const matches = [],
  325. matchesLength = [];
  326. const diffs = this._pageDiffs[pageIndex];
  327. let match;
  328. while ((match = query.exec(pageContent)) !== null) {
  329. if (entireWord && !this.#isEntireWord(pageContent, match.index, match[0].length)) {
  330. continue;
  331. }
  332. const [matchPos, matchLen] = getOriginalIndex(diffs, match.index, match[0].length);
  333. if (matchLen) {
  334. matches.push(matchPos);
  335. matchesLength.push(matchLen);
  336. }
  337. }
  338. this._pageMatches[pageIndex] = matches;
  339. this._pageMatchesLength[pageIndex] = matchesLength;
  340. }
  341. #convertToRegExpString(query, hasDiacritics) {
  342. const {
  343. matchDiacritics
  344. } = this._state;
  345. let isUnicode = false;
  346. query = query.replace(SPECIAL_CHARS_REG_EXP, (match, p1, p2, p3, p4, p5) => {
  347. if (p1) {
  348. return `[ ]*\\${p1}[ ]*`;
  349. }
  350. if (p2) {
  351. return `[ ]*${p2}[ ]*`;
  352. }
  353. if (p3) {
  354. return "[ ]+";
  355. }
  356. if (matchDiacritics) {
  357. return p4 || p5;
  358. }
  359. if (p4) {
  360. return DIACRITICS_EXCEPTION.has(p4.charCodeAt(0)) ? p4 : "";
  361. }
  362. if (hasDiacritics) {
  363. isUnicode = true;
  364. return `${p5}\\p{M}*`;
  365. }
  366. return p5;
  367. });
  368. const trailingSpaces = "[ ]*";
  369. if (query.endsWith(trailingSpaces)) {
  370. query = query.slice(0, query.length - trailingSpaces.length);
  371. }
  372. if (matchDiacritics) {
  373. if (hasDiacritics) {
  374. isUnicode = true;
  375. query = `${query}(?=[${DIACRITICS_EXCEPTION_STR}]|[^\\p{M}]|$)`;
  376. }
  377. }
  378. return [isUnicode, query];
  379. }
  380. #calculateMatch(pageIndex) {
  381. let query = this.#query;
  382. if (query.length === 0) {
  383. return;
  384. }
  385. const {
  386. caseSensitive,
  387. entireWord,
  388. phraseSearch
  389. } = this._state;
  390. const pageContent = this._pageContents[pageIndex];
  391. const hasDiacritics = this._hasDiacritics[pageIndex];
  392. let isUnicode = false;
  393. if (phraseSearch) {
  394. [isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics);
  395. } else {
  396. const match = query.match(/\S+/g);
  397. if (match) {
  398. query = match.sort().reverse().map(q => {
  399. const [isUnicodePart, queryPart] = this.#convertToRegExpString(q, hasDiacritics);
  400. isUnicode ||= isUnicodePart;
  401. return `(${queryPart})`;
  402. }).join("|");
  403. }
  404. }
  405. const flags = `g${isUnicode ? "u" : ""}${caseSensitive ? "" : "i"}`;
  406. query = new RegExp(query, flags);
  407. this.#calculateRegExpMatch(query, entireWord, pageIndex, pageContent);
  408. if (this._state.highlightAll) {
  409. this.#updatePage(pageIndex);
  410. }
  411. if (this._resumePageIdx === pageIndex) {
  412. this._resumePageIdx = null;
  413. this.#nextPageMatch();
  414. }
  415. const pageMatchesCount = this._pageMatches[pageIndex].length;
  416. if (pageMatchesCount > 0) {
  417. this._matchesCountTotal += pageMatchesCount;
  418. this.#updateUIResultsCount();
  419. }
  420. }
  421. #extractText() {
  422. if (this._extractTextPromises.length > 0) {
  423. return;
  424. }
  425. let promise = Promise.resolve();
  426. for (let i = 0, ii = this._linkService.pagesCount; i < ii; i++) {
  427. const extractTextCapability = (0, _pdf.createPromiseCapability)();
  428. this._extractTextPromises[i] = extractTextCapability.promise;
  429. promise = promise.then(() => {
  430. return this._pdfDocument.getPage(i + 1).then(pdfPage => {
  431. return pdfPage.getTextContent();
  432. }).then(textContent => {
  433. const strBuf = [];
  434. for (const textItem of textContent.items) {
  435. strBuf.push(textItem.str);
  436. if (textItem.hasEOL) {
  437. strBuf.push("\n");
  438. }
  439. }
  440. [this._pageContents[i], this._pageDiffs[i], this._hasDiacritics[i]] = normalize(strBuf.join(""));
  441. extractTextCapability.resolve();
  442. }, reason => {
  443. console.error(`Unable to get text content for page ${i + 1}`, reason);
  444. this._pageContents[i] = "";
  445. this._pageDiffs[i] = null;
  446. this._hasDiacritics[i] = false;
  447. extractTextCapability.resolve();
  448. });
  449. });
  450. }
  451. }
  452. #updatePage(index) {
  453. if (this._scrollMatches && this._selected.pageIdx === index) {
  454. this._linkService.page = index + 1;
  455. }
  456. this._eventBus.dispatch("updatetextlayermatches", {
  457. source: this,
  458. pageIndex: index
  459. });
  460. }
  461. #updateAllPages() {
  462. this._eventBus.dispatch("updatetextlayermatches", {
  463. source: this,
  464. pageIndex: -1
  465. });
  466. }
  467. #nextMatch() {
  468. const previous = this._state.findPrevious;
  469. const currentPageIndex = this._linkService.page - 1;
  470. const numPages = this._linkService.pagesCount;
  471. this._highlightMatches = true;
  472. if (this._dirtyMatch) {
  473. this._dirtyMatch = false;
  474. this._selected.pageIdx = this._selected.matchIdx = -1;
  475. this._offset.pageIdx = currentPageIndex;
  476. this._offset.matchIdx = null;
  477. this._offset.wrapped = false;
  478. this._resumePageIdx = null;
  479. this._pageMatches.length = 0;
  480. this._pageMatchesLength.length = 0;
  481. this._matchesCountTotal = 0;
  482. this.#updateAllPages();
  483. for (let i = 0; i < numPages; i++) {
  484. if (this._pendingFindMatches.has(i)) {
  485. continue;
  486. }
  487. this._pendingFindMatches.add(i);
  488. this._extractTextPromises[i].then(() => {
  489. this._pendingFindMatches.delete(i);
  490. this.#calculateMatch(i);
  491. });
  492. }
  493. }
  494. if (this.#query === "") {
  495. this.#updateUIState(FindState.FOUND);
  496. return;
  497. }
  498. if (this._resumePageIdx) {
  499. return;
  500. }
  501. const offset = this._offset;
  502. this._pagesToSearch = numPages;
  503. if (offset.matchIdx !== null) {
  504. const numPageMatches = this._pageMatches[offset.pageIdx].length;
  505. if (!previous && offset.matchIdx + 1 < numPageMatches || previous && offset.matchIdx > 0) {
  506. offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
  507. this.#updateMatch(true);
  508. return;
  509. }
  510. this.#advanceOffsetPage(previous);
  511. }
  512. this.#nextPageMatch();
  513. }
  514. #matchesReady(matches) {
  515. const offset = this._offset;
  516. const numMatches = matches.length;
  517. const previous = this._state.findPrevious;
  518. if (numMatches) {
  519. offset.matchIdx = previous ? numMatches - 1 : 0;
  520. this.#updateMatch(true);
  521. return true;
  522. }
  523. this.#advanceOffsetPage(previous);
  524. if (offset.wrapped) {
  525. offset.matchIdx = null;
  526. if (this._pagesToSearch < 0) {
  527. this.#updateMatch(false);
  528. return true;
  529. }
  530. }
  531. return false;
  532. }
  533. #nextPageMatch() {
  534. if (this._resumePageIdx !== null) {
  535. console.error("There can only be one pending page.");
  536. }
  537. let matches = null;
  538. do {
  539. const pageIdx = this._offset.pageIdx;
  540. matches = this._pageMatches[pageIdx];
  541. if (!matches) {
  542. this._resumePageIdx = pageIdx;
  543. break;
  544. }
  545. } while (!this.#matchesReady(matches));
  546. }
  547. #advanceOffsetPage(previous) {
  548. const offset = this._offset;
  549. const numPages = this._linkService.pagesCount;
  550. offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
  551. offset.matchIdx = null;
  552. this._pagesToSearch--;
  553. if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
  554. offset.pageIdx = previous ? numPages - 1 : 0;
  555. offset.wrapped = true;
  556. }
  557. }
  558. #updateMatch(found = false) {
  559. let state = FindState.NOT_FOUND;
  560. const wrapped = this._offset.wrapped;
  561. this._offset.wrapped = false;
  562. if (found) {
  563. const previousPage = this._selected.pageIdx;
  564. this._selected.pageIdx = this._offset.pageIdx;
  565. this._selected.matchIdx = this._offset.matchIdx;
  566. state = wrapped ? FindState.WRAPPED : FindState.FOUND;
  567. if (previousPage !== -1 && previousPage !== this._selected.pageIdx) {
  568. this.#updatePage(previousPage);
  569. }
  570. }
  571. this.#updateUIState(state, this._state.findPrevious);
  572. if (this._selected.pageIdx !== -1) {
  573. this._scrollMatches = true;
  574. this.#updatePage(this._selected.pageIdx);
  575. }
  576. }
  577. #onFindBarClose(evt) {
  578. const pdfDocument = this._pdfDocument;
  579. this._firstPageCapability.promise.then(() => {
  580. if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
  581. return;
  582. }
  583. if (this._findTimeout) {
  584. clearTimeout(this._findTimeout);
  585. this._findTimeout = null;
  586. }
  587. if (this._resumePageIdx) {
  588. this._resumePageIdx = null;
  589. this._dirtyMatch = true;
  590. }
  591. this.#updateUIState(FindState.FOUND);
  592. this._highlightMatches = false;
  593. this.#updateAllPages();
  594. });
  595. }
  596. #requestMatchesCount() {
  597. const {
  598. pageIdx,
  599. matchIdx
  600. } = this._selected;
  601. let current = 0,
  602. total = this._matchesCountTotal;
  603. if (matchIdx !== -1) {
  604. for (let i = 0; i < pageIdx; i++) {
  605. current += this._pageMatches[i]?.length || 0;
  606. }
  607. current += matchIdx + 1;
  608. }
  609. if (current < 1 || current > total) {
  610. current = total = 0;
  611. }
  612. return {
  613. current,
  614. total
  615. };
  616. }
  617. #updateUIResultsCount() {
  618. this._eventBus.dispatch("updatefindmatchescount", {
  619. source: this,
  620. matchesCount: this.#requestMatchesCount()
  621. });
  622. }
  623. #updateUIState(state, previous = false) {
  624. this._eventBus.dispatch("updatefindcontrolstate", {
  625. source: this,
  626. state,
  627. previous,
  628. matchesCount: this.#requestMatchesCount(),
  629. rawQuery: this._state?.query ?? null
  630. });
  631. }
  632. }
  633. exports.PDFFindController = PDFFindController;