[api-minor] Combine the textContent/textContentStream parameters

Rather than handling these parameters separately, which is a left-over from back when streaming of textContent was originally added, we can simply pass either data directly to the `TextLayer` and let it handle things accordingly.

Also, improves a few JSDoc comments and `typedef`-imports.
This commit is contained in:
Jonas Jenwald 2022-12-04 17:42:24 +01:00
parent 67e1c37e0f
commit fe8fded23b
5 changed files with 58 additions and 62 deletions

View File

@ -13,25 +13,28 @@
* limitations under the License.
*/
/** @typedef {import("./display_utils").PageViewport} PageViewport */
/** @typedef {import("./api").TextContent} TextContent */
import {
AbortException,
createPromiseCapability,
FeatureTest,
Util,
} from "../shared/util.js";
import { deprecated } from "./display_utils.js";
/**
* Text layer render parameters.
*
* @typedef {Object} TextLayerRenderParameters
* @property {import("./api").TextContent} [textContent] - Text content to
* render (the object is returned by the page's `getTextContent` method).
* @property {ReadableStream} [textContentStream] - Text content stream to
* render (the stream is returned by the page's `streamTextContent` method).
* @property {ReadableStream | TextContent} textContentSource - Text content to
* render, i.e. the value returned by the page's `streamTextContent` or
* `getTextContent` method.
* @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.
* runs.
* @property {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.
@ -49,9 +52,9 @@ import {
*
* @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.
* runs.
* @property {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.
@ -61,7 +64,7 @@ import {
* 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.
* rescaled.
*/
const MAX_TEXT_DIVS_TO_RENDER = 100000;
@ -236,7 +239,7 @@ function appendText(task, geom, styles) {
textDivProperties.canvasWidth = style.vertical ? geom.height : geom.width;
}
task._textDivProperties.set(textDiv, textDivProperties);
if (task._textContentStream) {
if (task._isReadableStream) {
task._layoutText(textDiv);
}
}
@ -286,7 +289,7 @@ function render(task) {
return;
}
if (!task._textContentStream) {
if (!task._isReadableStream) {
for (const textDiv of textDivs) {
task._layoutText(textDiv);
}
@ -298,8 +301,7 @@ function render(task) {
class TextLayerRenderTask {
constructor({
textContent,
textContentStream,
textContentSource,
container,
viewport,
textDivs,
@ -307,8 +309,8 @@ class TextLayerRenderTask {
textContentItemsStr,
isOffscreenCanvasSupported,
}) {
this._textContent = textContent;
this._textContentStream = textContentStream;
this._textContentSource = textContentSource;
this._isReadableStream = textContentSource instanceof ReadableStream;
this._container = this._rootContainer = container;
this._textDivs = textDivs || [];
this._textContentItemsStr = textContentItemsStr || [];
@ -421,14 +423,7 @@ class TextLayerRenderTask {
const capability = createPromiseCapability();
let styleCache = Object.create(null);
// The temporary canvas is used to measure text length in the DOM.
if (this._textContent) {
const textItems = this._textContent.items;
const textStyles = this._textContent.styles;
this._processItems(textItems, textStyles);
capability.resolve();
} else if (this._textContentStream) {
if (this._isReadableStream) {
const pump = () => {
this._reader.read().then(({ value, done }) => {
if (done) {
@ -442,12 +437,14 @@ class TextLayerRenderTask {
}, capability.reject);
};
this._reader = this._textContentStream.getReader();
this._reader = this._textContentSource.getReader();
pump();
} else if (this._textContentSource) {
const { items, styles } = this._textContentSource;
this._processItems(items, styles);
capability.resolve();
} else {
throw new Error(
'Neither "textContent" nor "textContentStream" parameters specified.'
);
throw new Error('No "textContentSource" parameter specified.');
}
capability.promise.then(() => {
@ -458,27 +455,29 @@ class TextLayerRenderTask {
}
/**
* @param {TextLayerRenderParameters} renderParameters
* @param {TextLayerRenderParameters} params
* @returns {TextLayerRenderTask}
*/
function renderTextLayer(renderParameters) {
const task = new TextLayerRenderTask({
textContent: renderParameters.textContent,
textContentStream: renderParameters.textContentStream,
container: renderParameters.container,
viewport: renderParameters.viewport,
textDivs: renderParameters.textDivs,
textContentItemsStr: renderParameters.textContentItemsStr,
textDivProperties: renderParameters.textDivProperties,
isOffscreenCanvasSupported: renderParameters.isOffscreenCanvasSupported,
});
function renderTextLayer(params) {
if (
(typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) &&
!params.textContentSource &&
(params.textContent || params.textContentStream)
) {
deprecated(
"The TextLayerRender `textContent`/`textContentStream` parameters " +
"will be removed in the future, please use `textContentSource` instead."
);
params.textContentSource = params.textContent || params.textContentStream;
}
const task = new TextLayerRenderTask(params);
task._render();
return task;
}
/**
* @param {TextLayerUpdateParameters} renderParameters
* @returns {TextLayerRenderTask}
* @param {TextLayerUpdateParameters} params
* @returns {undefined}
*/
function updateTextLayer({
container,

View File

@ -263,7 +263,7 @@ class Rasterize {
// Rendering text layer as HTML.
const task = renderTextLayer({
textContent,
textContentSource: textContent,
container: div,
viewport,
});

View File

@ -33,7 +33,7 @@ describe("textLayer", function () {
const textContentItemsStr = [];
const textLayerRenderTask = renderTextLayer({
textContentStream: page.streamTextContent(),
textContentSource: page.streamTextContent(),
container: document.createElement("div"),
viewport: page.getViewport(),
textContentItemsStr,

View File

@ -301,7 +301,7 @@ class PDFPageView {
const readableStream = pdfPage.streamTextContent({
includeMarkedContent: true,
});
textLayer.setTextContentStream(readableStream);
textLayer.setTextContentSource(readableStream);
}
await textLayer.render(viewport);
} catch (ex) {

View File

@ -15,6 +15,7 @@
// eslint-disable-next-line max-len
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
/** @typedef {import("../src/display/api").TextContent} TextContent */
/** @typedef {import("./text_highlighter").TextHighlighter} TextHighlighter */
// eslint-disable-next-line max-len
/** @typedef {import("./text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
@ -36,18 +37,18 @@ import { renderTextLayer, updateTextLayer } from "pdfjs-lib";
* contain text that matches the PDF text they are overlaying.
*/
class TextLayerBuilder {
#rotation = 0;
#scale = 0;
#rotation = 0;
#textContentSource = null;
constructor({
highlighter = null,
accessibilityManager = null,
isOffscreenCanvasSupported = true,
}) {
this.textContent = null;
this.textContentItemsStr = [];
this.textContentStream = null;
this.renderingDone = false;
this.textDivs = [];
this.textDivProperties = new WeakMap();
@ -76,12 +77,11 @@ class TextLayerBuilder {
/**
* Renders the text layer.
* @param {PageViewport} viewport
*/
async render(viewport) {
if (!(this.textContent || this.textContentStream)) {
throw new Error(
`Neither "textContent" nor "textContentStream" specified.`
);
if (!this.#textContentSource) {
throw new Error('No "textContentSource" parameter specified.');
}
const scale = viewport.scale * (globalThis.devicePixelRatio || 1);
@ -112,8 +112,7 @@ class TextLayerBuilder {
this.accessibilityManager?.setTextMapping(this.textDivs);
this.textLayerRenderTask = renderTextLayer({
textContent: this.textContent,
textContentStream: this.textContentStream,
textContentSource: this.#textContentSource,
container: this.div,
viewport,
textDivs: this.textDivs,
@ -156,14 +155,12 @@ class TextLayerBuilder {
this.textDivProperties = new WeakMap();
}
setTextContentStream(readableStream) {
/**
* @param {ReadableStream | TextContent} source
*/
setTextContentSource(source) {
this.cancel();
this.textContentStream = readableStream;
}
setTextContent(textContent) {
this.cancel();
this.textContent = textContent;
this.#textContentSource = source;
}
/**