mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-06-22 16:05:56 +02:00
Merge pull request #20951 from calixteman/bug2021392
Correctly scroll the search result in the viewport with rotated pdfs (bug 2021392)
This commit is contained in:
commit
2643125a12
@ -177,4 +177,56 @@ describe("find bar", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Check that the search results are correctly visible in rotated PDFs (bug 2021392)", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
pages = await loadAndWait(
|
||||||
|
"hello_world_rotated.pdf",
|
||||||
|
".textLayer",
|
||||||
|
"page-fit"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must scroll each match into the viewport when navigating search results", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await page.click("#viewFindButton");
|
||||||
|
await page.waitForSelector("#findInput", { visible: true });
|
||||||
|
await page.type("#findInput", "hello");
|
||||||
|
await page.waitForSelector("#findInput[data-status='']");
|
||||||
|
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
await page.click("#findNextButton");
|
||||||
|
await page.waitForSelector("#findInput[data-status='']");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify we are on the expected match number.
|
||||||
|
const resultElement =
|
||||||
|
await page.waitForSelector("#findResultsCount");
|
||||||
|
const resultText = await resultElement.evaluate(
|
||||||
|
el => el.textContent
|
||||||
|
);
|
||||||
|
expect(resultText)
|
||||||
|
.withContext(`In ${browserName}, match ${i + 1}`)
|
||||||
|
.toEqual(`${FSI}${i + 1}${PDI} of ${FSI}5${PDI} matches`);
|
||||||
|
|
||||||
|
// The selected highlight must be visible in the viewport.
|
||||||
|
const selected = await page.waitForSelector(
|
||||||
|
".textLayer .highlight.selected"
|
||||||
|
);
|
||||||
|
expect(await selected.isIntersectingViewport())
|
||||||
|
.withContext(`In ${browserName}, match ${i + 1}`)
|
||||||
|
.toBeTrue();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -891,3 +891,4 @@
|
|||||||
!extractPages_null_in_array.pdf
|
!extractPages_null_in_array.pdf
|
||||||
!issue20930.pdf
|
!issue20930.pdf
|
||||||
!text_rise_eol_bug.pdf
|
!text_rise_eol_bug.pdf
|
||||||
|
!hello_world_rotated.pdf
|
||||||
|
|||||||
74
test/pdfs/hello_world_rotated.pdf
Normal file
74
test/pdfs/hello_world_rotated.pdf
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
%PDF-1.4
|
||||||
|
%âãÏÓ
|
||||||
|
8 0 obj
|
||||||
|
<< /Length 43 >>
|
||||||
|
stream
|
||||||
|
BT /F1 36 Tf 210 370 Td (Hello world) Tj ET
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
9 0 obj
|
||||||
|
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >>
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<< /Type /Page /Parent 2 0 R
|
||||||
|
/MediaBox [0 0 612 792]
|
||||||
|
/Rotate 90
|
||||||
|
/Contents 8 0 R
|
||||||
|
/Resources << /Font << /F1 9 0 R >> >>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
4 0 obj
|
||||||
|
<< /Type /Page /Parent 2 0 R
|
||||||
|
/MediaBox [0 0 612 792]
|
||||||
|
/Rotate 90
|
||||||
|
/Contents 8 0 R
|
||||||
|
/Resources << /Font << /F1 9 0 R >> >>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
<< /Type /Page /Parent 2 0 R
|
||||||
|
/MediaBox [0 0 612 792]
|
||||||
|
/Rotate 90
|
||||||
|
/Contents 8 0 R
|
||||||
|
/Resources << /Font << /F1 9 0 R >> >>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<< /Type /Page /Parent 2 0 R
|
||||||
|
/MediaBox [0 0 612 792]
|
||||||
|
/Rotate 90
|
||||||
|
/Contents 8 0 R
|
||||||
|
/Resources << /Font << /F1 9 0 R >> >>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<< /Type /Page /Parent 2 0 R
|
||||||
|
/MediaBox [0 0 612 792]
|
||||||
|
/Rotate 90
|
||||||
|
/Contents 8 0 R
|
||||||
|
/Resources << /Font << /F1 9 0 R >> >>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<< /Type /Pages /Kids [3 0 R 4 0 R 5 0 R 6 0 R 7 0 R] /Count 5 >>
|
||||||
|
endobj
|
||||||
|
1 0 obj
|
||||||
|
<< /Type /Catalog /Pages 2 0 R >>
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 10
|
||||||
|
0000000000 65535 f
|
||||||
|
0000001009 00000 n
|
||||||
|
0000000928 00000 n
|
||||||
|
0000000183 00000 n
|
||||||
|
0000000332 00000 n
|
||||||
|
0000000481 00000 n
|
||||||
|
0000000630 00000 n
|
||||||
|
0000000779 00000 n
|
||||||
|
0000000015 00000 n
|
||||||
|
0000000108 00000 n
|
||||||
|
trailer
|
||||||
|
<< /Size 10 /Root 1 0 R >>
|
||||||
|
startxref
|
||||||
|
1058
|
||||||
|
%%EOF
|
||||||
@ -17,8 +17,8 @@
|
|||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./pdf_link_service.js").PDFLinkService} PDFLinkService */
|
/** @typedef {import("./pdf_link_service.js").PDFLinkService} PDFLinkService */
|
||||||
|
|
||||||
import { binarySearchFirstItem, scrollIntoView } from "./ui_utils.js";
|
|
||||||
import { getCharacterType, getNormalizeWithNFKC } from "./pdf_find_utils.js";
|
import { getCharacterType, getNormalizeWithNFKC } from "./pdf_find_utils.js";
|
||||||
|
import { binarySearchFirstItem } from "./ui_utils.js";
|
||||||
|
|
||||||
const FindState = {
|
const FindState = {
|
||||||
FOUND: 0,
|
FOUND: 0,
|
||||||
@ -28,7 +28,6 @@ const FindState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const FIND_TIMEOUT = 250; // ms
|
const FIND_TIMEOUT = 250; // ms
|
||||||
const MATCH_SCROLL_OFFSET_TOP = -50; // px
|
|
||||||
|
|
||||||
const CHARACTERS_TO_NORMALIZE = {
|
const CHARACTERS_TO_NORMALIZE = {
|
||||||
"\u2010": "-", // Hyphen
|
"\u2010": "-", // Hyphen
|
||||||
@ -553,7 +552,6 @@ class PDFFindController {
|
|||||||
/**
|
/**
|
||||||
* @typedef {Object} PDFFindControllerScrollMatchIntoViewParams
|
* @typedef {Object} PDFFindControllerScrollMatchIntoViewParams
|
||||||
* @property {HTMLElement} element
|
* @property {HTMLElement} element
|
||||||
* @property {number} selectedLeft
|
|
||||||
* @property {number} pageIndex
|
* @property {number} pageIndex
|
||||||
* @property {number} matchIndex
|
* @property {number} matchIndex
|
||||||
*/
|
*/
|
||||||
@ -562,12 +560,7 @@ class PDFFindController {
|
|||||||
* Scroll the current match into view.
|
* Scroll the current match into view.
|
||||||
* @param {PDFFindControllerScrollMatchIntoViewParams}
|
* @param {PDFFindControllerScrollMatchIntoViewParams}
|
||||||
*/
|
*/
|
||||||
scrollMatchIntoView({
|
scrollMatchIntoView({ element = null, pageIndex = -1, matchIndex = -1 }) {
|
||||||
element = null,
|
|
||||||
selectedLeft = 0,
|
|
||||||
pageIndex = -1,
|
|
||||||
matchIndex = -1,
|
|
||||||
}) {
|
|
||||||
if (!this._scrollMatches || !element) {
|
if (!this._scrollMatches || !element) {
|
||||||
return;
|
return;
|
||||||
} else if (matchIndex === -1 || matchIndex !== this._selected.matchIdx) {
|
} else if (matchIndex === -1 || matchIndex !== this._selected.matchIdx) {
|
||||||
@ -576,11 +569,7 @@ class PDFFindController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._scrollMatches = false; // Ensure that scrolling only happens once.
|
this._scrollMatches = false; // Ensure that scrolling only happens once.
|
||||||
const spot = {
|
element.scrollIntoView({ block: "start", inline: "center" });
|
||||||
top: MATCH_SCROLL_OFFSET_TOP,
|
|
||||||
left: selectedLeft,
|
|
||||||
};
|
|
||||||
scrollIntoView(element, spot, /* scrollMatches = */ true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#reset() {
|
#reset() {
|
||||||
|
|||||||
@ -195,11 +195,9 @@ class TextHighlighter {
|
|||||||
div.append(span);
|
div.append(span);
|
||||||
|
|
||||||
if (className.includes("selected")) {
|
if (className.includes("selected")) {
|
||||||
const { left } = span.getClientRects()[0];
|
return span;
|
||||||
const parentLeft = div.getBoundingClientRect().left;
|
|
||||||
return left - parentLeft;
|
|
||||||
}
|
}
|
||||||
return 0;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.append(node);
|
div.append(node);
|
||||||
@ -233,7 +231,7 @@ class TextHighlighter {
|
|||||||
const end = match.end;
|
const end = match.end;
|
||||||
const isSelected = isSelectedPage && i === selectedMatchIdx;
|
const isSelected = isSelectedPage && i === selectedMatchIdx;
|
||||||
const highlightSuffix = isSelected ? " selected" : "";
|
const highlightSuffix = isSelected ? " selected" : "";
|
||||||
let selectedLeft = 0;
|
let selectedSpan = null;
|
||||||
|
|
||||||
// Match inside new div.
|
// Match inside new div.
|
||||||
if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
|
if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
|
||||||
@ -248,14 +246,14 @@ class TextHighlighter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (begin.divIdx === end.divIdx) {
|
if (begin.divIdx === end.divIdx) {
|
||||||
selectedLeft = appendTextToDiv(
|
selectedSpan = appendTextToDiv(
|
||||||
begin.divIdx,
|
begin.divIdx,
|
||||||
begin.offset,
|
begin.offset,
|
||||||
end.offset,
|
end.offset,
|
||||||
"highlight" + highlightSuffix
|
"highlight" + highlightSuffix
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
selectedLeft = appendTextToDiv(
|
selectedSpan = appendTextToDiv(
|
||||||
begin.divIdx,
|
begin.divIdx,
|
||||||
begin.offset,
|
begin.offset,
|
||||||
infinity.offset,
|
infinity.offset,
|
||||||
@ -271,8 +269,7 @@ class TextHighlighter {
|
|||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
// Attempt to scroll the selected match into view.
|
// Attempt to scroll the selected match into view.
|
||||||
findController.scrollMatchIntoView({
|
findController.scrollMatchIntoView({
|
||||||
element: textDivs[begin.divIdx],
|
element: selectedSpan,
|
||||||
selectedLeft,
|
|
||||||
pageIndex: pageIdx,
|
pageIndex: pageIdx,
|
||||||
matchIndex: selectedMatchIdx,
|
matchIndex: selectedMatchIdx,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -110,6 +110,7 @@
|
|||||||
&.selected {
|
&.selected {
|
||||||
background-color: var(--highlight-selected-bg-color);
|
background-color: var(--highlight-selected-bg-color);
|
||||||
backdrop-filter: var(--highlight-selected-backdrop-filter);
|
backdrop-filter: var(--highlight-selected-backdrop-filter);
|
||||||
|
scroll-margin-top: 50px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,8 +13,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MathClamp } from "pdfjs-lib";
|
|
||||||
|
|
||||||
const DEFAULT_SCALE_VALUE = "auto";
|
const DEFAULT_SCALE_VALUE = "auto";
|
||||||
const DEFAULT_SCALE = 1.0;
|
const DEFAULT_SCALE = 1.0;
|
||||||
const DEFAULT_SCALE_DELTA = 1.1;
|
const DEFAULT_SCALE_DELTA = 1.1;
|
||||||
@ -78,11 +76,8 @@ const AutoPrintRegExp = /\bprint\s*\(/;
|
|||||||
* specifying the offset from the top left edge.
|
* specifying the offset from the top left edge.
|
||||||
* @param {number} [spot.left]
|
* @param {number} [spot.left]
|
||||||
* @param {number} [spot.top]
|
* @param {number} [spot.top]
|
||||||
* @param {boolean} [scrollMatches] - When scrolling search results into view,
|
|
||||||
* ignore elements that either: Contains marked content identifiers,
|
|
||||||
* or have the CSS-rule `overflow: hidden;` set. The default value is `false`.
|
|
||||||
*/
|
*/
|
||||||
function scrollIntoView(element, spot, scrollMatches = false) {
|
function scrollIntoView(element, spot) {
|
||||||
// Assuming offsetParent is available (it's not available when viewer is in
|
// Assuming offsetParent is available (it's not available when viewer is in
|
||||||
// hidden iframe or object). We have to scroll: if the offsetParent is not set
|
// hidden iframe or object). We have to scroll: if the offsetParent is not set
|
||||||
// producing the error. See also animationStarted.
|
// producing the error. See also animationStarted.
|
||||||
@ -94,11 +89,8 @@ function scrollIntoView(element, spot, scrollMatches = false) {
|
|||||||
let offsetY = element.offsetTop + element.clientTop;
|
let offsetY = element.offsetTop + element.clientTop;
|
||||||
let offsetX = element.offsetLeft + element.clientLeft;
|
let offsetX = element.offsetLeft + element.clientLeft;
|
||||||
while (
|
while (
|
||||||
(parent.clientHeight === parent.scrollHeight &&
|
parent.clientHeight === parent.scrollHeight &&
|
||||||
parent.clientWidth === parent.scrollWidth) ||
|
parent.clientWidth === parent.scrollWidth
|
||||||
(scrollMatches &&
|
|
||||||
(parent.classList.contains("markedContent") ||
|
|
||||||
getComputedStyle(parent).overflow === "hidden"))
|
|
||||||
) {
|
) {
|
||||||
offsetY += parent.offsetTop;
|
offsetY += parent.offsetTop;
|
||||||
offsetX += parent.offsetLeft;
|
offsetX += parent.offsetLeft;
|
||||||
@ -113,17 +105,7 @@ function scrollIntoView(element, spot, scrollMatches = false) {
|
|||||||
offsetY += spot.top;
|
offsetY += spot.top;
|
||||||
}
|
}
|
||||||
if (spot.left !== undefined) {
|
if (spot.left !== undefined) {
|
||||||
if (scrollMatches) {
|
offsetX += spot.left;
|
||||||
const elementWidth = element.getBoundingClientRect().width;
|
|
||||||
const padding = MathClamp(
|
|
||||||
(parent.clientWidth - elementWidth) / 2,
|
|
||||||
20,
|
|
||||||
400
|
|
||||||
);
|
|
||||||
offsetX += spot.left - padding;
|
|
||||||
} else {
|
|
||||||
offsetX += spot.left;
|
|
||||||
}
|
|
||||||
parent.scrollLeft = offsetX;
|
parent.scrollLeft = offsetX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user