Skip to content

Commit f04deee

Browse files
authored
Merge pull request #20577 from calixteman/update_search
The 'find in page' feature must correctly work after the pages have been reorganized (bug 2010814)
2 parents fef0cb1 + 3a20ea7 commit f04deee

4 files changed

Lines changed: 196 additions & 10 deletions

File tree

test/integration/reorganize_pages_spec.mjs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import {
1717
awaitPromise,
18+
clearInput,
1819
closePages,
1920
createPromise,
2021
dragAndDrop,
@@ -56,6 +57,46 @@ function waitForPagesEdited(page) {
5657
});
5758
}
5859

60+
function getSearchResults(page) {
61+
return page.evaluate(() => {
62+
const pages = document.querySelectorAll(".page");
63+
const results = [];
64+
for (let i = 0; i < pages.length; i++) {
65+
const domPage = pages[i];
66+
const pageNumber = parseInt(domPage.getAttribute("data-page-number"), 10);
67+
const highlights = domPage.querySelectorAll("span.highlight");
68+
if (highlights.length === 0) {
69+
continue;
70+
}
71+
results.push([
72+
i + 1,
73+
pageNumber,
74+
Array.from(highlights).map(span => span.textContent),
75+
]);
76+
}
77+
return results;
78+
});
79+
}
80+
81+
function movePages(page, selectedPages, atIndex) {
82+
return page.evaluate(
83+
(selected, index) => {
84+
const viewer = window.PDFViewerApplication.pdfViewer;
85+
const pagesToMove = Array.from(selected).sort((a, b) => a - b);
86+
viewer.pagesMapper.pagesNumber =
87+
document.querySelectorAll(".page").length;
88+
viewer.pagesMapper.movePages(new Set(pagesToMove), pagesToMove, index);
89+
window.PDFViewerApplication.eventBus.dispatch("pagesedited", {
90+
pagesMapper: viewer.pagesMapper,
91+
index,
92+
pagesToMove,
93+
});
94+
},
95+
selectedPages,
96+
atIndex
97+
);
98+
}
99+
59100
describe("Reorganize Pages View", () => {
60101
describe("Drag & Drop", () => {
61102
let pages;
@@ -262,4 +303,116 @@ describe("Reorganize Pages View", () => {
262303
);
263304
});
264305
});
306+
307+
describe("Search in pdf", () => {
308+
let pages;
309+
310+
beforeEach(async () => {
311+
pages = await loadAndWait(
312+
"page_with_number.pdf",
313+
"#viewsManagerToggleButton",
314+
"1",
315+
null,
316+
{ enableSplitMerge: true }
317+
);
318+
});
319+
320+
afterEach(async () => {
321+
await closePages(pages);
322+
});
323+
324+
it("should check if the search is working after moving pages", async () => {
325+
await Promise.all(
326+
pages.map(async ([browserName, page]) => {
327+
await page.click("#viewFindButton");
328+
await page.waitForSelector(":has(> #findHighlightAll)", {
329+
visible: true,
330+
});
331+
await page.click(":has(> #findHighlightAll)");
332+
333+
await page.waitForSelector("#findInput", { visible: true });
334+
await page.type("#findInput", "1");
335+
await page.keyboard.press("Enter");
336+
337+
await page.waitForFunction(
338+
() => document.querySelectorAll("span.highlight").length === 10
339+
);
340+
341+
let results = await getSearchResults(page);
342+
expect(results)
343+
.withContext(`In ${browserName}`)
344+
.toEqual([
345+
// Page number, Id, [matches]
346+
[1, 1, ["1"]],
347+
[10, 10, ["1"]],
348+
[11, 11, ["1", "1"]],
349+
[12, 12, ["1"]],
350+
[13, 13, ["1"]],
351+
[14, 14, ["1"]],
352+
[15, 15, ["1"]],
353+
[16, 16, ["1"]],
354+
[17, 17, ["1"]],
355+
]);
356+
357+
await movePages(page, [11, 2], 3);
358+
await page.waitForFunction(
359+
() => document.querySelectorAll("span.highlight").length === 0
360+
);
361+
362+
await clearInput(page, "#findInput", true);
363+
await page.type("#findInput", "1");
364+
await page.keyboard.press("Enter");
365+
366+
await page.waitForFunction(
367+
() => document.querySelectorAll("span.highlight").length === 10
368+
);
369+
370+
results = await getSearchResults(page);
371+
expect(results)
372+
.withContext(`In ${browserName}`)
373+
.toEqual([
374+
// Page number, Id, [matches]
375+
[1, 1, ["1"]],
376+
[4, 11, ["1", "1"]],
377+
[11, 10, ["1"]],
378+
[12, 12, ["1"]],
379+
[13, 13, ["1"]],
380+
[14, 14, ["1"]],
381+
[15, 15, ["1"]],
382+
[16, 16, ["1"]],
383+
[17, 17, ["1"]],
384+
]);
385+
386+
await movePages(page, [13], 0);
387+
await page.waitForFunction(
388+
() => document.querySelectorAll("span.highlight").length === 0
389+
);
390+
391+
await clearInput(page, "#findInput", true);
392+
await page.type("#findInput", "1");
393+
await page.keyboard.press("Enter");
394+
395+
await page.waitForFunction(
396+
() => document.querySelectorAll("span.highlight").length === 10
397+
);
398+
399+
results = await getSearchResults(page);
400+
expect(results)
401+
.withContext(`In ${browserName}`)
402+
.toEqual([
403+
// Page number, Id, [matches]
404+
[1, 13, ["1"]],
405+
[2, 1, ["1"]],
406+
[5, 11, ["1", "1"]],
407+
[12, 10, ["1"]],
408+
[13, 12, ["1"]],
409+
[14, 14, ["1"]],
410+
[15, 15, ["1"]],
411+
[16, 16, ["1"]],
412+
[17, 17, ["1"]],
413+
]);
414+
})
415+
);
416+
});
417+
});
265418
});

web/pdf_find_controller.js

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
/** @typedef {import("./event_utils").EventBus} EventBus */
1818
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
1919

20-
import { binarySearchFirstItem, scrollIntoView } from "./ui_utils.js";
20+
import {
21+
binarySearchFirstItem,
22+
PagesMapper,
23+
scrollIntoView,
24+
} from "./ui_utils.js";
2125
import { getCharacterType, getNormalizeWithNFKC } from "./pdf_find_utils.js";
2226

2327
const FindState = {
@@ -422,6 +426,8 @@ class PDFFindController {
422426

423427
#visitedPagesCount = 0;
424428

429+
#pagesMapper = PagesMapper.instance;
430+
425431
/**
426432
* @param {PDFFindControllerOptions} options
427433
*/
@@ -439,6 +445,7 @@ class PDFFindController {
439445
this.#reset();
440446
eventBus._on("find", this.#onFind.bind(this));
441447
eventBus._on("findbarclose", this.#onFindBarClose.bind(this));
448+
eventBus._on("pagesedited", this.#onPagesEdited.bind(this));
442449
}
443450

444451
get highlightMatches() {
@@ -794,12 +801,13 @@ class PDFFindController {
794801
if (query.length === 0) {
795802
return; // Do nothing: the matches should be wiped out already.
796803
}
797-
const pageContent = this._pageContents[pageIndex];
804+
const pageId = this.getPageId(pageIndex);
805+
const pageContent = this._pageContents[pageId];
798806
const matcherResult = this.match(query, pageContent, pageIndex);
799807

800808
const matches = (this._pageMatches[pageIndex] = []);
801809
const matchesLength = (this._pageMatchesLength[pageIndex] = []);
802-
const diffs = this._pageDiffs[pageIndex];
810+
const diffs = this._pageDiffs[pageId];
803811

804812
matcherResult?.forEach(({ index, length }) => {
805813
const [matchPos, matchLen] = getOriginalIndex(diffs, index, length);
@@ -848,7 +856,7 @@ class PDFFindController {
848856
* page.
849857
*/
850858
match(query, pageContent, pageIndex) {
851-
const hasDiacritics = this._hasDiacritics[pageIndex];
859+
const hasDiacritics = this._hasDiacritics[this.getPageId(pageIndex)];
852860

853861
let isUnicode = false;
854862
if (typeof query === "string") {
@@ -949,6 +957,14 @@ class PDFFindController {
949957
}
950958
}
951959

960+
getPageNumber(idx) {
961+
return this.#pagesMapper.getPageNumber(idx + 1) - 1;
962+
}
963+
964+
getPageId(pageNumber) {
965+
return this.#pagesMapper.getPageId(pageNumber + 1) - 1;
966+
}
967+
952968
#updatePage(index) {
953969
if (this._scrollMatches && this._selected.pageIdx === index) {
954970
// If the page is selected, scroll the page into view, which triggers
@@ -960,13 +976,15 @@ class PDFFindController {
960976
this._eventBus.dispatch("updatetextlayermatches", {
961977
source: this,
962978
pageIndex: index,
979+
pageId: this.getPageId(index),
963980
});
964981
}
965982

966983
#updateAllPages() {
967984
this._eventBus.dispatch("updatetextlayermatches", {
968985
source: this,
969986
pageIndex: -1,
987+
pageId: -1,
970988
});
971989
}
972990

@@ -998,7 +1016,7 @@ class PDFFindController {
9981016
continue;
9991017
}
10001018
this._pendingFindMatches.add(i);
1001-
this._extractTextPromises[i].then(() => {
1019+
this._extractTextPromises[this.getPageId(i)].then(() => {
10021020
this._pendingFindMatches.delete(i);
10031021
this.#calculateMatch(i);
10041022
});
@@ -1126,6 +1144,14 @@ class PDFFindController {
11261144
}
11271145
}
11281146

1147+
#onPagesEdited() {
1148+
if (this._extractTextPromises.length === 0) {
1149+
return;
1150+
}
1151+
this.#onFindBarClose();
1152+
this._dirtyMatch = true;
1153+
}
1154+
11291155
#onFindBarClose(evt) {
11301156
const pdfDocument = this._pdfDocument;
11311157
// Since searching is asynchronous, ensure that the removal of highlighted

web/pdf_viewer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,10 @@ class PDFViewer {
304304
`The API version "${version}" does not match the Viewer version "${viewerVersion}".`
305305
);
306306
}
307+
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
308+
this.pagesMapper = PagesMapper.instance;
309+
}
310+
307311
this.container = options.container;
308312
this.viewer = options.viewer || options.container.firstElementChild;
309313
this.#viewerAlert = options.viewerAlert || null;

web/text_highlighter.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class TextHighlighter {
7777
this.eventBus._on(
7878
"updatetextlayermatches",
7979
evt => {
80-
if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) {
80+
if (evt.pageId === this.pageIdx || evt.pageId === -1) {
8181
this._updateMatches();
8282
}
8383
},
@@ -159,7 +159,8 @@ class TextHighlighter {
159159
const { findController, pageIdx } = this;
160160
const { textContentItemsStr, textDivs } = this;
161161

162-
const isSelectedPage = pageIdx === findController.selected.pageIdx;
162+
const isSelectedPage =
163+
findController.getPageNumber(pageIdx) === findController.selected.pageIdx;
163164
const selectedMatchIdx = findController.selected.matchIdx;
164165
const highlightAll = findController.state.highlightAll;
165166
let prevEnd = null;
@@ -273,7 +274,7 @@ class TextHighlighter {
273274
findController.scrollMatchIntoView({
274275
element: textDivs[begin.divIdx],
275276
selectedLeft,
276-
pageIndex: pageIdx,
277+
pageIndex: findController.getPageNumber(pageIdx),
277278
matchIndex: selectedMatchIdx,
278279
});
279280
}
@@ -308,8 +309,10 @@ class TextHighlighter {
308309
}
309310
// Convert the matches on the `findController` into the match format
310311
// used for the textLayer.
311-
const pageMatches = findController.pageMatches[pageIdx] || null;
312-
const pageMatchesLength = findController.pageMatchesLength[pageIdx] || null;
312+
const pageNumber = findController.getPageNumber(pageIdx);
313+
const pageMatches = findController.pageMatches[pageNumber] || null;
314+
const pageMatchesLength =
315+
findController.pageMatchesLength[pageNumber] || null;
313316

314317
this.matches = this._convertMatches(pageMatches, pageMatchesLength);
315318
this._renderMatches(this.matches);

0 commit comments

Comments
 (0)