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
@ -16,6 +16,7 @@
|
|||||||
import {
|
import {
|
||||||
AbortException,
|
AbortException,
|
||||||
createPromiseCapability,
|
createPromiseCapability,
|
||||||
|
FeatureTest,
|
||||||
Util,
|
Util,
|
||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
|
|
||||||
@ -27,16 +28,40 @@ import {
|
|||||||
* render (the object is returned by the page's `getTextContent` method).
|
* render (the object is returned by the page's `getTextContent` method).
|
||||||
* @property {ReadableStream} [textContentStream] - Text content stream to
|
* @property {ReadableStream} [textContentStream] - Text content stream to
|
||||||
* render (the stream is returned by the page's `streamTextContent` method).
|
* render (the stream is returned by the page's `streamTextContent` method).
|
||||||
* @property {DocumentFragment | HTMLElement} container - The DOM node that
|
* @property {HTMLElement} container - The DOM node that will contain the text
|
||||||
* will contain the text runs.
|
* runs.
|
||||||
* @property {import("./display_utils").PageViewport} viewport - The target
|
* @property {import("./display_utils").PageViewport} viewport - The target
|
||||||
* viewport to properly layout the text runs.
|
* viewport to properly layout the text runs.
|
||||||
* @property {Array<HTMLElement>} [textDivs] - HTML elements that correspond to
|
* @property {Array<HTMLElement>} [textDivs] - HTML elements that correspond to
|
||||||
* the text items of the textContent input.
|
* the text items of the textContent input.
|
||||||
* This is output and shall initially be set to an empty array.
|
* This is output and shall initially be set to an empty array.
|
||||||
|
* @property {WeakMap<HTMLElement,Object>} [textDivProperties] - Some properties
|
||||||
|
* weakly mapped to the HTML elements used to render the text.
|
||||||
* @property {Array<string>} [textContentItemsStr] - Strings that correspond to
|
* @property {Array<string>} [textContentItemsStr] - Strings that correspond to
|
||||||
* the `str` property of the text items of the textContent input.
|
* the `str` property of the text items of the textContent input.
|
||||||
* This is output and shall initially be set to an empty array.
|
* This is output and shall initially be set to an empty array.
|
||||||
|
* @property {boolean} [isOffscreenCanvasSupported] true if we can use
|
||||||
|
* OffscreenCanvas to measure string widths.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text layer update parameters.
|
||||||
|
*
|
||||||
|
* @typedef {Object} TextLayerUpdateParameters
|
||||||
|
* @property {HTMLElement} container - The DOM node that will contain the text
|
||||||
|
* runs.
|
||||||
|
* @property {import("./display_utils").PageViewport} viewport - The target
|
||||||
|
* viewport to properly layout the text runs.
|
||||||
|
* @property {Array<HTMLElement>} [textDivs] - HTML elements that correspond to
|
||||||
|
* the text items of the textContent input.
|
||||||
|
* This is output and shall initially be set to an empty array.
|
||||||
|
* @property {WeakMap<HTMLElement,Object>} [textDivProperties] - Some properties
|
||||||
|
* weakly mapped to the HTML elements used to render the text.
|
||||||
|
* @property {boolean} [isOffscreenCanvasSupported] true if we can use
|
||||||
|
* OffscreenCanvas to measure string widths.
|
||||||
|
* @property {boolean} [mustRotate] true if the text layer must be rotated.
|
||||||
|
* @property {boolean} [mustRescale] true if the text layer contents must be
|
||||||
|
* rescaled.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const MAX_TEXT_DIVS_TO_RENDER = 100000;
|
const MAX_TEXT_DIVS_TO_RENDER = 100000;
|
||||||
@ -44,13 +69,27 @@ const DEFAULT_FONT_SIZE = 30;
|
|||||||
const DEFAULT_FONT_ASCENT = 0.8;
|
const DEFAULT_FONT_ASCENT = 0.8;
|
||||||
const ascentCache = new Map();
|
const ascentCache = new Map();
|
||||||
|
|
||||||
function getAscent(fontFamily, ctx) {
|
function getCtx(size, isOffscreenCanvasSupported) {
|
||||||
|
let ctx;
|
||||||
|
if (isOffscreenCanvasSupported && FeatureTest.isOffscreenCanvasSupported) {
|
||||||
|
ctx = new OffscreenCanvas(size, size).getContext("2d", { alpha: false });
|
||||||
|
} else {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = canvas.height = size;
|
||||||
|
ctx = canvas.getContext("2d", { alpha: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAscent(fontFamily, isOffscreenCanvasSupported) {
|
||||||
const cachedAscent = ascentCache.get(fontFamily);
|
const cachedAscent = ascentCache.get(fontFamily);
|
||||||
if (cachedAscent) {
|
if (cachedAscent) {
|
||||||
return cachedAscent;
|
return cachedAscent;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.save();
|
const ctx = getCtx(DEFAULT_FONT_SIZE, isOffscreenCanvasSupported);
|
||||||
|
|
||||||
ctx.font = `${DEFAULT_FONT_SIZE}px ${fontFamily}`;
|
ctx.font = `${DEFAULT_FONT_SIZE}px ${fontFamily}`;
|
||||||
const metrics = ctx.measureText("");
|
const metrics = ctx.measureText("");
|
||||||
|
|
||||||
@ -58,9 +97,10 @@ function getAscent(fontFamily, ctx) {
|
|||||||
let ascent = metrics.fontBoundingBoxAscent;
|
let ascent = metrics.fontBoundingBoxAscent;
|
||||||
let descent = Math.abs(metrics.fontBoundingBoxDescent);
|
let descent = Math.abs(metrics.fontBoundingBoxDescent);
|
||||||
if (ascent) {
|
if (ascent) {
|
||||||
ctx.restore();
|
|
||||||
const ratio = ascent / (ascent + descent);
|
const ratio = ascent / (ascent + descent);
|
||||||
ascentCache.set(fontFamily, ratio);
|
ascentCache.set(fontFamily, ratio);
|
||||||
|
|
||||||
|
ctx.canvas.width = ctx.canvas.height = 0;
|
||||||
return ratio;
|
return ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +139,7 @@ function getAscent(fontFamily, ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.canvas.width = ctx.canvas.height = 0;
|
||||||
|
|
||||||
if (ascent) {
|
if (ascent) {
|
||||||
const ratio = ascent / (ascent + descent);
|
const ratio = ascent / (ascent + descent);
|
||||||
@ -111,7 +151,7 @@ function getAscent(fontFamily, ctx) {
|
|||||||
return DEFAULT_FONT_ASCENT;
|
return DEFAULT_FONT_ASCENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendText(task, geom, styles, ctx) {
|
function appendText(task, geom, styles) {
|
||||||
// Initialize all used properties to keep the caches monomorphic.
|
// Initialize all used properties to keep the caches monomorphic.
|
||||||
const textDiv = document.createElement("span");
|
const textDiv = document.createElement("span");
|
||||||
const textDivProperties = {
|
const textDivProperties = {
|
||||||
@ -123,14 +163,15 @@ function appendText(task, geom, styles, ctx) {
|
|||||||
};
|
};
|
||||||
task._textDivs.push(textDiv);
|
task._textDivs.push(textDiv);
|
||||||
|
|
||||||
const tx = Util.transform(task._viewport.transform, geom.transform);
|
const tx = Util.transform(task._transform, geom.transform);
|
||||||
let angle = Math.atan2(tx[1], tx[0]);
|
let angle = Math.atan2(tx[1], tx[0]);
|
||||||
const style = styles[geom.fontName];
|
const style = styles[geom.fontName];
|
||||||
if (style.vertical) {
|
if (style.vertical) {
|
||||||
angle += Math.PI / 2;
|
angle += Math.PI / 2;
|
||||||
}
|
}
|
||||||
const fontHeight = Math.hypot(tx[2], tx[3]);
|
const fontHeight = Math.hypot(tx[2], tx[3]);
|
||||||
const fontAscent = fontHeight * getAscent(style.fontFamily, ctx);
|
const fontAscent =
|
||||||
|
fontHeight * getAscent(style.fontFamily, task._isOffscreenCanvasSupported);
|
||||||
|
|
||||||
let left, top;
|
let left, top;
|
||||||
if (angle === 0) {
|
if (angle === 0) {
|
||||||
@ -140,12 +181,21 @@ function appendText(task, geom, styles, ctx) {
|
|||||||
left = tx[4] + fontAscent * Math.sin(angle);
|
left = tx[4] + fontAscent * Math.sin(angle);
|
||||||
top = tx[5] - fontAscent * Math.cos(angle);
|
top = tx[5] - fontAscent * Math.cos(angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scaleFactorStr = "calc(var(--scale-factor)*";
|
||||||
|
const divStyle = textDiv.style;
|
||||||
// Setting the style properties individually, rather than all at once,
|
// Setting the style properties individually, rather than all at once,
|
||||||
// should be OK since the `textDiv` isn't appended to the document yet.
|
// should be OK since the `textDiv` isn't appended to the document yet.
|
||||||
textDiv.style.left = `${left}px`;
|
if (task._container === task._rootContainer) {
|
||||||
textDiv.style.top = `${top}px`;
|
divStyle.left = `${((100 * left) / task._pageWidth).toFixed(2)}%`;
|
||||||
textDiv.style.fontSize = `${fontHeight}px`;
|
divStyle.top = `${((100 * top) / task._pageHeight).toFixed(2)}%`;
|
||||||
textDiv.style.fontFamily = style.fontFamily;
|
} else {
|
||||||
|
// We're in a marked content span, hence we can't use percents.
|
||||||
|
divStyle.left = `${scaleFactorStr}${left.toFixed(2)}px)`;
|
||||||
|
divStyle.top = `${scaleFactorStr}${top.toFixed(2)}px)`;
|
||||||
|
}
|
||||||
|
divStyle.fontSize = `${scaleFactorStr}${fontHeight.toFixed(2)}px)`;
|
||||||
|
divStyle.fontFamily = style.fontFamily;
|
||||||
|
|
||||||
textDivProperties.fontSize = fontHeight;
|
textDivProperties.fontSize = fontHeight;
|
||||||
|
|
||||||
@ -183,11 +233,7 @@ function appendText(task, geom, styles, ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shouldScaleText) {
|
if (shouldScaleText) {
|
||||||
if (style.vertical) {
|
textDivProperties.canvasWidth = style.vertical ? geom.height : geom.width;
|
||||||
textDivProperties.canvasWidth = geom.height * task._viewport.scale;
|
|
||||||
} else {
|
|
||||||
textDivProperties.canvasWidth = geom.width * task._viewport.scale;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
task._textDivProperties.set(textDiv, textDivProperties);
|
task._textDivProperties.set(textDiv, textDivProperties);
|
||||||
if (task._textContentStream) {
|
if (task._textContentStream) {
|
||||||
@ -195,6 +241,35 @@ function appendText(task, geom, styles, ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function layout(params) {
|
||||||
|
const { div, scale, properties, ctx, prevFontSize, prevFontFamily } = params;
|
||||||
|
const { style } = div;
|
||||||
|
let transform = "";
|
||||||
|
if (properties.canvasWidth !== 0 && properties.hasText) {
|
||||||
|
const { fontFamily } = style;
|
||||||
|
const { canvasWidth, fontSize } = properties;
|
||||||
|
|
||||||
|
if (prevFontSize !== fontSize || prevFontFamily !== fontFamily) {
|
||||||
|
ctx.font = `${fontSize * scale}px ${fontFamily}`;
|
||||||
|
params.prevFontSize = fontSize;
|
||||||
|
params.prevFontFamily = fontFamily;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only measure the width for multi-char text divs, see `appendText`.
|
||||||
|
const { width } = ctx.measureText(div.textContent);
|
||||||
|
|
||||||
|
if (width > 0) {
|
||||||
|
transform = `scaleX(${(canvasWidth * scale) / width})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (properties.angle !== 0) {
|
||||||
|
transform = `rotate(${properties.angle}deg) ${transform}`;
|
||||||
|
}
|
||||||
|
if (transform.length > 0) {
|
||||||
|
style.transform = transform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function render(task) {
|
function render(task) {
|
||||||
if (task._canceled) {
|
if (task._canceled) {
|
||||||
return;
|
return;
|
||||||
@ -228,40 +303,41 @@ class TextLayerRenderTask {
|
|||||||
container,
|
container,
|
||||||
viewport,
|
viewport,
|
||||||
textDivs,
|
textDivs,
|
||||||
|
textDivProperties,
|
||||||
textContentItemsStr,
|
textContentItemsStr,
|
||||||
|
isOffscreenCanvasSupported,
|
||||||
}) {
|
}) {
|
||||||
this._textContent = textContent;
|
this._textContent = textContent;
|
||||||
this._textContentStream = textContentStream;
|
this._textContentStream = textContentStream;
|
||||||
this._container = container;
|
this._container = this._rootContainer = container;
|
||||||
this._document = container.ownerDocument;
|
|
||||||
this._viewport = viewport;
|
|
||||||
this._textDivs = textDivs || [];
|
this._textDivs = textDivs || [];
|
||||||
this._textContentItemsStr = textContentItemsStr || [];
|
this._textContentItemsStr = textContentItemsStr || [];
|
||||||
this._fontInspectorEnabled = !!globalThis.FontInspector?.enabled;
|
this._fontInspectorEnabled = !!globalThis.FontInspector?.enabled;
|
||||||
|
|
||||||
this._reader = null;
|
this._reader = null;
|
||||||
this._layoutTextLastFontSize = null;
|
this._textDivProperties = textDivProperties || new WeakMap();
|
||||||
this._layoutTextLastFontFamily = null;
|
|
||||||
this._layoutTextCtx = null;
|
|
||||||
this._textDivProperties = new WeakMap();
|
|
||||||
this._renderingDone = false;
|
this._renderingDone = false;
|
||||||
this._canceled = false;
|
this._canceled = false;
|
||||||
this._capability = createPromiseCapability();
|
this._capability = createPromiseCapability();
|
||||||
this._devicePixelRatio = globalThis.devicePixelRatio || 1;
|
this._layoutTextParams = {
|
||||||
|
prevFontSize: null,
|
||||||
|
prevFontFamily: null,
|
||||||
|
div: null,
|
||||||
|
scale: viewport.scale * (globalThis.devicePixelRatio || 1),
|
||||||
|
properties: null,
|
||||||
|
ctx: getCtx(0, isOffscreenCanvasSupported),
|
||||||
|
};
|
||||||
|
const [pageLLx, pageLLy, pageURx, pageURy] = viewport.viewBox;
|
||||||
|
this._transform = [1, 0, 0, -1, -pageLLx, pageURy];
|
||||||
|
this._pageWidth = pageURx - pageLLx;
|
||||||
|
this._pageHeight = pageURy - pageLLy;
|
||||||
|
|
||||||
|
setTextLayerDimensions(container, viewport);
|
||||||
|
|
||||||
// Always clean-up the temporary canvas once rendering is no longer pending.
|
// Always clean-up the temporary canvas once rendering is no longer pending.
|
||||||
this._capability.promise
|
this._capability.promise
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
// The `textDiv` properties are no longer needed.
|
this._layoutTextParams = null;
|
||||||
this._textDivProperties = null;
|
|
||||||
|
|
||||||
if (this._layoutTextCtx) {
|
|
||||||
// Zeroing the width and height cause Firefox to release graphics
|
|
||||||
// resources immediately, which can greatly reduce memory consumption.
|
|
||||||
this._layoutTextCtx.canvas.width = 0;
|
|
||||||
this._layoutTextCtx.canvas.height = 0;
|
|
||||||
this._layoutTextCtx = null;
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// Avoid "Uncaught promise" messages in the console.
|
// Avoid "Uncaught promise" messages in the console.
|
||||||
@ -289,7 +365,7 @@ class TextLayerRenderTask {
|
|||||||
});
|
});
|
||||||
this._reader = null;
|
this._reader = null;
|
||||||
}
|
}
|
||||||
this._capability.reject(new Error("TextLayer task cancelled."));
|
this._capability.reject(new AbortException("TextLayer task cancelled."));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -315,7 +391,7 @@ class TextLayerRenderTask {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this._textContentItemsStr.push(item.str);
|
this._textContentItemsStr.push(item.str);
|
||||||
appendText(this, item, styleCache, this._layoutTextCtx);
|
appendText(this, item, styleCache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,39 +399,10 @@ class TextLayerRenderTask {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_layoutText(textDiv) {
|
_layoutText(textDiv) {
|
||||||
const textDivProperties = this._textDivProperties.get(textDiv);
|
const textDivProperties = (this._layoutTextParams.properties =
|
||||||
|
this._textDivProperties.get(textDiv));
|
||||||
let transform = "";
|
this._layoutTextParams.div = textDiv;
|
||||||
if (textDivProperties.canvasWidth !== 0 && textDivProperties.hasText) {
|
layout(this._layoutTextParams);
|
||||||
const { fontFamily } = textDiv.style;
|
|
||||||
const { fontSize } = textDivProperties;
|
|
||||||
|
|
||||||
// Only build font string and set to context if different from last.
|
|
||||||
if (
|
|
||||||
fontSize !== this._layoutTextLastFontSize ||
|
|
||||||
fontFamily !== this._layoutTextLastFontFamily
|
|
||||||
) {
|
|
||||||
this._layoutTextCtx.font = `${
|
|
||||||
fontSize * this._devicePixelRatio
|
|
||||||
}px ${fontFamily}`;
|
|
||||||
this._layoutTextLastFontSize = fontSize;
|
|
||||||
this._layoutTextLastFontFamily = fontFamily;
|
|
||||||
}
|
|
||||||
// Only measure the width for multi-char text divs, see `appendText`.
|
|
||||||
const { width } = this._layoutTextCtx.measureText(textDiv.textContent);
|
|
||||||
|
|
||||||
if (width > 0) {
|
|
||||||
transform = `scaleX(${
|
|
||||||
(this._devicePixelRatio * textDivProperties.canvasWidth) / width
|
|
||||||
})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (textDivProperties.angle !== 0) {
|
|
||||||
transform = `rotate(${textDivProperties.angle}deg) ${transform}`;
|
|
||||||
}
|
|
||||||
if (transform.length > 0) {
|
|
||||||
textDiv.style.transform = transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textDivProperties.hasText) {
|
if (textDivProperties.hasText) {
|
||||||
this._container.append(textDiv);
|
this._container.append(textDiv);
|
||||||
@ -375,10 +422,6 @@ class TextLayerRenderTask {
|
|||||||
let styleCache = Object.create(null);
|
let styleCache = Object.create(null);
|
||||||
|
|
||||||
// The temporary canvas is used to measure text length in the DOM.
|
// The temporary canvas is used to measure text length in the DOM.
|
||||||
const canvas = this._document.createElement("canvas");
|
|
||||||
canvas.height = canvas.width = DEFAULT_FONT_SIZE;
|
|
||||||
|
|
||||||
this._layoutTextCtx = canvas.getContext("2d", { alpha: false });
|
|
||||||
|
|
||||||
if (this._textContent) {
|
if (this._textContent) {
|
||||||
const textItems = this._textContent.items;
|
const textItems = this._textContent.items;
|
||||||
@ -426,9 +469,67 @@ function renderTextLayer(renderParameters) {
|
|||||||
viewport: renderParameters.viewport,
|
viewport: renderParameters.viewport,
|
||||||
textDivs: renderParameters.textDivs,
|
textDivs: renderParameters.textDivs,
|
||||||
textContentItemsStr: renderParameters.textContentItemsStr,
|
textContentItemsStr: renderParameters.textContentItemsStr,
|
||||||
|
textDivProperties: renderParameters.textDivProperties,
|
||||||
|
isOffscreenCanvasSupported: renderParameters.isOffscreenCanvasSupported,
|
||||||
});
|
});
|
||||||
task._render();
|
task._render();
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { renderTextLayer, TextLayerRenderTask };
|
/**
|
||||||
|
* @param {TextLayerUpdateParameters} renderParameters
|
||||||
|
* @returns {TextLayerRenderTask}
|
||||||
|
*/
|
||||||
|
function updateTextLayer({
|
||||||
|
container,
|
||||||
|
viewport,
|
||||||
|
textDivs,
|
||||||
|
textDivProperties,
|
||||||
|
isOffscreenCanvasSupported,
|
||||||
|
mustRotate = true,
|
||||||
|
mustRescale = true,
|
||||||
|
}) {
|
||||||
|
if (mustRotate) {
|
||||||
|
setTextLayerDimensions(container, { rotation: viewport.rotation });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mustRescale) {
|
||||||
|
const ctx = getCtx(0, isOffscreenCanvasSupported);
|
||||||
|
const scale = viewport.scale * (globalThis.devicePixelRatio || 1);
|
||||||
|
const params = {
|
||||||
|
prevFontSize: null,
|
||||||
|
prevFontFamily: null,
|
||||||
|
div: null,
|
||||||
|
scale,
|
||||||
|
properties: null,
|
||||||
|
ctx,
|
||||||
|
};
|
||||||
|
for (const div of textDivs) {
|
||||||
|
params.properties = textDivProperties.get(div);
|
||||||
|
params.div = div;
|
||||||
|
layout(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLDivElement} div
|
||||||
|
* @param {import("./display_utils").PageViewport} viewport
|
||||||
|
*/
|
||||||
|
function setTextLayerDimensions(div, viewport) {
|
||||||
|
if (!viewport.viewBox) {
|
||||||
|
div.setAttribute("data-main-rotation", viewport.rotation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [pageLLx, pageLLy, pageURx, pageURy] = viewport.viewBox;
|
||||||
|
const pageWidth = pageURx - pageLLx;
|
||||||
|
const pageHeight = pageURy - pageLLy;
|
||||||
|
const { style } = div;
|
||||||
|
|
||||||
|
style.width = `calc(var(--scale-factor) * ${pageWidth}px)`;
|
||||||
|
style.height = `calc(var(--scale-factor) * ${pageHeight}px)`;
|
||||||
|
div.setAttribute("data-main-rotation", viewport.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { renderTextLayer, TextLayerRenderTask, updateTextLayer };
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
/** @typedef {import("./display/text_layer").TextLayerRenderTask} TextLayerRenderTask */
|
/** @typedef {import("./display/text_layer").TextLayerRenderTask} TextLayerRenderTask */
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AbortException,
|
||||||
AnnotationEditorParamsType,
|
AnnotationEditorParamsType,
|
||||||
AnnotationEditorType,
|
AnnotationEditorType,
|
||||||
AnnotationMode,
|
AnnotationMode,
|
||||||
@ -60,12 +61,12 @@ import {
|
|||||||
PixelsPerInch,
|
PixelsPerInch,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
} from "./display/display_utils.js";
|
} from "./display/display_utils.js";
|
||||||
|
import { renderTextLayer, updateTextLayer } from "./display/text_layer.js";
|
||||||
import { AnnotationEditorLayer } from "./display/editor/annotation_editor_layer.js";
|
import { AnnotationEditorLayer } from "./display/editor/annotation_editor_layer.js";
|
||||||
import { AnnotationEditorUIManager } from "./display/editor/tools.js";
|
import { AnnotationEditorUIManager } from "./display/editor/tools.js";
|
||||||
import { AnnotationLayer } from "./display/annotation_layer.js";
|
import { AnnotationLayer } from "./display/annotation_layer.js";
|
||||||
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
||||||
import { isNodeJS } from "./shared/is_node.js";
|
import { isNodeJS } from "./shared/is_node.js";
|
||||||
import { renderTextLayer } from "./display/text_layer.js";
|
|
||||||
import { SVGGraphics } from "./display/svg.js";
|
import { SVGGraphics } from "./display/svg.js";
|
||||||
import { XfaLayer } from "./display/xfa_layer.js";
|
import { XfaLayer } from "./display/xfa_layer.js";
|
||||||
|
|
||||||
@ -110,6 +111,7 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
AbortException,
|
||||||
AnnotationEditorLayer,
|
AnnotationEditorLayer,
|
||||||
AnnotationEditorParamsType,
|
AnnotationEditorParamsType,
|
||||||
AnnotationEditorType,
|
AnnotationEditorType,
|
||||||
@ -143,6 +145,7 @@ export {
|
|||||||
SVGGraphics,
|
SVGGraphics,
|
||||||
UnexpectedResponseException,
|
UnexpectedResponseException,
|
||||||
UNSUPPORTED_FEATURES,
|
UNSUPPORTED_FEATURES,
|
||||||
|
updateTextLayer,
|
||||||
Util,
|
Util,
|
||||||
VerbosityLevel,
|
VerbosityLevel,
|
||||||
version,
|
version,
|
||||||
|
@ -168,7 +168,7 @@ class Rasterize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get textStylePromise() {
|
static get textStylePromise() {
|
||||||
const styles = ["./text_layer_test.css"];
|
const styles = [VIEWER_CSS, "./text_layer_test.css"];
|
||||||
return shadow(this, "textStylePromise", loadStyles(styles));
|
return shadow(this, "textStylePromise", loadStyles(styles));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,8 +256,10 @@ class Rasterize {
|
|||||||
// Items are transformed to have 1px font size.
|
// Items are transformed to have 1px font size.
|
||||||
svg.setAttribute("font-size", 1);
|
svg.setAttribute("font-size", 1);
|
||||||
|
|
||||||
const [overrides] = await this.textStylePromise;
|
const [common, overrides] = await this.textStylePromise;
|
||||||
style.textContent = overrides;
|
style.textContent =
|
||||||
|
`${common}\n${overrides}\n` +
|
||||||
|
`:root { --scale-factor: ${viewport.scale} }`;
|
||||||
|
|
||||||
// Rendering text layer as HTML.
|
// Rendering text layer as HTML.
|
||||||
const task = renderTextLayer({
|
const task = renderTextLayer({
|
||||||
@ -265,6 +267,7 @@ class Rasterize {
|
|||||||
container: div,
|
container: div,
|
||||||
viewport,
|
viewport,
|
||||||
});
|
});
|
||||||
|
|
||||||
await task.promise;
|
await task.promise;
|
||||||
svg.append(foreignObject);
|
svg.append(foreignObject);
|
||||||
|
|
||||||
|
@ -22,9 +22,11 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.textLayer span,
|
.textLayer span,
|
||||||
.textLayer br {
|
.textLayer br {
|
||||||
|
color: black;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
transform-origin: 0% 0%;
|
transform-origin: 0% 0%;
|
||||||
|
@ -24,7 +24,7 @@ import { isNodeJS } from "../../src/shared/is_node.js";
|
|||||||
describe("textLayer", function () {
|
describe("textLayer", function () {
|
||||||
it("creates textLayer from ReadableStream", async function () {
|
it("creates textLayer from ReadableStream", async function () {
|
||||||
if (isNodeJS) {
|
if (isNodeJS) {
|
||||||
pending("document.createDocumentFragment is not supported in Node.js.");
|
pending("document.createElement is not supported in Node.js.");
|
||||||
}
|
}
|
||||||
const loadingTask = getDocument(buildGetDocumentParams("basicapi.pdf"));
|
const loadingTask = getDocument(buildGetDocumentParams("basicapi.pdf"));
|
||||||
const pdfDocument = await loadingTask.promise;
|
const pdfDocument = await loadingTask.promise;
|
||||||
@ -34,7 +34,7 @@ describe("textLayer", function () {
|
|||||||
|
|
||||||
const textLayerRenderTask = renderTextLayer({
|
const textLayerRenderTask = renderTextLayer({
|
||||||
textContentStream: page.streamTextContent(),
|
textContentStream: page.streamTextContent(),
|
||||||
container: document.createDocumentFragment(),
|
container: document.createElement("div"),
|
||||||
viewport: page.getViewport(),
|
viewport: page.getViewport(),
|
||||||
textContentItemsStr,
|
textContentItemsStr,
|
||||||
});
|
});
|
||||||
|
@ -501,6 +501,7 @@ const PDFViewerApplication = {
|
|||||||
imageResourcesPath: AppOptions.get("imageResourcesPath"),
|
imageResourcesPath: AppOptions.get("imageResourcesPath"),
|
||||||
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
|
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
|
||||||
useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
|
useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
|
||||||
|
isOffscreenCanvasSupported: AppOptions.get("isOffscreenCanvasSupported"),
|
||||||
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
||||||
enablePermissions: AppOptions.get("enablePermissions"),
|
enablePermissions: AppOptions.get("enablePermissions"),
|
||||||
pageColors,
|
pageColors,
|
||||||
|
@ -165,12 +165,9 @@ class DefaultStructTreeLayerFactory {
|
|||||||
class DefaultTextLayerFactory {
|
class DefaultTextLayerFactory {
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} CreateTextLayerBuilderParameters
|
* @typedef {Object} CreateTextLayerBuilderParameters
|
||||||
* @property {HTMLDivElement} textLayerDiv
|
|
||||||
* @property {number} pageIndex
|
|
||||||
* @property {PageViewport} viewport
|
|
||||||
* @property {EventBus} eventBus
|
|
||||||
* @property {TextHighlighter} highlighter
|
* @property {TextHighlighter} highlighter
|
||||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||||
|
* @property {boolean} [isOffscreenCanvasSupported]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,20 +175,14 @@ class DefaultTextLayerFactory {
|
|||||||
* @returns {TextLayerBuilder}
|
* @returns {TextLayerBuilder}
|
||||||
*/
|
*/
|
||||||
createTextLayerBuilder({
|
createTextLayerBuilder({
|
||||||
textLayerDiv,
|
|
||||||
pageIndex,
|
|
||||||
viewport,
|
|
||||||
eventBus,
|
|
||||||
highlighter,
|
highlighter,
|
||||||
accessibilityManager = null,
|
accessibilityManager = null,
|
||||||
|
isOffscreenCanvasSupported = true,
|
||||||
}) {
|
}) {
|
||||||
return new TextLayerBuilder({
|
return new TextLayerBuilder({
|
||||||
textLayerDiv,
|
|
||||||
pageIndex,
|
|
||||||
viewport,
|
|
||||||
eventBus,
|
|
||||||
highlighter,
|
highlighter,
|
||||||
accessibilityManager,
|
accessibilityManager,
|
||||||
|
isOffscreenCanvasSupported,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
/** @typedef {import("./annotation_layer_builder").AnnotationLayerBuilder} AnnotationLayerBuilder */
|
/** @typedef {import("./annotation_layer_builder").AnnotationLayerBuilder} AnnotationLayerBuilder */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./annotation_editor_layer_builder").AnnotationEditorLayerBuilder} AnnotationEditorLayerBuilder */
|
/** @typedef {import("./annotation_editor_layer_builder").AnnotationEditorLayerBuilder} AnnotationEditorLayerBuilder */
|
||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./struct_tree_builder").StructTreeLayerBuilder} StructTreeLayerBuilder */
|
/** @typedef {import("./struct_tree_builder").StructTreeLayerBuilder} StructTreeLayerBuilder */
|
||||||
/** @typedef {import("./text_highlighter").TextHighlighter} TextHighlighter */
|
/** @typedef {import("./text_highlighter").TextHighlighter} TextHighlighter */
|
||||||
@ -168,12 +167,9 @@ class IRenderableView {
|
|||||||
class IPDFTextLayerFactory {
|
class IPDFTextLayerFactory {
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} CreateTextLayerBuilderParameters
|
* @typedef {Object} CreateTextLayerBuilderParameters
|
||||||
* @property {HTMLDivElement} textLayerDiv
|
|
||||||
* @property {number} pageIndex
|
|
||||||
* @property {PageViewport} viewport
|
|
||||||
* @property {EventBus} eventBus
|
|
||||||
* @property {TextHighlighter} highlighter
|
* @property {TextHighlighter} highlighter
|
||||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||||
|
* @property {boolean} [isOffscreenCanvasSupported]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -181,12 +177,9 @@ class IPDFTextLayerFactory {
|
|||||||
* @returns {TextLayerBuilder}
|
* @returns {TextLayerBuilder}
|
||||||
*/
|
*/
|
||||||
createTextLayerBuilder({
|
createTextLayerBuilder({
|
||||||
textLayerDiv,
|
|
||||||
pageIndex,
|
|
||||||
viewport,
|
|
||||||
eventBus,
|
|
||||||
highlighter,
|
highlighter,
|
||||||
accessibilityManager,
|
accessibilityManager,
|
||||||
|
isOffscreenCanvasSupported,
|
||||||
}) {}
|
}) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AbortException,
|
||||||
AnnotationMode,
|
AnnotationMode,
|
||||||
createPromiseCapability,
|
createPromiseCapability,
|
||||||
PixelsPerInch,
|
PixelsPerInch,
|
||||||
@ -82,6 +83,8 @@ import { TextAccessibilityManager } from "./text_accessibility.js";
|
|||||||
* for annotation icons. Include trailing slash.
|
* for annotation icons. Include trailing slash.
|
||||||
* @property {boolean} [useOnlyCssZoom] - Enables CSS only zooming. The default
|
* @property {boolean} [useOnlyCssZoom] - Enables CSS only zooming. The default
|
||||||
* value is `false`.
|
* value is `false`.
|
||||||
|
* @property {boolean} [isOffscreenCanvasSupported] - Allows to use an
|
||||||
|
* OffscreenCanvas if needed.
|
||||||
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
||||||
* total pixels, i.e. width * height. Use -1 for no limit. The default value
|
* total pixels, i.e. width * height. Use -1 for no limit. The default value
|
||||||
* is 4096 * 4096 (16 mega-pixels).
|
* is 4096 * 4096 (16 mega-pixels).
|
||||||
@ -128,6 +131,8 @@ class PDFPageView {
|
|||||||
options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
|
options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
|
||||||
this.imageResourcesPath = options.imageResourcesPath || "";
|
this.imageResourcesPath = options.imageResourcesPath || "";
|
||||||
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
||||||
|
this.isOffscreenCanvasSupported =
|
||||||
|
options.isOffscreenCanvasSupported ?? true;
|
||||||
this.maxCanvasPixels = options.maxCanvasPixels || MAX_CANVAS_PIXELS;
|
this.maxCanvasPixels = options.maxCanvasPixels || MAX_CANVAS_PIXELS;
|
||||||
this.pageColors = options.pageColors || null;
|
this.pageColors = options.pageColors || null;
|
||||||
|
|
||||||
@ -174,8 +179,8 @@ class PDFPageView {
|
|||||||
|
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.className = "page";
|
div.className = "page";
|
||||||
div.style.width = Math.floor(this.viewport.width) + "px";
|
div.style.width = Math.round(this.viewport.width) + "px";
|
||||||
div.style.height = Math.floor(this.viewport.height) + "px";
|
div.style.height = Math.round(this.viewport.height) + "px";
|
||||||
div.setAttribute("data-page-number", this.id);
|
div.setAttribute("data-page-number", this.id);
|
||||||
div.setAttribute("role", "region");
|
div.setAttribute("role", "region");
|
||||||
this.l10n.get("page_landmark", { page: this.id }).then(msg => {
|
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) {
|
async _buildXfaTextContentItems(textDivs) {
|
||||||
const text = await this.pdfPage.getTextContent();
|
const text = await this.pdfPage.getTextContent();
|
||||||
const items = [];
|
const items = [];
|
||||||
@ -320,17 +356,19 @@ class PDFPageView {
|
|||||||
keepAnnotationLayer = false,
|
keepAnnotationLayer = false,
|
||||||
keepAnnotationEditorLayer = false,
|
keepAnnotationEditorLayer = false,
|
||||||
keepXfaLayer = false,
|
keepXfaLayer = false,
|
||||||
|
keepTextLayer = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.cancelRendering({
|
this.cancelRendering({
|
||||||
keepAnnotationLayer,
|
keepAnnotationLayer,
|
||||||
keepAnnotationEditorLayer,
|
keepAnnotationEditorLayer,
|
||||||
keepXfaLayer,
|
keepXfaLayer,
|
||||||
|
keepTextLayer,
|
||||||
});
|
});
|
||||||
this.renderingState = RenderingStates.INITIAL;
|
this.renderingState = RenderingStates.INITIAL;
|
||||||
|
|
||||||
const div = this.div;
|
const div = this.div;
|
||||||
div.style.width = Math.floor(this.viewport.width) + "px";
|
div.style.width = Math.round(this.viewport.width) + "px";
|
||||||
div.style.height = Math.floor(this.viewport.height) + "px";
|
div.style.height = Math.round(this.viewport.height) + "px";
|
||||||
|
|
||||||
const childNodes = div.childNodes,
|
const childNodes = div.childNodes,
|
||||||
zoomLayerNode = (keepZoomLayer && this.zoomLayer) || null,
|
zoomLayerNode = (keepZoomLayer && this.zoomLayer) || null,
|
||||||
@ -338,7 +376,8 @@ class PDFPageView {
|
|||||||
(keepAnnotationLayer && this.annotationLayer?.div) || null,
|
(keepAnnotationLayer && this.annotationLayer?.div) || null,
|
||||||
annotationEditorLayerNode =
|
annotationEditorLayerNode =
|
||||||
(keepAnnotationEditorLayer && this.annotationEditorLayer?.div) || null,
|
(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--) {
|
for (let i = childNodes.length - 1; i >= 0; i--) {
|
||||||
const node = childNodes[i];
|
const node = childNodes[i];
|
||||||
switch (node) {
|
switch (node) {
|
||||||
@ -346,6 +385,7 @@ class PDFPageView {
|
|||||||
case annotationLayerNode:
|
case annotationLayerNode:
|
||||||
case annotationEditorLayerNode:
|
case annotationEditorLayerNode:
|
||||||
case xfaLayerNode:
|
case xfaLayerNode:
|
||||||
|
case textLayerNode:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
node.remove();
|
node.remove();
|
||||||
@ -369,6 +409,10 @@ class PDFPageView {
|
|||||||
this.xfaLayer.hide();
|
this.xfaLayer.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (textLayerNode) {
|
||||||
|
this.textLayer.hide();
|
||||||
|
}
|
||||||
|
|
||||||
if (!zoomLayerNode) {
|
if (!zoomLayerNode) {
|
||||||
if (this.canvas) {
|
if (this.canvas) {
|
||||||
this.paintedViewportMap.delete(this.canvas);
|
this.paintedViewportMap.delete(this.canvas);
|
||||||
@ -450,6 +494,7 @@ class PDFPageView {
|
|||||||
redrawAnnotationLayer: true,
|
redrawAnnotationLayer: true,
|
||||||
redrawAnnotationEditorLayer: true,
|
redrawAnnotationEditorLayer: true,
|
||||||
redrawXfaLayer: true,
|
redrawXfaLayer: true,
|
||||||
|
redrawTextLayer: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventBus.dispatch("pagerendered", {
|
this.eventBus.dispatch("pagerendered", {
|
||||||
@ -484,6 +529,7 @@ class PDFPageView {
|
|||||||
redrawAnnotationLayer: true,
|
redrawAnnotationLayer: true,
|
||||||
redrawAnnotationEditorLayer: true,
|
redrawAnnotationEditorLayer: true,
|
||||||
redrawXfaLayer: true,
|
redrawXfaLayer: true,
|
||||||
|
redrawTextLayer: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.eventBus.dispatch("pagerendered", {
|
this.eventBus.dispatch("pagerendered", {
|
||||||
@ -508,6 +554,7 @@ class PDFPageView {
|
|||||||
keepAnnotationLayer: true,
|
keepAnnotationLayer: true,
|
||||||
keepAnnotationEditorLayer: true,
|
keepAnnotationEditorLayer: true,
|
||||||
keepXfaLayer: true,
|
keepXfaLayer: true,
|
||||||
|
keepTextLayer: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,6 +566,7 @@ class PDFPageView {
|
|||||||
keepAnnotationLayer = false,
|
keepAnnotationLayer = false,
|
||||||
keepAnnotationEditorLayer = false,
|
keepAnnotationEditorLayer = false,
|
||||||
keepXfaLayer = false,
|
keepXfaLayer = false,
|
||||||
|
keepTextLayer = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
if (this.paintTask) {
|
if (this.paintTask) {
|
||||||
this.paintTask.cancel();
|
this.paintTask.cancel();
|
||||||
@ -526,7 +574,7 @@ class PDFPageView {
|
|||||||
}
|
}
|
||||||
this.resume = null;
|
this.resume = null;
|
||||||
|
|
||||||
if (this.textLayer) {
|
if (this.textLayer && (!keepTextLayer || !this.textLayer.div)) {
|
||||||
this.textLayer.cancel();
|
this.textLayer.cancel();
|
||||||
this.textLayer = null;
|
this.textLayer = null;
|
||||||
}
|
}
|
||||||
@ -561,6 +609,7 @@ class PDFPageView {
|
|||||||
redrawAnnotationLayer = false,
|
redrawAnnotationLayer = false,
|
||||||
redrawAnnotationEditorLayer = false,
|
redrawAnnotationEditorLayer = false,
|
||||||
redrawXfaLayer = false,
|
redrawXfaLayer = false,
|
||||||
|
redrawTextLayer = false,
|
||||||
}) {
|
}) {
|
||||||
// Scale target (canvas or svg), its wrapper and page container.
|
// Scale target (canvas or svg), its wrapper and page container.
|
||||||
const width = this.viewport.width;
|
const width = this.viewport.width;
|
||||||
@ -587,49 +636,6 @@ class PDFPageView {
|
|||||||
}
|
}
|
||||||
target.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX}, ${scaleY})`;
|
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) {
|
if (redrawAnnotationLayer && this.annotationLayer) {
|
||||||
this._renderAnnotationLayer();
|
this._renderAnnotationLayer();
|
||||||
}
|
}
|
||||||
@ -639,6 +645,9 @@ class PDFPageView {
|
|||||||
if (redrawXfaLayer && this.xfaLayer) {
|
if (redrawXfaLayer && this.xfaLayer) {
|
||||||
this._renderXfaLayer();
|
this._renderXfaLayer();
|
||||||
}
|
}
|
||||||
|
if (redrawTextLayer && this.textLayer) {
|
||||||
|
this.#renderTextLayer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get width() {
|
get width() {
|
||||||
@ -686,40 +695,33 @@ class PDFPageView {
|
|||||||
canvasWrapper.style.height = div.style.height;
|
canvasWrapper.style.height = div.style.height;
|
||||||
canvasWrapper.classList.add("canvasWrapper");
|
canvasWrapper.classList.add("canvasWrapper");
|
||||||
|
|
||||||
const lastDivBeforeTextDiv =
|
if (this.textLayer) {
|
||||||
this.annotationLayer?.div || this.annotationEditorLayer?.div;
|
this.textLayer.div.before(canvasWrapper);
|
||||||
|
|
||||||
if (lastDivBeforeTextDiv) {
|
|
||||||
// The annotation layer needs to stay on top.
|
|
||||||
lastDivBeforeTextDiv.before(canvasWrapper);
|
|
||||||
} else {
|
} else {
|
||||||
div.append(canvasWrapper);
|
const lastDivBeforeTextDiv =
|
||||||
}
|
this.annotationLayer?.div || this.annotationEditorLayer?.div;
|
||||||
|
|
||||||
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;
|
|
||||||
if (lastDivBeforeTextDiv) {
|
if (lastDivBeforeTextDiv) {
|
||||||
// The annotation layer needs to stay on top.
|
// The annotation layer needs to stay on top.
|
||||||
lastDivBeforeTextDiv.before(textLayerDiv);
|
lastDivBeforeTextDiv.before(canvasWrapper);
|
||||||
} else {
|
} else {
|
||||||
div.append(textLayerDiv);
|
div.append(canvasWrapper);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
textLayer = this.textLayerFactory.createTextLayerBuilder({
|
if (
|
||||||
textLayerDiv,
|
!this.textLayer &&
|
||||||
pageIndex: this.id - 1,
|
this.textLayerMode !== TextLayerMode.DISABLE &&
|
||||||
viewport: this.viewport,
|
this.textLayerFactory
|
||||||
eventBus: this.eventBus,
|
) {
|
||||||
|
this._accessibilityManager ||= new TextAccessibilityManager();
|
||||||
|
|
||||||
|
this.textLayer = this.textLayerFactory.createTextLayerBuilder({
|
||||||
highlighter: this.textHighlighter,
|
highlighter: this.textHighlighter,
|
||||||
accessibilityManager: this._accessibilityManager,
|
accessibilityManager: this._accessibilityManager,
|
||||||
|
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
||||||
});
|
});
|
||||||
|
canvasWrapper.after(this.textLayer.div);
|
||||||
}
|
}
|
||||||
this.textLayer = textLayer;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.#annotationMode !== AnnotationMode.DISABLE &&
|
this.#annotationMode !== AnnotationMode.DISABLE &&
|
||||||
@ -809,13 +811,7 @@ class PDFPageView {
|
|||||||
const resultPromise = paintTask.promise.then(
|
const resultPromise = paintTask.promise.then(
|
||||||
() => {
|
() => {
|
||||||
return finishPaintTask(null).then(() => {
|
return finishPaintTask(null).then(() => {
|
||||||
if (textLayer) {
|
this.#renderTextLayer();
|
||||||
const readableStream = pdfPage.streamTextContent({
|
|
||||||
includeMarkedContent: true,
|
|
||||||
});
|
|
||||||
textLayer.setTextContentStream(readableStream);
|
|
||||||
textLayer.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.annotationLayer) {
|
if (this.annotationLayer) {
|
||||||
this._renderAnnotationLayer().then(() => {
|
this._renderAnnotationLayer().then(() => {
|
||||||
@ -949,10 +945,12 @@ class PDFPageView {
|
|||||||
|
|
||||||
const sfx = approximateFraction(outputScale.sx);
|
const sfx = approximateFraction(outputScale.sx);
|
||||||
const sfy = approximateFraction(outputScale.sy);
|
const sfy = approximateFraction(outputScale.sy);
|
||||||
|
|
||||||
canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);
|
canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);
|
||||||
canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
|
canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
|
||||||
canvas.style.width = roundToDivide(viewport.width, sfx[1]) + "px";
|
const { style } = canvas;
|
||||||
canvas.style.height = roundToDivide(viewport.height, sfy[1]) + "px";
|
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.
|
// Add the viewport so it's known what it was originally drawn with.
|
||||||
this.paintedViewportMap.set(canvas, viewport);
|
this.paintedViewportMap.set(canvas, viewport);
|
||||||
|
@ -128,6 +128,8 @@ function isValidAnnotationEditorMode(mode) {
|
|||||||
* landscape pages upon printing. The default is `false`.
|
* landscape pages upon printing. The default is `false`.
|
||||||
* @property {boolean} [useOnlyCssZoom] - Enables CSS only zooming. The default
|
* @property {boolean} [useOnlyCssZoom] - Enables CSS only zooming. The default
|
||||||
* value is `false`.
|
* value is `false`.
|
||||||
|
* @property {boolean} [isOffscreenCanvasSupported] - Allows to use an
|
||||||
|
* OffscreenCanvas if needed.
|
||||||
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
||||||
* total pixels, i.e. width * height. Use -1 for no limit. The default value
|
* total pixels, i.e. width * height. Use -1 for no limit. The default value
|
||||||
* is 4096 * 4096 (16 mega-pixels).
|
* is 4096 * 4096 (16 mega-pixels).
|
||||||
@ -287,6 +289,8 @@ class PDFViewer {
|
|||||||
this.renderer = options.renderer || RendererType.CANVAS;
|
this.renderer = options.renderer || RendererType.CANVAS;
|
||||||
}
|
}
|
||||||
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
||||||
|
this.isOffscreenCanvasSupported =
|
||||||
|
options.isOffscreenCanvasSupported ?? true;
|
||||||
this.maxCanvasPixels = options.maxCanvasPixels;
|
this.maxCanvasPixels = options.maxCanvasPixels;
|
||||||
this.l10n = options.l10n || NullL10n;
|
this.l10n = options.l10n || NullL10n;
|
||||||
this.#enablePermissions = options.enablePermissions || false;
|
this.#enablePermissions = options.enablePermissions || false;
|
||||||
@ -775,6 +779,7 @@ class PDFViewer {
|
|||||||
? this.renderer
|
? this.renderer
|
||||||
: null,
|
: null,
|
||||||
useOnlyCssZoom: this.useOnlyCssZoom,
|
useOnlyCssZoom: this.useOnlyCssZoom,
|
||||||
|
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
||||||
maxCanvasPixels: this.maxCanvasPixels,
|
maxCanvasPixels: this.maxCanvasPixels,
|
||||||
pageColors: this.pageColors,
|
pageColors: this.pageColors,
|
||||||
l10n: this.l10n,
|
l10n: this.l10n,
|
||||||
@ -1635,12 +1640,9 @@ class PDFViewer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} CreateTextLayerBuilderParameters
|
* @typedef {Object} CreateTextLayerBuilderParameters
|
||||||
* @property {HTMLDivElement} textLayerDiv
|
|
||||||
* @property {number} pageIndex
|
|
||||||
* @property {PageViewport} viewport
|
|
||||||
* @property {EventBus} eventBus
|
|
||||||
* @property {TextHighlighter} highlighter
|
* @property {TextHighlighter} highlighter
|
||||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||||
|
* @property {boolean} [isOffscreenCanvasSupported]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1648,20 +1650,14 @@ class PDFViewer {
|
|||||||
* @returns {TextLayerBuilder}
|
* @returns {TextLayerBuilder}
|
||||||
*/
|
*/
|
||||||
createTextLayerBuilder({
|
createTextLayerBuilder({
|
||||||
textLayerDiv,
|
|
||||||
pageIndex,
|
|
||||||
viewport,
|
|
||||||
eventBus,
|
|
||||||
highlighter,
|
highlighter,
|
||||||
accessibilityManager = null,
|
accessibilityManager = null,
|
||||||
|
isOffscreenCanvasSupported = true,
|
||||||
}) {
|
}) {
|
||||||
return new TextLayerBuilder({
|
return new TextLayerBuilder({
|
||||||
textLayerDiv,
|
|
||||||
eventBus,
|
|
||||||
pageIndex,
|
|
||||||
viewport,
|
|
||||||
highlighter,
|
highlighter,
|
||||||
accessibilityManager,
|
accessibilityManager,
|
||||||
|
isOffscreenCanvasSupported,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ class TextHighlighter {
|
|||||||
);
|
);
|
||||||
this._onUpdateTextLayerMatches = null;
|
this._onUpdateTextLayerMatches = null;
|
||||||
}
|
}
|
||||||
|
this._updateMatches(/* reset = */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_convertMatches(matches, matchesLength) {
|
_convertMatches(matches, matchesLength) {
|
||||||
@ -264,8 +265,8 @@ class TextHighlighter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateMatches() {
|
_updateMatches(reset = false) {
|
||||||
if (!this.enabled) {
|
if (!this.enabled && !reset) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { findController, matches, pageIdx } = this;
|
const { findController, matches, pageIdx } = this;
|
||||||
@ -283,7 +284,7 @@ class TextHighlighter {
|
|||||||
clearedUntilDivIdx = match.end.divIdx + 1;
|
clearedUntilDivIdx = match.end.divIdx + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!findController?.highlightMatches) {
|
if (!findController?.highlightMatches || reset) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Convert the matches on the `findController` into the match format
|
// Convert the matches on the `findController` into the match format
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
text-size-adjust: none;
|
text-size-adjust: none;
|
||||||
forced-color-adjust: none;
|
forced-color-adjust: none;
|
||||||
|
transform-origin: 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textLayer span,
|
.textLayer span,
|
||||||
|
@ -15,22 +15,19 @@
|
|||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
|
||||||
/** @typedef {import("./text_highlighter").TextHighlighter} TextHighlighter */
|
/** @typedef {import("./text_highlighter").TextHighlighter} TextHighlighter */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
/** @typedef {import("./text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
||||||
|
|
||||||
import { renderTextLayer } from "pdfjs-lib";
|
import { renderTextLayer, updateTextLayer } from "pdfjs-lib";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} TextLayerBuilderOptions
|
* @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
|
* @property {TextHighlighter} highlighter - Optional object that will handle
|
||||||
* highlighting text from the find controller.
|
* highlighting text from the find controller.
|
||||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
* @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.
|
* contain text that matches the PDF text they are overlaying.
|
||||||
*/
|
*/
|
||||||
class TextLayerBuilder {
|
class TextLayerBuilder {
|
||||||
|
#scale = 0;
|
||||||
|
|
||||||
|
#rotation = 0;
|
||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
textLayerDiv,
|
|
||||||
eventBus,
|
|
||||||
pageIndex,
|
|
||||||
viewport,
|
|
||||||
highlighter = null,
|
highlighter = null,
|
||||||
accessibilityManager = null,
|
accessibilityManager = null,
|
||||||
|
isOffscreenCanvasSupported = true,
|
||||||
}) {
|
}) {
|
||||||
this.textLayerDiv = textLayerDiv;
|
|
||||||
this.eventBus = eventBus;
|
|
||||||
this.textContent = null;
|
this.textContent = null;
|
||||||
this.textContentItemsStr = [];
|
this.textContentItemsStr = [];
|
||||||
this.textContentStream = null;
|
this.textContentStream = null;
|
||||||
this.renderingDone = false;
|
this.renderingDone = false;
|
||||||
this.pageNumber = pageIndex + 1;
|
|
||||||
this.viewport = viewport;
|
|
||||||
this.textDivs = [];
|
this.textDivs = [];
|
||||||
|
this.textDivProperties = new WeakMap();
|
||||||
this.textLayerRenderTask = null;
|
this.textLayerRenderTask = null;
|
||||||
this.highlighter = highlighter;
|
this.highlighter = highlighter;
|
||||||
this.accessibilityManager = accessibilityManager;
|
this.accessibilityManager = accessibilityManager;
|
||||||
|
this.isOffscreenCanvasSupported = isOffscreenCanvasSupported;
|
||||||
|
|
||||||
this.#bindMouse();
|
this.div = document.createElement("div");
|
||||||
|
this.div.className = "textLayer";
|
||||||
}
|
}
|
||||||
|
|
||||||
#finishRendering() {
|
#finishRendering() {
|
||||||
@ -68,48 +65,80 @@ class TextLayerBuilder {
|
|||||||
|
|
||||||
const endOfContent = document.createElement("div");
|
const endOfContent = document.createElement("div");
|
||||||
endOfContent.className = "endOfContent";
|
endOfContent.className = "endOfContent";
|
||||||
this.textLayerDiv.append(endOfContent);
|
this.div.append(endOfContent);
|
||||||
|
|
||||||
this.eventBus.dispatch("textlayerrendered", {
|
this.#bindMouse();
|
||||||
source: this,
|
}
|
||||||
pageNumber: this.pageNumber,
|
|
||||||
numTextDivs: this.textDivs.length,
|
get numTextDivs() {
|
||||||
});
|
return this.textDivs.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the text layer.
|
* Renders the text layer.
|
||||||
*/
|
*/
|
||||||
render() {
|
async render(viewport) {
|
||||||
if (!(this.textContent || this.textContentStream) || this.renderingDone) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
this.cancel();
|
|
||||||
|
|
||||||
this.textDivs.length = 0;
|
this.cancel();
|
||||||
this.highlighter?.setTextMapping(this.textDivs, this.textContentItemsStr);
|
this.highlighter?.setTextMapping(this.textDivs, this.textContentItemsStr);
|
||||||
this.accessibilityManager?.setTextMapping(this.textDivs);
|
this.accessibilityManager?.setTextMapping(this.textDivs);
|
||||||
|
|
||||||
const textLayerFrag = document.createDocumentFragment();
|
|
||||||
this.textLayerRenderTask = renderTextLayer({
|
this.textLayerRenderTask = renderTextLayer({
|
||||||
textContent: this.textContent,
|
textContent: this.textContent,
|
||||||
textContentStream: this.textContentStream,
|
textContentStream: this.textContentStream,
|
||||||
container: textLayerFrag,
|
container: this.div,
|
||||||
viewport: this.viewport,
|
viewport,
|
||||||
textDivs: this.textDivs,
|
textDivs: this.textDivs,
|
||||||
|
textDivProperties: this.textDivProperties,
|
||||||
textContentItemsStr: this.textContentItemsStr,
|
textContentItemsStr: this.textContentItemsStr,
|
||||||
|
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
||||||
});
|
});
|
||||||
this.textLayerRenderTask.promise.then(
|
|
||||||
() => {
|
await this.textLayerRenderTask.promise;
|
||||||
this.textLayerDiv.append(textLayerFrag);
|
this.#finishRendering();
|
||||||
this.#finishRendering();
|
this.#scale = scale;
|
||||||
this.highlighter?.enable();
|
this.accessibilityManager?.enable();
|
||||||
this.accessibilityManager?.enable();
|
this.show();
|
||||||
},
|
}
|
||||||
function (reason) {
|
|
||||||
// Cancelled or failed to render text layer; skipping errors.
|
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.highlighter?.disable();
|
||||||
this.accessibilityManager?.disable();
|
this.accessibilityManager?.disable();
|
||||||
|
this.textContentItemsStr.length = 0;
|
||||||
|
this.textDivs.length = 0;
|
||||||
|
this.textDivProperties = new WeakMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
setTextContentStream(readableStream) {
|
setTextContentStream(readableStream) {
|
||||||
@ -140,7 +172,7 @@ class TextLayerBuilder {
|
|||||||
* dragged up or down.
|
* dragged up or down.
|
||||||
*/
|
*/
|
||||||
#bindMouse() {
|
#bindMouse() {
|
||||||
const div = this.textLayerDiv;
|
const { div } = this;
|
||||||
|
|
||||||
div.addEventListener("mousedown", evt => {
|
div.addEventListener("mousedown", evt => {
|
||||||
const end = div.querySelector(".endOfContent");
|
const end = div.querySelector(".endOfContent");
|
||||||
|
Loading…
Reference in New Issue
Block a user