Merge pull request #20231 from calixteman/xfa_render_richtext

Add a new function renderRichText to be used in the annotation layer
This commit is contained in:
calixteman 2025-09-05 08:36:28 +02:00 committed by GitHub
commit 2a93ade197
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 108 additions and 36 deletions

View File

@ -41,12 +41,12 @@ import {
applyOpacity,
changeLightness,
PDFDateString,
renderRichText,
setLayerDimensions,
} from "./display_utils.js";
import { AnnotationStorage } from "./annotation_storage.js";
import { ColorConverters } from "../shared/scripting_utils.js";
import { DOMSVGFactory } from "./svg_factory.js";
import { XfaLayer } from "./xfa_layer.js";
const DEFAULT_FONT_SIZE = 9;
const GetElementsByNameSet = new WeakSet();
@ -2491,18 +2491,15 @@ class PopupElement {
header.append(modificationDate);
}
const html = this.#html;
if (html) {
XfaLayer.render({
xfaHtml: html,
intent: "richText",
div: popup,
});
popup.lastChild.classList.add("richText", "popupContent");
} else {
const contents = this._formatContents(this.#contentsObj);
popup.append(contents);
}
renderRichText(
{
html: this.#html || this.#contentsObj.str,
dir: this.#contentsObj.dir,
className: "popupContent",
},
popup
);
this.#container.append(popup);
}
@ -2561,29 +2558,6 @@ class PopupElement {
return popupContent;
}
/**
* Format the contents of the popup by adding newlines where necessary.
*
* @private
* @param {Object<string, string>} contentsObj
* @memberof PopupElement
* @returns {HTMLParagraphElement}
*/
_formatContents({ str, dir }) {
const p = document.createElement("p");
p.classList.add("popupContent");
p.dir = dir;
const lines = str.split(/(?:\r\n?|\n)/);
for (let i = 0, ii = lines.length; i < ii; ++i) {
const line = lines[i];
p.append(document.createTextNode(line));
if (i < ii - 1) {
p.append(document.createElement("br"));
}
}
return p;
}
#keyDown(event) {
if (event.altKey || event.shiftKey || event.ctrlKey || event.metaKey) {
return;

View File

@ -20,6 +20,7 @@ import {
Util,
warn,
} from "../shared/util.js";
import { XfaLayer } from "./xfa_layer.js";
const SVG_NS = "http://www.w3.org/2000/svg";
@ -829,6 +830,31 @@ function applyOpacity(r, g, b, opacity) {
return [r, g, b];
}
function renderRichText({ html, dir, className }, container) {
const fragment = document.createDocumentFragment();
if (typeof html === "string") {
const p = document.createElement("p");
p.dir = dir || "auto";
const lines = html.split(/(?:\r\n?|\n)/);
for (let i = 0, ii = lines.length; i < ii; ++i) {
const line = lines[i];
p.append(document.createTextNode(line));
if (i < ii - 1) {
p.append(document.createElement("br"));
}
}
fragment.append(p);
} else {
XfaLayer.render({
xfaHtml: html,
div: fragment,
intent: "richText",
});
}
fragment.firstChild.classList.add("richText", className);
container.append(fragment);
}
export {
applyOpacity,
changeLightness,
@ -851,6 +877,7 @@ export {
PDFDateString,
PixelsPerInch,
RenderingCancelledException,
renderRichText,
setLayerDimensions,
StatTimer,
stopEvent,

View File

@ -59,6 +59,7 @@ import {
PDFDateString,
PixelsPerInch,
RenderingCancelledException,
renderRichText,
setLayerDimensions,
stopEvent,
SupportedImageMimeTypes,
@ -132,6 +133,7 @@ globalThis.pdfjsLib = {
PermissionFlag,
PixelsPerInch,
RenderingCancelledException,
renderRichText,
ResponseException,
setLayerDimensions,
shadow,
@ -189,6 +191,7 @@ export {
PermissionFlag,
PixelsPerInch,
RenderingCancelledException,
renderRichText,
ResponseException,
setLayerDimensions,
shadow,

View File

@ -21,6 +21,7 @@ import {
getRGB,
isValidFetchUrl,
PDFDateString,
renderRichText,
} from "../../src/display/display_utils.js";
import { isNodeJS, toBase64Util } from "../../src/shared/util.js";
@ -342,4 +343,67 @@ describe("display_utils", function () {
expect(applyOpacity(123, 45, 67, ctx.globalAlpha)).toEqual([r, g, b]);
});
});
describe("renderRichText", function () {
// Unlike other tests we cannot simply compare the HTML-strings since
// Chrome and Firefox produce different results. Instead we compare sets
// containing the individual parts of the HTML-strings.
const splitParts = s => new Set(s.split(/[<>/ ]+/).filter(x => x));
it("should render plain text", function () {
if (isNodeJS) {
pending("DOM is not supported in Node.js.");
}
const container = document.createElement("div");
renderRichText(
{
html: "Hello world!\nThis is a test.",
dir: "ltr",
className: "foo",
},
container
);
expect(splitParts(container.innerHTML)).toEqual(
splitParts(
'<p dir="ltr" class="richText foo">Hello world!<br>This is a test.</p>'
)
);
});
it("should render XFA rich text", function () {
if (isNodeJS) {
pending("DOM is not supported in Node.js.");
}
const container = document.createElement("div");
const xfaHtml = {
name: "div",
attributes: { style: { color: "red" } },
children: [
{
name: "p",
attributes: { style: { fontSize: "20px" } },
children: [
{
name: "span",
attributes: { style: { fontWeight: "bold" } },
value: "Hello",
},
{ name: "#text", value: " world!" },
],
},
],
};
renderRichText(
{ html: xfaHtml, dir: "ltr", className: "foo" },
container
);
expect(splitParts(container.innerHTML)).toEqual(
splitParts(
'<div style="color: red;" class="richText foo">' +
'<p style="font-size: 20px;">' +
'<span style="font-weight: bold;">Hello</span> world!</p></div>'
)
);
});
});
});

View File

@ -50,6 +50,7 @@ import {
PDFDateString,
PixelsPerInch,
RenderingCancelledException,
renderRichText,
setLayerDimensions,
stopEvent,
SupportedImageMimeTypes,
@ -116,6 +117,7 @@ const expectedAPI = Object.freeze({
PermissionFlag,
PixelsPerInch,
RenderingCancelledException,
renderRichText,
ResponseException,
setLayerDimensions,
shadow,

View File

@ -55,6 +55,7 @@ const {
PermissionFlag,
PixelsPerInch,
RenderingCancelledException,
renderRichText,
ResponseException,
setLayerDimensions,
shadow,
@ -112,6 +113,7 @@ export {
PermissionFlag,
PixelsPerInch,
RenderingCancelledException,
renderRichText,
ResponseException,
setLayerDimensions,
shadow,