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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@
// 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("../src/display/api").TextContent} TextContent */
/** @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 */
@ -36,18 +37,18 @@ import { renderTextLayer, updateTextLayer } 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 {
#rotation = 0;
#scale = 0; #scale = 0;
#rotation = 0; #textContentSource = null;
constructor({ constructor({
highlighter = null, highlighter = null,
accessibilityManager = null, accessibilityManager = null,
isOffscreenCanvasSupported = true, isOffscreenCanvasSupported = true,
}) { }) {
this.textContent = null;
this.textContentItemsStr = []; this.textContentItemsStr = [];
this.textContentStream = null;
this.renderingDone = false; this.renderingDone = false;
this.textDivs = []; this.textDivs = [];
this.textDivProperties = new WeakMap(); this.textDivProperties = new WeakMap();
@ -76,12 +77,11 @@ class TextLayerBuilder {
/** /**
* Renders the text layer. * Renders the text layer.
* @param {PageViewport} viewport
*/ */
async render(viewport) { async render(viewport) {
if (!(this.textContent || this.textContentStream)) { if (!this.#textContentSource) {
throw new Error( throw new Error('No "textContentSource" parameter specified.');
`Neither "textContent" nor "textContentStream" specified.`
);
} }
const scale = viewport.scale * (globalThis.devicePixelRatio || 1); const scale = viewport.scale * (globalThis.devicePixelRatio || 1);
@ -112,8 +112,7 @@ class TextLayerBuilder {
this.accessibilityManager?.setTextMapping(this.textDivs); this.accessibilityManager?.setTextMapping(this.textDivs);
this.textLayerRenderTask = renderTextLayer({ this.textLayerRenderTask = renderTextLayer({
textContent: this.textContent, textContentSource: this.#textContentSource,
textContentStream: this.textContentStream,
container: this.div, container: this.div,
viewport, viewport,
textDivs: this.textDivs, textDivs: this.textDivs,
@ -156,14 +155,12 @@ class TextLayerBuilder {
this.textDivProperties = new WeakMap(); this.textDivProperties = new WeakMap();
} }
setTextContentStream(readableStream) { /**
* @param {ReadableStream | TextContent} source
*/
setTextContentSource(source) {
this.cancel(); this.cancel();
this.textContentStream = readableStream; this.#textContentSource = source;
}
setTextContent(textContent) {
this.cancel();
this.textContent = textContent;
} }
/** /**