1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-29 15:47:57 +02:00
pdf.js/web/text_layer_builder.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

338 lines
10 KiB
JavaScript
Raw Normal View History

2013-06-18 09:05:55 -07:00
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */
// eslint-disable-next-line max-len
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
/** @typedef {import("./text_highlighter").TextHighlighter} TextHighlighter */
// eslint-disable-next-line max-len
/** @typedef {import("./text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
Fix Viewer API definitions and include in CI The Viewer API definitions do not compile because of missing imports and anonymous objects are typed as `Object`. These issues were not caught during CI because the test project was not compiling anything from the Viewer API. As an example of the first problem: ``` /** * @implements MyInterface */ export class MyClass { ... } ``` will generate a broken definition that doesn’t import MyInterface: ``` /** * @implements MyInterface */ export class MyClass implements MyInterface { ... } ``` This can be fixed by adding a typedef jsdoc to specify the import: ``` /** @typedef {import("./otherFile").MyInterface} MyInterface */ ``` See https://github.com/jsdoc/jsdoc/issues/1537 and https://github.com/microsoft/TypeScript/issues/22160 for more details. As an example of the second problem: ``` /** * Gets the size of the specified page, converted from PDF units to inches. * @param {Object} An Object containing the properties: {Array} `view`, * {number} `userUnit`, and {number} `rotate`. */ function getPageSizeInches({ view, userUnit, rotate }) { ... } ``` generates the broken definition: ``` function getPageSizeInches({ view, userUnit, rotate }: Object) { ... } ``` The jsdoc should specify the type of each nested property: ``` /** * Gets the size of the specified page, converted from PDF units to inches. * @param {Object} options An object containing the properties: {Array} `view`, * {number} `userUnit`, and {number} `rotate`. * @param {number[]} options.view * @param {number} options.userUnit * @param {number} options.rotate */ ```
2021-08-25 18:44:06 -04:00
import { normalizeUnicode, stopEvent, TextLayer } from "pdfjs-lib";
import { removeNullCharacters } from "./ui_utils.js";
/**
* @typedef {Object} TextLayerBuilderOptions
* @property {PDFPageProxy} pdfPage
* @property {TextHighlighter} [highlighter] - Optional object that will handle
* highlighting text from the find controller.
* @property {TextAccessibilityManager} [accessibilityManager]
* @property {boolean} [enablePermissions]
* @property {function} [onAppend]
*/
/**
* @typedef {Object} TextLayerBuilderRenderOptions
* @property {PageViewport} viewport
* @property {Object} [textContentParams]
*/
2013-06-18 09:05:55 -07:00
/**
* The text layer builder provides text selection functionality for the PDF.
* It does this by creating overlay divs over the PDF's text. These divs
* contain text that matches the PDF text they are overlaying.
2013-06-18 09:05:55 -07:00
*/
class TextLayerBuilder {
#enablePermissions = false;
#onAppend = null;
#renderingDone = false;
#textLayer = null;
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
static #textLayers = new Map();
static #selectionChangeAbortController = null;
/**
* @param {TextLayerBuilderOptions} options
*/
constructor({
pdfPage,
highlighter = null,
accessibilityManager = null,
enablePermissions = false,
onAppend = null,
}) {
this.pdfPage = pdfPage;
this.highlighter = highlighter;
this.accessibilityManager = accessibilityManager;
this.#enablePermissions = enablePermissions === true;
this.#onAppend = onAppend;
this.div = document.createElement("div");
this.div.tabIndex = 0;
this.div.className = "textLayer";
}
2013-06-18 09:05:55 -07:00
/**
* Renders the text layer.
* @param {TextLayerBuilderRenderOptions} options
* @returns {Promise<void>}
*/
async render({ viewport, textContentParams = null }) {
if (this.#renderingDone && this.#textLayer) {
this.#textLayer.update({
viewport,
onBefore: this.hide.bind(this),
});
this.show();
return;
}
2013-06-18 09:05:55 -07:00
this.cancel();
this.#textLayer = new TextLayer({
textContentSource: this.pdfPage.streamTextContent(
textContentParams || {
includeMarkedContent: true,
disableNormalization: true,
}
),
container: this.div,
viewport,
});
const { textDivs, textContentItemsStr } = this.#textLayer;
this.highlighter?.setTextMapping(textDivs, textContentItemsStr);
this.accessibilityManager?.setTextMapping(textDivs);
await this.#textLayer.render();
this.#renderingDone = true;
const endOfContent = document.createElement("div");
endOfContent.className = "endOfContent";
this.div.append(endOfContent);
this.#bindMouse(endOfContent);
// Ensure that the textLayer is appended to the DOM *before* handling
// e.g. a pending search operation.
this.#onAppend?.(this.div);
this.highlighter?.enable();
this.accessibilityManager?.enable();
}
hide() {
if (!this.div.hidden && this.#renderingDone) {
// We turn off the highlighter in order to avoid to scroll into view an
// element of the text layer which could be hidden.
this.highlighter?.disable();
this.div.hidden = true;
}
}
show() {
if (this.div.hidden && this.#renderingDone) {
this.div.hidden = false;
this.highlighter?.enable();
}
}
/**
* Cancel rendering of the text layer.
*/
cancel() {
this.#textLayer?.cancel();
this.#textLayer = null;
this.highlighter?.disable();
this.accessibilityManager?.disable();
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
TextLayerBuilder.#removeGlobalSelectionListener(this.div);
}
/**
* Improves text selection by adding an additional div where the mouse was
* clicked. This reduces flickering of the content if the mouse is slowly
* dragged up or down.
*/
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
#bindMouse(end) {
const { div } = this;
div.addEventListener("mousedown", () => {
div.classList.add("selecting");
});
div.addEventListener("copy", event => {
if (!this.#enablePermissions) {
const selection = document.getSelection();
event.clipboardData.setData(
"text/plain",
removeNullCharacters(normalizeUnicode(selection.toString()))
);
}
stopEvent(event);
});
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
TextLayerBuilder.#textLayers.set(div, end);
TextLayerBuilder.#enableGlobalSelectionListener();
}
static #removeGlobalSelectionListener(textLayerDiv) {
this.#textLayers.delete(textLayerDiv);
if (this.#textLayers.size === 0) {
this.#selectionChangeAbortController?.abort();
this.#selectionChangeAbortController = null;
}
}
static #enableGlobalSelectionListener() {
if (this.#selectionChangeAbortController) {
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
// document-level event listeners already installed
return;
}
this.#selectionChangeAbortController = new AbortController();
const { signal } = this.#selectionChangeAbortController;
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
const reset = (end, textLayer) => {
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
textLayer.append(end);
end.style.width = "";
end.style.height = "";
}
textLayer.classList.remove("selecting");
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
};
let isPointerDown = false;
document.addEventListener(
"pointerdown",
() => {
isPointerDown = true;
},
{ signal }
);
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
document.addEventListener(
"pointerup",
() => {
isPointerDown = false;
this.#textLayers.forEach(reset);
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
},
{ signal }
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
);
window.addEventListener(
"blur",
() => {
isPointerDown = false;
this.#textLayers.forEach(reset);
},
{ signal }
);
document.addEventListener(
"keyup",
() => {
if (!isPointerDown) {
this.#textLayers.forEach(reset);
}
},
{ signal }
);
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
// eslint-disable-next-line no-var
var isFirefox, prevRange;
}
document.addEventListener(
"selectionchange",
() => {
const selection = document.getSelection();
if (selection.rangeCount === 0) {
this.#textLayers.forEach(reset);
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
return;
}
// Even though the spec says that .rangeCount should be 0 or 1, Firefox
// creates multiple ranges when selecting across multiple pages.
// Make sure to collect all the .textLayer elements where the selection
// is happening.
const activeTextLayers = new Set();
for (let i = 0; i < selection.rangeCount; i++) {
const range = selection.getRangeAt(i);
for (const textLayerDiv of this.#textLayers.keys()) {
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
if (
!activeTextLayers.has(textLayerDiv) &&
range.intersectsNode(textLayerDiv)
) {
activeTextLayers.add(textLayerDiv);
}
}
}
for (const [textLayerDiv, endDiv] of this.#textLayers) {
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
if (activeTextLayers.has(textLayerDiv)) {
textLayerDiv.classList.add("selecting");
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
} else {
reset(endDiv, textLayerDiv);
}
}
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
return;
}
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) {
isFirefox ??=
getComputedStyle(
this.#textLayers.values().next().value
).getPropertyValue("-moz-user-select") === "none";
if (isFirefox) {
return;
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
}
}
// In non-Firefox browsers, when hovering over an empty space (thus,
// on .endOfContent), the selection will expand to cover all the
// text between the current selection and .endOfContent. By moving
// .endOfContent to right after (or before, depending on which side
// of the selection the user is moving), we limit the selection jump
// to at most cover the enteirety of the <span> where the selection
// is being modified.
const range = selection.getRangeAt(0);
const modifyStart =
prevRange &&
(range.compareBoundaryPoints(Range.END_TO_END, prevRange) === 0 ||
range.compareBoundaryPoints(Range.START_TO_END, prevRange) === 0);
let anchor = modifyStart ? range.startContainer : range.endContainer;
if (anchor.nodeType === Node.TEXT_NODE) {
anchor = anchor.parentNode;
}
[chrome] Fix text selection with `.markedContent` The current text layer approach based on absolutely positioned `<span>` elements by default causes flickering with text selection, and we have browser-specific workarounds to solve that. In Chrome, the workaround involves moving the `.endOfContent` element to right after the last element that contains some selected content. This works well in simple PDFs, but breaks when we have `span.markedContent` elements. Given a text layer structure like the following, rendered as four consecutive lines: ```html <span class="markedContent"> <br> <span>development enter the construction phase (estimated at around</span> </span> <span class="markedContent"> <br> <span>300 MEUR).</span> </span> <span class="markedContent"> <br> <span>Kreate's EBITA increased to 2.8 MEUR (Q4'23: 2.7 MEUR) and the</span> </span> <span class="markedContent"> <br> <span>margin rose to 3.7% (Q4'23: 3.4%). However, profitability was</span> </span> ``` when starting to select from inside the first line and dragging down to the empty space after the second line, Chrome will anchor the selection at the beginning of either the `<br>` or the `<span>` inside the last `.markedContent`, depending on whether the selection is in "per-character mode" (i.e. click and drag) or "per-word mode" (i.e. double click and drag). This causes us to insert the `.endOfContent` element in the wrong place (one element too far), which causes one more line to be selected, which triggers another `"selecctionchange"` event, which causes us to move `.endOfContent` again, and so on, looping until when the whole page is selected. This commit fixes the issue by making sure that when the end of the selection range points to the _begining_ of an element, we walk back the dom finding the first non-empty element, and attatch `.endOfContent` to the end of that.
2025-04-07 15:59:45 +02:00
if (!modifyStart && range.endOffset === 0) {
do {
while (!anchor.previousSibling) {
anchor = anchor.parentNode;
}
anchor = anchor.previousSibling;
} while (!anchor.childNodes.length);
}
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
const parentTextLayer = anchor.parentElement?.closest(".textLayer");
const endDiv = this.#textLayers.get(parentTextLayer);
if (endDiv) {
endDiv.style.width = parentTextLayer.style.width;
endDiv.style.height = parentTextLayer.style.height;
anchor.parentElement.insertBefore(
endDiv,
modifyStart ? anchor : anchor.nextSibling
);
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
}
prevRange = range.cloneRange();
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
},
{ signal }
Fix flickering on text selection When seleciting on a touch screen device, whenever the finger moves to a blank area (so over `div.textLayer` directly rather than on a `<span>`), the selection jumps to include all the text between the beginning of the .textLayer and the selection side that is not being moved. The existing selection flickering fix when using the mouse cannot be trivially re-used on mobile, because when modifying a selection on a touchscreen device Firefox will not emit any pointer event (and Chrome will emit them inconsistently). Instead, we have to listen to the 'selectionchange' event. The fix is different in Firefox and Chrome: - on Firefox, we have to make sure that, when modifying the selection, hovering on blank areas will hover on the .endOfContent element rather than on the .textLayer element. This is done by adjusting the z-indexes so that .endOfContent is above .textLayer. - on Chrome, hovering on blank areas needs to trigger hovering on an element that is either immediately after (or immediately before, depending on which side of the selection the user is moving) the currently selected text. This is done by moving the .endOfContent element around between the correct `<span>`s in the text layer. The new anti-flickering code is also used when selecting using a mouse: the improvement in Firefox is only observable on multi-page selection, while in Chrome it also affects selection within a single page. After this commit, the `z-index`es inside .textLayer are as follows: - .endOfContent has `z-index: 0` - everything else has `z-index: 1` - except for .markedContent, which have `z-index: 0` and their contents have `z-index: 1`. `.textLayer` has an explicit `z-index: 0` to introduce a new stacking context, so that its contents are not drawn on top of `.annotationLayer`.
2024-04-11 12:50:57 +02:00
);
}
}
export { TextLayerBuilder };