mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-07-07 15:45:48 +02:00
Merge pull request #20577 from calixteman/update_search
The 'find in page' feature must correctly work after the pages have been reorganized (bug 2010814)
This commit is contained in:
commit
f04deeeddf
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
awaitPromise,
|
awaitPromise,
|
||||||
|
clearInput,
|
||||||
closePages,
|
closePages,
|
||||||
createPromise,
|
createPromise,
|
||||||
dragAndDrop,
|
dragAndDrop,
|
||||||
@ -56,6 +57,46 @@ function waitForPagesEdited(page) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSearchResults(page) {
|
||||||
|
return page.evaluate(() => {
|
||||||
|
const pages = document.querySelectorAll(".page");
|
||||||
|
const results = [];
|
||||||
|
for (let i = 0; i < pages.length; i++) {
|
||||||
|
const domPage = pages[i];
|
||||||
|
const pageNumber = parseInt(domPage.getAttribute("data-page-number"), 10);
|
||||||
|
const highlights = domPage.querySelectorAll("span.highlight");
|
||||||
|
if (highlights.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
results.push([
|
||||||
|
i + 1,
|
||||||
|
pageNumber,
|
||||||
|
Array.from(highlights).map(span => span.textContent),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function movePages(page, selectedPages, atIndex) {
|
||||||
|
return page.evaluate(
|
||||||
|
(selected, index) => {
|
||||||
|
const viewer = window.PDFViewerApplication.pdfViewer;
|
||||||
|
const pagesToMove = Array.from(selected).sort((a, b) => a - b);
|
||||||
|
viewer.pagesMapper.pagesNumber =
|
||||||
|
document.querySelectorAll(".page").length;
|
||||||
|
viewer.pagesMapper.movePages(new Set(pagesToMove), pagesToMove, index);
|
||||||
|
window.PDFViewerApplication.eventBus.dispatch("pagesedited", {
|
||||||
|
pagesMapper: viewer.pagesMapper,
|
||||||
|
index,
|
||||||
|
pagesToMove,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
selectedPages,
|
||||||
|
atIndex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
describe("Reorganize Pages View", () => {
|
describe("Reorganize Pages View", () => {
|
||||||
describe("Drag & Drop", () => {
|
describe("Drag & Drop", () => {
|
||||||
let pages;
|
let pages;
|
||||||
@ -262,4 +303,116 @@ describe("Reorganize Pages View", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Search in pdf", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
pages = await loadAndWait(
|
||||||
|
"page_with_number.pdf",
|
||||||
|
"#viewsManagerToggleButton",
|
||||||
|
"1",
|
||||||
|
null,
|
||||||
|
{ enableSplitMerge: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should check if the search is working after moving pages", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await page.click("#viewFindButton");
|
||||||
|
await page.waitForSelector(":has(> #findHighlightAll)", {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
await page.click(":has(> #findHighlightAll)");
|
||||||
|
|
||||||
|
await page.waitForSelector("#findInput", { visible: true });
|
||||||
|
await page.type("#findInput", "1");
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
|
||||||
|
await page.waitForFunction(
|
||||||
|
() => document.querySelectorAll("span.highlight").length === 10
|
||||||
|
);
|
||||||
|
|
||||||
|
let results = await getSearchResults(page);
|
||||||
|
expect(results)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toEqual([
|
||||||
|
// Page number, Id, [matches]
|
||||||
|
[1, 1, ["1"]],
|
||||||
|
[10, 10, ["1"]],
|
||||||
|
[11, 11, ["1", "1"]],
|
||||||
|
[12, 12, ["1"]],
|
||||||
|
[13, 13, ["1"]],
|
||||||
|
[14, 14, ["1"]],
|
||||||
|
[15, 15, ["1"]],
|
||||||
|
[16, 16, ["1"]],
|
||||||
|
[17, 17, ["1"]],
|
||||||
|
]);
|
||||||
|
|
||||||
|
await movePages(page, [11, 2], 3);
|
||||||
|
await page.waitForFunction(
|
||||||
|
() => document.querySelectorAll("span.highlight").length === 0
|
||||||
|
);
|
||||||
|
|
||||||
|
await clearInput(page, "#findInput", true);
|
||||||
|
await page.type("#findInput", "1");
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
|
||||||
|
await page.waitForFunction(
|
||||||
|
() => document.querySelectorAll("span.highlight").length === 10
|
||||||
|
);
|
||||||
|
|
||||||
|
results = await getSearchResults(page);
|
||||||
|
expect(results)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toEqual([
|
||||||
|
// Page number, Id, [matches]
|
||||||
|
[1, 1, ["1"]],
|
||||||
|
[4, 11, ["1", "1"]],
|
||||||
|
[11, 10, ["1"]],
|
||||||
|
[12, 12, ["1"]],
|
||||||
|
[13, 13, ["1"]],
|
||||||
|
[14, 14, ["1"]],
|
||||||
|
[15, 15, ["1"]],
|
||||||
|
[16, 16, ["1"]],
|
||||||
|
[17, 17, ["1"]],
|
||||||
|
]);
|
||||||
|
|
||||||
|
await movePages(page, [13], 0);
|
||||||
|
await page.waitForFunction(
|
||||||
|
() => document.querySelectorAll("span.highlight").length === 0
|
||||||
|
);
|
||||||
|
|
||||||
|
await clearInput(page, "#findInput", true);
|
||||||
|
await page.type("#findInput", "1");
|
||||||
|
await page.keyboard.press("Enter");
|
||||||
|
|
||||||
|
await page.waitForFunction(
|
||||||
|
() => document.querySelectorAll("span.highlight").length === 10
|
||||||
|
);
|
||||||
|
|
||||||
|
results = await getSearchResults(page);
|
||||||
|
expect(results)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toEqual([
|
||||||
|
// Page number, Id, [matches]
|
||||||
|
[1, 13, ["1"]],
|
||||||
|
[2, 1, ["1"]],
|
||||||
|
[5, 11, ["1", "1"]],
|
||||||
|
[12, 10, ["1"]],
|
||||||
|
[13, 12, ["1"]],
|
||||||
|
[14, 14, ["1"]],
|
||||||
|
[15, 15, ["1"]],
|
||||||
|
[16, 16, ["1"]],
|
||||||
|
[17, 17, ["1"]],
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -17,7 +17,11 @@
|
|||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||||
|
|
||||||
import { binarySearchFirstItem, scrollIntoView } from "./ui_utils.js";
|
import {
|
||||||
|
binarySearchFirstItem,
|
||||||
|
PagesMapper,
|
||||||
|
scrollIntoView,
|
||||||
|
} from "./ui_utils.js";
|
||||||
import { getCharacterType, getNormalizeWithNFKC } from "./pdf_find_utils.js";
|
import { getCharacterType, getNormalizeWithNFKC } from "./pdf_find_utils.js";
|
||||||
|
|
||||||
const FindState = {
|
const FindState = {
|
||||||
@ -422,6 +426,8 @@ class PDFFindController {
|
|||||||
|
|
||||||
#visitedPagesCount = 0;
|
#visitedPagesCount = 0;
|
||||||
|
|
||||||
|
#pagesMapper = PagesMapper.instance;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {PDFFindControllerOptions} options
|
* @param {PDFFindControllerOptions} options
|
||||||
*/
|
*/
|
||||||
@ -439,6 +445,7 @@ class PDFFindController {
|
|||||||
this.#reset();
|
this.#reset();
|
||||||
eventBus._on("find", this.#onFind.bind(this));
|
eventBus._on("find", this.#onFind.bind(this));
|
||||||
eventBus._on("findbarclose", this.#onFindBarClose.bind(this));
|
eventBus._on("findbarclose", this.#onFindBarClose.bind(this));
|
||||||
|
eventBus._on("pagesedited", this.#onPagesEdited.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
get highlightMatches() {
|
get highlightMatches() {
|
||||||
@ -794,12 +801,13 @@ class PDFFindController {
|
|||||||
if (query.length === 0) {
|
if (query.length === 0) {
|
||||||
return; // Do nothing: the matches should be wiped out already.
|
return; // Do nothing: the matches should be wiped out already.
|
||||||
}
|
}
|
||||||
const pageContent = this._pageContents[pageIndex];
|
const pageId = this.getPageId(pageIndex);
|
||||||
|
const pageContent = this._pageContents[pageId];
|
||||||
const matcherResult = this.match(query, pageContent, pageIndex);
|
const matcherResult = this.match(query, pageContent, pageIndex);
|
||||||
|
|
||||||
const matches = (this._pageMatches[pageIndex] = []);
|
const matches = (this._pageMatches[pageIndex] = []);
|
||||||
const matchesLength = (this._pageMatchesLength[pageIndex] = []);
|
const matchesLength = (this._pageMatchesLength[pageIndex] = []);
|
||||||
const diffs = this._pageDiffs[pageIndex];
|
const diffs = this._pageDiffs[pageId];
|
||||||
|
|
||||||
matcherResult?.forEach(({ index, length }) => {
|
matcherResult?.forEach(({ index, length }) => {
|
||||||
const [matchPos, matchLen] = getOriginalIndex(diffs, index, length);
|
const [matchPos, matchLen] = getOriginalIndex(diffs, index, length);
|
||||||
@ -848,7 +856,7 @@ class PDFFindController {
|
|||||||
* page.
|
* page.
|
||||||
*/
|
*/
|
||||||
match(query, pageContent, pageIndex) {
|
match(query, pageContent, pageIndex) {
|
||||||
const hasDiacritics = this._hasDiacritics[pageIndex];
|
const hasDiacritics = this._hasDiacritics[this.getPageId(pageIndex)];
|
||||||
|
|
||||||
let isUnicode = false;
|
let isUnicode = false;
|
||||||
if (typeof query === "string") {
|
if (typeof query === "string") {
|
||||||
@ -949,6 +957,14 @@ class PDFFindController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPageNumber(idx) {
|
||||||
|
return this.#pagesMapper.getPageNumber(idx + 1) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPageId(pageNumber) {
|
||||||
|
return this.#pagesMapper.getPageId(pageNumber + 1) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
#updatePage(index) {
|
#updatePage(index) {
|
||||||
if (this._scrollMatches && this._selected.pageIdx === index) {
|
if (this._scrollMatches && this._selected.pageIdx === index) {
|
||||||
// If the page is selected, scroll the page into view, which triggers
|
// If the page is selected, scroll the page into view, which triggers
|
||||||
@ -960,6 +976,7 @@ class PDFFindController {
|
|||||||
this._eventBus.dispatch("updatetextlayermatches", {
|
this._eventBus.dispatch("updatetextlayermatches", {
|
||||||
source: this,
|
source: this,
|
||||||
pageIndex: index,
|
pageIndex: index,
|
||||||
|
pageId: this.getPageId(index),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -967,6 +984,7 @@ class PDFFindController {
|
|||||||
this._eventBus.dispatch("updatetextlayermatches", {
|
this._eventBus.dispatch("updatetextlayermatches", {
|
||||||
source: this,
|
source: this,
|
||||||
pageIndex: -1,
|
pageIndex: -1,
|
||||||
|
pageId: -1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -998,7 +1016,7 @@ class PDFFindController {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this._pendingFindMatches.add(i);
|
this._pendingFindMatches.add(i);
|
||||||
this._extractTextPromises[i].then(() => {
|
this._extractTextPromises[this.getPageId(i)].then(() => {
|
||||||
this._pendingFindMatches.delete(i);
|
this._pendingFindMatches.delete(i);
|
||||||
this.#calculateMatch(i);
|
this.#calculateMatch(i);
|
||||||
});
|
});
|
||||||
@ -1126,6 +1144,14 @@ class PDFFindController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#onPagesEdited() {
|
||||||
|
if (this._extractTextPromises.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#onFindBarClose();
|
||||||
|
this._dirtyMatch = true;
|
||||||
|
}
|
||||||
|
|
||||||
#onFindBarClose(evt) {
|
#onFindBarClose(evt) {
|
||||||
const pdfDocument = this._pdfDocument;
|
const pdfDocument = this._pdfDocument;
|
||||||
// Since searching is asynchronous, ensure that the removal of highlighted
|
// Since searching is asynchronous, ensure that the removal of highlighted
|
||||||
|
|||||||
@ -304,6 +304,10 @@ class PDFViewer {
|
|||||||
`The API version "${version}" does not match the Viewer version "${viewerVersion}".`
|
`The API version "${version}" does not match the Viewer version "${viewerVersion}".`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||||||
|
this.pagesMapper = PagesMapper.instance;
|
||||||
|
}
|
||||||
|
|
||||||
this.container = options.container;
|
this.container = options.container;
|
||||||
this.viewer = options.viewer || options.container.firstElementChild;
|
this.viewer = options.viewer || options.container.firstElementChild;
|
||||||
this.#viewerAlert = options.viewerAlert || null;
|
this.#viewerAlert = options.viewerAlert || null;
|
||||||
|
|||||||
@ -77,7 +77,7 @@ class TextHighlighter {
|
|||||||
this.eventBus._on(
|
this.eventBus._on(
|
||||||
"updatetextlayermatches",
|
"updatetextlayermatches",
|
||||||
evt => {
|
evt => {
|
||||||
if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) {
|
if (evt.pageId === this.pageIdx || evt.pageId === -1) {
|
||||||
this._updateMatches();
|
this._updateMatches();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -159,7 +159,8 @@ class TextHighlighter {
|
|||||||
const { findController, pageIdx } = this;
|
const { findController, pageIdx } = this;
|
||||||
const { textContentItemsStr, textDivs } = this;
|
const { textContentItemsStr, textDivs } = this;
|
||||||
|
|
||||||
const isSelectedPage = pageIdx === findController.selected.pageIdx;
|
const isSelectedPage =
|
||||||
|
findController.getPageNumber(pageIdx) === findController.selected.pageIdx;
|
||||||
const selectedMatchIdx = findController.selected.matchIdx;
|
const selectedMatchIdx = findController.selected.matchIdx;
|
||||||
const highlightAll = findController.state.highlightAll;
|
const highlightAll = findController.state.highlightAll;
|
||||||
let prevEnd = null;
|
let prevEnd = null;
|
||||||
@ -273,7 +274,7 @@ class TextHighlighter {
|
|||||||
findController.scrollMatchIntoView({
|
findController.scrollMatchIntoView({
|
||||||
element: textDivs[begin.divIdx],
|
element: textDivs[begin.divIdx],
|
||||||
selectedLeft,
|
selectedLeft,
|
||||||
pageIndex: pageIdx,
|
pageIndex: findController.getPageNumber(pageIdx),
|
||||||
matchIndex: selectedMatchIdx,
|
matchIndex: selectedMatchIdx,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -308,8 +309,10 @@ class TextHighlighter {
|
|||||||
}
|
}
|
||||||
// Convert the matches on the `findController` into the match format
|
// Convert the matches on the `findController` into the match format
|
||||||
// used for the textLayer.
|
// used for the textLayer.
|
||||||
const pageMatches = findController.pageMatches[pageIdx] || null;
|
const pageNumber = findController.getPageNumber(pageIdx);
|
||||||
const pageMatchesLength = findController.pageMatchesLength[pageIdx] || null;
|
const pageMatches = findController.pageMatches[pageNumber] || null;
|
||||||
|
const pageMatchesLength =
|
||||||
|
findController.pageMatchesLength[pageNumber] || null;
|
||||||
|
|
||||||
this.matches = this._convertMatches(pageMatches, pageMatchesLength);
|
this.matches = this._convertMatches(pageMatches, pageMatchesLength);
|
||||||
this._renderMatches(this.matches);
|
this._renderMatches(this.matches);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user