mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
Merge pull request #15722 from calixteman/refactor_textlayer
[api-minor] Refactor the text layer code in order to avoid to recompute it on each draw
This commit is contained in:
commit
6e4968225e
13 changed files with 362 additions and 240 deletions
|
@ -501,6 +501,7 @@ const PDFViewerApplication = {
|
|||
imageResourcesPath: AppOptions.get("imageResourcesPath"),
|
||||
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
|
||||
useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
|
||||
isOffscreenCanvasSupported: AppOptions.get("isOffscreenCanvasSupported"),
|
||||
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
||||
enablePermissions: AppOptions.get("enablePermissions"),
|
||||
pageColors,
|
||||
|
|
|
@ -165,12 +165,9 @@ class DefaultStructTreeLayerFactory {
|
|||
class DefaultTextLayerFactory {
|
||||
/**
|
||||
* @typedef {Object} CreateTextLayerBuilderParameters
|
||||
* @property {HTMLDivElement} textLayerDiv
|
||||
* @property {number} pageIndex
|
||||
* @property {PageViewport} viewport
|
||||
* @property {EventBus} eventBus
|
||||
* @property {TextHighlighter} highlighter
|
||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||
* @property {boolean} [isOffscreenCanvasSupported]
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -178,20 +175,14 @@ class DefaultTextLayerFactory {
|
|||
* @returns {TextLayerBuilder}
|
||||
*/
|
||||
createTextLayerBuilder({
|
||||
textLayerDiv,
|
||||
pageIndex,
|
||||
viewport,
|
||||
eventBus,
|
||||
highlighter,
|
||||
accessibilityManager = null,
|
||||
isOffscreenCanvasSupported = true,
|
||||
}) {
|
||||
return new TextLayerBuilder({
|
||||
textLayerDiv,
|
||||
pageIndex,
|
||||
viewport,
|
||||
eventBus,
|
||||
highlighter,
|
||||
accessibilityManager,
|
||||
isOffscreenCanvasSupported,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
/** @typedef {import("./annotation_layer_builder").AnnotationLayerBuilder} AnnotationLayerBuilder */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./annotation_editor_layer_builder").AnnotationEditorLayerBuilder} AnnotationEditorLayerBuilder */
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./struct_tree_builder").StructTreeLayerBuilder} StructTreeLayerBuilder */
|
||||
/** @typedef {import("./text_highlighter").TextHighlighter} TextHighlighter */
|
||||
|
@ -168,12 +167,9 @@ class IRenderableView {
|
|||
class IPDFTextLayerFactory {
|
||||
/**
|
||||
* @typedef {Object} CreateTextLayerBuilderParameters
|
||||
* @property {HTMLDivElement} textLayerDiv
|
||||
* @property {number} pageIndex
|
||||
* @property {PageViewport} viewport
|
||||
* @property {EventBus} eventBus
|
||||
* @property {TextHighlighter} highlighter
|
||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||
* @property {boolean} [isOffscreenCanvasSupported]
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -181,12 +177,9 @@ class IPDFTextLayerFactory {
|
|||
* @returns {TextLayerBuilder}
|
||||
*/
|
||||
createTextLayerBuilder({
|
||||
textLayerDiv,
|
||||
pageIndex,
|
||||
viewport,
|
||||
eventBus,
|
||||
highlighter,
|
||||
accessibilityManager,
|
||||
isOffscreenCanvasSupported,
|
||||
}) {}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
||||
|
||||
import {
|
||||
AbortException,
|
||||
AnnotationMode,
|
||||
createPromiseCapability,
|
||||
PixelsPerInch,
|
||||
|
@ -82,6 +83,8 @@ import { TextAccessibilityManager } from "./text_accessibility.js";
|
|||
* for annotation icons. Include trailing slash.
|
||||
* @property {boolean} [useOnlyCssZoom] - Enables CSS only zooming. The default
|
||||
* value is `false`.
|
||||
* @property {boolean} [isOffscreenCanvasSupported] - Allows to use an
|
||||
* OffscreenCanvas if needed.
|
||||
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
||||
* total pixels, i.e. width * height. Use -1 for no limit. The default value
|
||||
* is 4096 * 4096 (16 mega-pixels).
|
||||
|
@ -128,6 +131,8 @@ class PDFPageView {
|
|||
options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
|
||||
this.imageResourcesPath = options.imageResourcesPath || "";
|
||||
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
||||
this.isOffscreenCanvasSupported =
|
||||
options.isOffscreenCanvasSupported ?? true;
|
||||
this.maxCanvasPixels = options.maxCanvasPixels || MAX_CANVAS_PIXELS;
|
||||
this.pageColors = options.pageColors || null;
|
||||
|
||||
|
@ -174,8 +179,8 @@ class PDFPageView {
|
|||
|
||||
const div = document.createElement("div");
|
||||
div.className = "page";
|
||||
div.style.width = Math.floor(this.viewport.width) + "px";
|
||||
div.style.height = Math.floor(this.viewport.height) + "px";
|
||||
div.style.width = Math.round(this.viewport.width) + "px";
|
||||
div.style.height = Math.round(this.viewport.height) + "px";
|
||||
div.setAttribute("data-page-number", this.id);
|
||||
div.setAttribute("role", "region");
|
||||
this.l10n.get("page_landmark", { page: this.id }).then(msg => {
|
||||
|
@ -284,6 +289,37 @@ class PDFPageView {
|
|||
}
|
||||
}
|
||||
|
||||
async #renderTextLayer() {
|
||||
const { pdfPage, textLayer, viewport } = this;
|
||||
if (!textLayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
let error = null;
|
||||
try {
|
||||
if (!textLayer.renderingDone) {
|
||||
const readableStream = pdfPage.streamTextContent({
|
||||
includeMarkedContent: true,
|
||||
});
|
||||
textLayer.setTextContentStream(readableStream);
|
||||
}
|
||||
await textLayer.render(viewport);
|
||||
} catch (ex) {
|
||||
if (ex instanceof AbortException) {
|
||||
return;
|
||||
}
|
||||
console.error(`#renderTextLayer: "${ex}".`);
|
||||
error = ex;
|
||||
}
|
||||
|
||||
this.eventBus.dispatch("textlayerrendered", {
|
||||
source: this,
|
||||
pageNumber: this.id,
|
||||
numTextDivs: textLayer.numTextDivs,
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
async _buildXfaTextContentItems(textDivs) {
|
||||
const text = await this.pdfPage.getTextContent();
|
||||
const items = [];
|
||||
|
@ -320,17 +356,19 @@ class PDFPageView {
|
|||
keepAnnotationLayer = false,
|
||||
keepAnnotationEditorLayer = false,
|
||||
keepXfaLayer = false,
|
||||
keepTextLayer = false,
|
||||
} = {}) {
|
||||
this.cancelRendering({
|
||||
keepAnnotationLayer,
|
||||
keepAnnotationEditorLayer,
|
||||
keepXfaLayer,
|
||||
keepTextLayer,
|
||||
});
|
||||
this.renderingState = RenderingStates.INITIAL;
|
||||
|
||||
const div = this.div;
|
||||
div.style.width = Math.floor(this.viewport.width) + "px";
|
||||
div.style.height = Math.floor(this.viewport.height) + "px";
|
||||
div.style.width = Math.round(this.viewport.width) + "px";
|
||||
div.style.height = Math.round(this.viewport.height) + "px";
|
||||
|
||||
const childNodes = div.childNodes,
|
||||
zoomLayerNode = (keepZoomLayer && this.zoomLayer) || null,
|
||||
|
@ -338,7 +376,8 @@ class PDFPageView {
|
|||
(keepAnnotationLayer && this.annotationLayer?.div) || null,
|
||||
annotationEditorLayerNode =
|
||||
(keepAnnotationEditorLayer && this.annotationEditorLayer?.div) || null,
|
||||
xfaLayerNode = (keepXfaLayer && this.xfaLayer?.div) || null;
|
||||
xfaLayerNode = (keepXfaLayer && this.xfaLayer?.div) || null,
|
||||
textLayerNode = (keepTextLayer && this.textLayer?.div) || null;
|
||||
for (let i = childNodes.length - 1; i >= 0; i--) {
|
||||
const node = childNodes[i];
|
||||
switch (node) {
|
||||
|
@ -346,6 +385,7 @@ class PDFPageView {
|
|||
case annotationLayerNode:
|
||||
case annotationEditorLayerNode:
|
||||
case xfaLayerNode:
|
||||
case textLayerNode:
|
||||
continue;
|
||||
}
|
||||
node.remove();
|
||||
|
@ -369,6 +409,10 @@ class PDFPageView {
|
|||
this.xfaLayer.hide();
|
||||
}
|
||||
|
||||
if (textLayerNode) {
|
||||
this.textLayer.hide();
|
||||
}
|
||||
|
||||
if (!zoomLayerNode) {
|
||||
if (this.canvas) {
|
||||
this.paintedViewportMap.delete(this.canvas);
|
||||
|
@ -450,6 +494,7 @@ class PDFPageView {
|
|||
redrawAnnotationLayer: true,
|
||||
redrawAnnotationEditorLayer: true,
|
||||
redrawXfaLayer: true,
|
||||
redrawTextLayer: true,
|
||||
});
|
||||
|
||||
this.eventBus.dispatch("pagerendered", {
|
||||
|
@ -484,6 +529,7 @@ class PDFPageView {
|
|||
redrawAnnotationLayer: true,
|
||||
redrawAnnotationEditorLayer: true,
|
||||
redrawXfaLayer: true,
|
||||
redrawTextLayer: true,
|
||||
});
|
||||
|
||||
this.eventBus.dispatch("pagerendered", {
|
||||
|
@ -508,6 +554,7 @@ class PDFPageView {
|
|||
keepAnnotationLayer: true,
|
||||
keepAnnotationEditorLayer: true,
|
||||
keepXfaLayer: true,
|
||||
keepTextLayer: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -519,6 +566,7 @@ class PDFPageView {
|
|||
keepAnnotationLayer = false,
|
||||
keepAnnotationEditorLayer = false,
|
||||
keepXfaLayer = false,
|
||||
keepTextLayer = false,
|
||||
} = {}) {
|
||||
if (this.paintTask) {
|
||||
this.paintTask.cancel();
|
||||
|
@ -526,7 +574,7 @@ class PDFPageView {
|
|||
}
|
||||
this.resume = null;
|
||||
|
||||
if (this.textLayer) {
|
||||
if (this.textLayer && (!keepTextLayer || !this.textLayer.div)) {
|
||||
this.textLayer.cancel();
|
||||
this.textLayer = null;
|
||||
}
|
||||
|
@ -561,6 +609,7 @@ class PDFPageView {
|
|||
redrawAnnotationLayer = false,
|
||||
redrawAnnotationEditorLayer = false,
|
||||
redrawXfaLayer = false,
|
||||
redrawTextLayer = false,
|
||||
}) {
|
||||
// Scale target (canvas or svg), its wrapper and page container.
|
||||
const width = this.viewport.width;
|
||||
|
@ -587,49 +636,6 @@ class PDFPageView {
|
|||
}
|
||||
target.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX}, ${scaleY})`;
|
||||
|
||||
if (this.textLayer) {
|
||||
// Rotating the text layer is more complicated since the divs inside the
|
||||
// the text layer are rotated.
|
||||
// TODO: This could probably be simplified by drawing the text layer in
|
||||
// one orientation and then rotating overall.
|
||||
const textLayerViewport = this.textLayer.viewport;
|
||||
const textRelativeRotation =
|
||||
this.viewport.rotation - textLayerViewport.rotation;
|
||||
const textAbsRotation = Math.abs(textRelativeRotation);
|
||||
let scale = width / textLayerViewport.width;
|
||||
if (textAbsRotation === 90 || textAbsRotation === 270) {
|
||||
scale = width / textLayerViewport.height;
|
||||
}
|
||||
const textLayerDiv = this.textLayer.textLayerDiv;
|
||||
let transX, transY;
|
||||
switch (textAbsRotation) {
|
||||
case 0:
|
||||
transX = transY = 0;
|
||||
break;
|
||||
case 90:
|
||||
transX = 0;
|
||||
transY = "-" + textLayerDiv.style.height;
|
||||
break;
|
||||
case 180:
|
||||
transX = "-" + textLayerDiv.style.width;
|
||||
transY = "-" + textLayerDiv.style.height;
|
||||
break;
|
||||
case 270:
|
||||
transX = "-" + textLayerDiv.style.width;
|
||||
transY = 0;
|
||||
break;
|
||||
default:
|
||||
console.error("Bad rotation value.");
|
||||
break;
|
||||
}
|
||||
|
||||
textLayerDiv.style.transform =
|
||||
`rotate(${textAbsRotation}deg) ` +
|
||||
`scale(${scale}) ` +
|
||||
`translate(${transX}, ${transY})`;
|
||||
textLayerDiv.style.transformOrigin = "0% 0%";
|
||||
}
|
||||
|
||||
if (redrawAnnotationLayer && this.annotationLayer) {
|
||||
this._renderAnnotationLayer();
|
||||
}
|
||||
|
@ -639,6 +645,9 @@ class PDFPageView {
|
|||
if (redrawXfaLayer && this.xfaLayer) {
|
||||
this._renderXfaLayer();
|
||||
}
|
||||
if (redrawTextLayer && this.textLayer) {
|
||||
this.#renderTextLayer();
|
||||
}
|
||||
}
|
||||
|
||||
get width() {
|
||||
|
@ -686,40 +695,33 @@ class PDFPageView {
|
|||
canvasWrapper.style.height = div.style.height;
|
||||
canvasWrapper.classList.add("canvasWrapper");
|
||||
|
||||
const lastDivBeforeTextDiv =
|
||||
this.annotationLayer?.div || this.annotationEditorLayer?.div;
|
||||
|
||||
if (lastDivBeforeTextDiv) {
|
||||
// The annotation layer needs to stay on top.
|
||||
lastDivBeforeTextDiv.before(canvasWrapper);
|
||||
if (this.textLayer) {
|
||||
this.textLayer.div.before(canvasWrapper);
|
||||
} else {
|
||||
div.append(canvasWrapper);
|
||||
}
|
||||
|
||||
let textLayer = null;
|
||||
if (this.textLayerMode !== TextLayerMode.DISABLE && this.textLayerFactory) {
|
||||
this._accessibilityManager ||= new TextAccessibilityManager();
|
||||
const textLayerDiv = document.createElement("div");
|
||||
textLayerDiv.className = "textLayer";
|
||||
textLayerDiv.style.width = canvasWrapper.style.width;
|
||||
textLayerDiv.style.height = canvasWrapper.style.height;
|
||||
const lastDivBeforeTextDiv =
|
||||
this.annotationLayer?.div || this.annotationEditorLayer?.div;
|
||||
if (lastDivBeforeTextDiv) {
|
||||
// The annotation layer needs to stay on top.
|
||||
lastDivBeforeTextDiv.before(textLayerDiv);
|
||||
lastDivBeforeTextDiv.before(canvasWrapper);
|
||||
} else {
|
||||
div.append(textLayerDiv);
|
||||
div.append(canvasWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
textLayer = this.textLayerFactory.createTextLayerBuilder({
|
||||
textLayerDiv,
|
||||
pageIndex: this.id - 1,
|
||||
viewport: this.viewport,
|
||||
eventBus: this.eventBus,
|
||||
if (
|
||||
!this.textLayer &&
|
||||
this.textLayerMode !== TextLayerMode.DISABLE &&
|
||||
this.textLayerFactory
|
||||
) {
|
||||
this._accessibilityManager ||= new TextAccessibilityManager();
|
||||
|
||||
this.textLayer = this.textLayerFactory.createTextLayerBuilder({
|
||||
highlighter: this.textHighlighter,
|
||||
accessibilityManager: this._accessibilityManager,
|
||||
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
||||
});
|
||||
canvasWrapper.after(this.textLayer.div);
|
||||
}
|
||||
this.textLayer = textLayer;
|
||||
|
||||
if (
|
||||
this.#annotationMode !== AnnotationMode.DISABLE &&
|
||||
|
@ -809,13 +811,7 @@ class PDFPageView {
|
|||
const resultPromise = paintTask.promise.then(
|
||||
() => {
|
||||
return finishPaintTask(null).then(() => {
|
||||
if (textLayer) {
|
||||
const readableStream = pdfPage.streamTextContent({
|
||||
includeMarkedContent: true,
|
||||
});
|
||||
textLayer.setTextContentStream(readableStream);
|
||||
textLayer.render();
|
||||
}
|
||||
this.#renderTextLayer();
|
||||
|
||||
if (this.annotationLayer) {
|
||||
this._renderAnnotationLayer().then(() => {
|
||||
|
@ -949,10 +945,12 @@ class PDFPageView {
|
|||
|
||||
const sfx = approximateFraction(outputScale.sx);
|
||||
const sfy = approximateFraction(outputScale.sy);
|
||||
|
||||
canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);
|
||||
canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
|
||||
canvas.style.width = roundToDivide(viewport.width, sfx[1]) + "px";
|
||||
canvas.style.height = roundToDivide(viewport.height, sfy[1]) + "px";
|
||||
const { style } = canvas;
|
||||
style.width = roundToDivide(viewport.width, sfx[1]) + "px";
|
||||
style.height = roundToDivide(viewport.height, sfy[1]) + "px";
|
||||
|
||||
// Add the viewport so it's known what it was originally drawn with.
|
||||
this.paintedViewportMap.set(canvas, viewport);
|
||||
|
|
|
@ -128,6 +128,8 @@ function isValidAnnotationEditorMode(mode) {
|
|||
* landscape pages upon printing. The default is `false`.
|
||||
* @property {boolean} [useOnlyCssZoom] - Enables CSS only zooming. The default
|
||||
* value is `false`.
|
||||
* @property {boolean} [isOffscreenCanvasSupported] - Allows to use an
|
||||
* OffscreenCanvas if needed.
|
||||
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
||||
* total pixels, i.e. width * height. Use -1 for no limit. The default value
|
||||
* is 4096 * 4096 (16 mega-pixels).
|
||||
|
@ -287,6 +289,8 @@ class PDFViewer {
|
|||
this.renderer = options.renderer || RendererType.CANVAS;
|
||||
}
|
||||
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
||||
this.isOffscreenCanvasSupported =
|
||||
options.isOffscreenCanvasSupported ?? true;
|
||||
this.maxCanvasPixels = options.maxCanvasPixels;
|
||||
this.l10n = options.l10n || NullL10n;
|
||||
this.#enablePermissions = options.enablePermissions || false;
|
||||
|
@ -775,6 +779,7 @@ class PDFViewer {
|
|||
? this.renderer
|
||||
: null,
|
||||
useOnlyCssZoom: this.useOnlyCssZoom,
|
||||
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
||||
maxCanvasPixels: this.maxCanvasPixels,
|
||||
pageColors: this.pageColors,
|
||||
l10n: this.l10n,
|
||||
|
@ -1635,12 +1640,9 @@ class PDFViewer {
|
|||
|
||||
/**
|
||||
* @typedef {Object} CreateTextLayerBuilderParameters
|
||||
* @property {HTMLDivElement} textLayerDiv
|
||||
* @property {number} pageIndex
|
||||
* @property {PageViewport} viewport
|
||||
* @property {EventBus} eventBus
|
||||
* @property {TextHighlighter} highlighter
|
||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||
* @property {boolean} [isOffscreenCanvasSupported]
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1648,20 +1650,14 @@ class PDFViewer {
|
|||
* @returns {TextLayerBuilder}
|
||||
*/
|
||||
createTextLayerBuilder({
|
||||
textLayerDiv,
|
||||
pageIndex,
|
||||
viewport,
|
||||
eventBus,
|
||||
highlighter,
|
||||
accessibilityManager = null,
|
||||
isOffscreenCanvasSupported = true,
|
||||
}) {
|
||||
return new TextLayerBuilder({
|
||||
textLayerDiv,
|
||||
eventBus,
|
||||
pageIndex,
|
||||
viewport,
|
||||
highlighter,
|
||||
accessibilityManager,
|
||||
isOffscreenCanvasSupported,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ class TextHighlighter {
|
|||
);
|
||||
this._onUpdateTextLayerMatches = null;
|
||||
}
|
||||
this._updateMatches(/* reset = */ true);
|
||||
}
|
||||
|
||||
_convertMatches(matches, matchesLength) {
|
||||
|
@ -264,8 +265,8 @@ class TextHighlighter {
|
|||
}
|
||||
}
|
||||
|
||||
_updateMatches() {
|
||||
if (!this.enabled) {
|
||||
_updateMatches(reset = false) {
|
||||
if (!this.enabled && !reset) {
|
||||
return;
|
||||
}
|
||||
const { findController, matches, pageIdx } = this;
|
||||
|
@ -283,7 +284,7 @@ class TextHighlighter {
|
|||
clearedUntilDivIdx = match.end.divIdx + 1;
|
||||
}
|
||||
|
||||
if (!findController?.highlightMatches) {
|
||||
if (!findController?.highlightMatches || reset) {
|
||||
return;
|
||||
}
|
||||
// Convert the matches on the `findController` into the match format
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
line-height: 1;
|
||||
text-size-adjust: none;
|
||||
forced-color-adjust: none;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.textLayer span,
|
||||
|
|
|
@ -15,22 +15,19 @@
|
|||
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
/** @typedef {import("./text_highlighter").TextHighlighter} TextHighlighter */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
||||
|
||||
import { renderTextLayer } from "pdfjs-lib";
|
||||
import { renderTextLayer, updateTextLayer } from "pdfjs-lib";
|
||||
|
||||
/**
|
||||
* @typedef {Object} TextLayerBuilderOptions
|
||||
* @property {HTMLDivElement} textLayerDiv - The text layer container.
|
||||
* @property {EventBus} eventBus - The application event bus.
|
||||
* @property {number} pageIndex - The page index.
|
||||
* @property {PageViewport} viewport - The viewport of the text layer.
|
||||
* @property {TextHighlighter} highlighter - Optional object that will handle
|
||||
* highlighting text from the find controller.
|
||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||
* @property {boolean} [isOffscreenCanvasSupported] - Allows to use an
|
||||
* OffscreenCanvas if needed.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -39,28 +36,28 @@ import { renderTextLayer } from "pdfjs-lib";
|
|||
* contain text that matches the PDF text they are overlaying.
|
||||
*/
|
||||
class TextLayerBuilder {
|
||||
#scale = 0;
|
||||
|
||||
#rotation = 0;
|
||||
|
||||
constructor({
|
||||
textLayerDiv,
|
||||
eventBus,
|
||||
pageIndex,
|
||||
viewport,
|
||||
highlighter = null,
|
||||
accessibilityManager = null,
|
||||
isOffscreenCanvasSupported = true,
|
||||
}) {
|
||||
this.textLayerDiv = textLayerDiv;
|
||||
this.eventBus = eventBus;
|
||||
this.textContent = null;
|
||||
this.textContentItemsStr = [];
|
||||
this.textContentStream = null;
|
||||
this.renderingDone = false;
|
||||
this.pageNumber = pageIndex + 1;
|
||||
this.viewport = viewport;
|
||||
this.textDivs = [];
|
||||
this.textDivProperties = new WeakMap();
|
||||
this.textLayerRenderTask = null;
|
||||
this.highlighter = highlighter;
|
||||
this.accessibilityManager = accessibilityManager;
|
||||
this.isOffscreenCanvasSupported = isOffscreenCanvasSupported;
|
||||
|
||||
this.#bindMouse();
|
||||
this.div = document.createElement("div");
|
||||
this.div.className = "textLayer";
|
||||
}
|
||||
|
||||
#finishRendering() {
|
||||
|
@ -68,48 +65,80 @@ class TextLayerBuilder {
|
|||
|
||||
const endOfContent = document.createElement("div");
|
||||
endOfContent.className = "endOfContent";
|
||||
this.textLayerDiv.append(endOfContent);
|
||||
this.div.append(endOfContent);
|
||||
|
||||
this.eventBus.dispatch("textlayerrendered", {
|
||||
source: this,
|
||||
pageNumber: this.pageNumber,
|
||||
numTextDivs: this.textDivs.length,
|
||||
});
|
||||
this.#bindMouse();
|
||||
}
|
||||
|
||||
get numTextDivs() {
|
||||
return this.textDivs.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the text layer.
|
||||
*/
|
||||
render() {
|
||||
if (!(this.textContent || this.textContentStream) || this.renderingDone) {
|
||||
async render(viewport) {
|
||||
if (!(this.textContent || this.textContentStream)) {
|
||||
throw new Error(
|
||||
`Neither "textContent" nor "textContentStream" specified.`
|
||||
);
|
||||
}
|
||||
|
||||
const scale = viewport.scale * (globalThis.devicePixelRatio || 1);
|
||||
if (this.renderingDone) {
|
||||
const { rotation } = viewport;
|
||||
const mustRotate = rotation !== this.#rotation;
|
||||
const mustRescale = scale !== this.#scale;
|
||||
if (mustRotate || mustRescale) {
|
||||
this.hide();
|
||||
updateTextLayer({
|
||||
container: this.div,
|
||||
viewport,
|
||||
textDivs: this.textDivs,
|
||||
textDivProperties: this.textDivProperties,
|
||||
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
||||
mustRescale,
|
||||
mustRotate,
|
||||
});
|
||||
this.show();
|
||||
this.#scale = scale;
|
||||
this.#rotation = rotation;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.cancel();
|
||||
|
||||
this.textDivs.length = 0;
|
||||
this.cancel();
|
||||
this.highlighter?.setTextMapping(this.textDivs, this.textContentItemsStr);
|
||||
this.accessibilityManager?.setTextMapping(this.textDivs);
|
||||
|
||||
const textLayerFrag = document.createDocumentFragment();
|
||||
this.textLayerRenderTask = renderTextLayer({
|
||||
textContent: this.textContent,
|
||||
textContentStream: this.textContentStream,
|
||||
container: textLayerFrag,
|
||||
viewport: this.viewport,
|
||||
container: this.div,
|
||||
viewport,
|
||||
textDivs: this.textDivs,
|
||||
textDivProperties: this.textDivProperties,
|
||||
textContentItemsStr: this.textContentItemsStr,
|
||||
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
||||
});
|
||||
this.textLayerRenderTask.promise.then(
|
||||
() => {
|
||||
this.textLayerDiv.append(textLayerFrag);
|
||||
this.#finishRendering();
|
||||
this.highlighter?.enable();
|
||||
this.accessibilityManager?.enable();
|
||||
},
|
||||
function (reason) {
|
||||
// Cancelled or failed to render text layer; skipping errors.
|
||||
}
|
||||
);
|
||||
|
||||
await this.textLayerRenderTask.promise;
|
||||
this.#finishRendering();
|
||||
this.#scale = scale;
|
||||
this.accessibilityManager?.enable();
|
||||
this.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
// 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() {
|
||||
this.div.hidden = false;
|
||||
this.highlighter?.enable();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,6 +151,9 @@ class TextLayerBuilder {
|
|||
}
|
||||
this.highlighter?.disable();
|
||||
this.accessibilityManager?.disable();
|
||||
this.textContentItemsStr.length = 0;
|
||||
this.textDivs.length = 0;
|
||||
this.textDivProperties = new WeakMap();
|
||||
}
|
||||
|
||||
setTextContentStream(readableStream) {
|
||||
|
@ -140,7 +172,7 @@ class TextLayerBuilder {
|
|||
* dragged up or down.
|
||||
*/
|
||||
#bindMouse() {
|
||||
const div = this.textLayerDiv;
|
||||
const { div } = this;
|
||||
|
||||
div.addEventListener("mousedown", evt => {
|
||||
const end = div.querySelector(".endOfContent");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue