Skip to content

Commit 2a93ade

Browse files
authored
Merge pull request #20231 from calixteman/xfa_render_richtext
Add a new function renderRichText to be used in the annotation layer
2 parents 7b87c22 + 35c9098 commit 2a93ade

6 files changed

Lines changed: 108 additions & 36 deletions

File tree

src/display/annotation_layer.js

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ import {
4141
applyOpacity,
4242
changeLightness,
4343
PDFDateString,
44+
renderRichText,
4445
setLayerDimensions,
4546
} from "./display_utils.js";
4647
import { AnnotationStorage } from "./annotation_storage.js";
4748
import { ColorConverters } from "../shared/scripting_utils.js";
4849
import { DOMSVGFactory } from "./svg_factory.js";
49-
import { XfaLayer } from "./xfa_layer.js";
5050

5151
const DEFAULT_FONT_SIZE = 9;
5252
const GetElementsByNameSet = new WeakSet();
@@ -2491,18 +2491,15 @@ class PopupElement {
24912491
header.append(modificationDate);
24922492
}
24932493

2494-
const html = this.#html;
2495-
if (html) {
2496-
XfaLayer.render({
2497-
xfaHtml: html,
2498-
intent: "richText",
2499-
div: popup,
2500-
});
2501-
popup.lastChild.classList.add("richText", "popupContent");
2502-
} else {
2503-
const contents = this._formatContents(this.#contentsObj);
2504-
popup.append(contents);
2505-
}
2494+
renderRichText(
2495+
{
2496+
html: this.#html || this.#contentsObj.str,
2497+
dir: this.#contentsObj.dir,
2498+
className: "popupContent",
2499+
},
2500+
popup
2501+
);
2502+
25062503
this.#container.append(popup);
25072504
}
25082505

@@ -2561,29 +2558,6 @@ class PopupElement {
25612558
return popupContent;
25622559
}
25632560

2564-
/**
2565-
* Format the contents of the popup by adding newlines where necessary.
2566-
*
2567-
* @private
2568-
* @param {Object<string, string>} contentsObj
2569-
* @memberof PopupElement
2570-
* @returns {HTMLParagraphElement}
2571-
*/
2572-
_formatContents({ str, dir }) {
2573-
const p = document.createElement("p");
2574-
p.classList.add("popupContent");
2575-
p.dir = dir;
2576-
const lines = str.split(/(?:\r\n?|\n)/);
2577-
for (let i = 0, ii = lines.length; i < ii; ++i) {
2578-
const line = lines[i];
2579-
p.append(document.createTextNode(line));
2580-
if (i < ii - 1) {
2581-
p.append(document.createElement("br"));
2582-
}
2583-
}
2584-
return p;
2585-
}
2586-
25872561
#keyDown(event) {
25882562
if (event.altKey || event.shiftKey || event.ctrlKey || event.metaKey) {
25892563
return;

src/display/display_utils.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
Util,
2121
warn,
2222
} from "../shared/util.js";
23+
import { XfaLayer } from "./xfa_layer.js";
2324

2425
const SVG_NS = "http://www.w3.org/2000/svg";
2526

@@ -829,6 +830,31 @@ function applyOpacity(r, g, b, opacity) {
829830
return [r, g, b];
830831
}
831832

833+
function renderRichText({ html, dir, className }, container) {
834+
const fragment = document.createDocumentFragment();
835+
if (typeof html === "string") {
836+
const p = document.createElement("p");
837+
p.dir = dir || "auto";
838+
const lines = html.split(/(?:\r\n?|\n)/);
839+
for (let i = 0, ii = lines.length; i < ii; ++i) {
840+
const line = lines[i];
841+
p.append(document.createTextNode(line));
842+
if (i < ii - 1) {
843+
p.append(document.createElement("br"));
844+
}
845+
}
846+
fragment.append(p);
847+
} else {
848+
XfaLayer.render({
849+
xfaHtml: html,
850+
div: fragment,
851+
intent: "richText",
852+
});
853+
}
854+
fragment.firstChild.classList.add("richText", className);
855+
container.append(fragment);
856+
}
857+
832858
export {
833859
applyOpacity,
834860
changeLightness,
@@ -851,6 +877,7 @@ export {
851877
PDFDateString,
852878
PixelsPerInch,
853879
RenderingCancelledException,
880+
renderRichText,
854881
setLayerDimensions,
855882
StatTimer,
856883
stopEvent,

src/pdf.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
PDFDateString,
6060
PixelsPerInch,
6161
RenderingCancelledException,
62+
renderRichText,
6263
setLayerDimensions,
6364
stopEvent,
6465
SupportedImageMimeTypes,
@@ -132,6 +133,7 @@ globalThis.pdfjsLib = {
132133
PermissionFlag,
133134
PixelsPerInch,
134135
RenderingCancelledException,
136+
renderRichText,
135137
ResponseException,
136138
setLayerDimensions,
137139
shadow,
@@ -189,6 +191,7 @@ export {
189191
PermissionFlag,
190192
PixelsPerInch,
191193
RenderingCancelledException,
194+
renderRichText,
192195
ResponseException,
193196
setLayerDimensions,
194197
shadow,

test/unit/display_utils_spec.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
getRGB,
2222
isValidFetchUrl,
2323
PDFDateString,
24+
renderRichText,
2425
} from "../../src/display/display_utils.js";
2526
import { isNodeJS, toBase64Util } from "../../src/shared/util.js";
2627

@@ -342,4 +343,67 @@ describe("display_utils", function () {
342343
expect(applyOpacity(123, 45, 67, ctx.globalAlpha)).toEqual([r, g, b]);
343344
});
344345
});
346+
347+
describe("renderRichText", function () {
348+
// Unlike other tests we cannot simply compare the HTML-strings since
349+
// Chrome and Firefox produce different results. Instead we compare sets
350+
// containing the individual parts of the HTML-strings.
351+
const splitParts = s => new Set(s.split(/[<>/ ]+/).filter(x => x));
352+
353+
it("should render plain text", function () {
354+
if (isNodeJS) {
355+
pending("DOM is not supported in Node.js.");
356+
}
357+
const container = document.createElement("div");
358+
renderRichText(
359+
{
360+
html: "Hello world!\nThis is a test.",
361+
dir: "ltr",
362+
className: "foo",
363+
},
364+
container
365+
);
366+
expect(splitParts(container.innerHTML)).toEqual(
367+
splitParts(
368+
'<p dir="ltr" class="richText foo">Hello world!<br>This is a test.</p>'
369+
)
370+
);
371+
});
372+
373+
it("should render XFA rich text", function () {
374+
if (isNodeJS) {
375+
pending("DOM is not supported in Node.js.");
376+
}
377+
const container = document.createElement("div");
378+
const xfaHtml = {
379+
name: "div",
380+
attributes: { style: { color: "red" } },
381+
children: [
382+
{
383+
name: "p",
384+
attributes: { style: { fontSize: "20px" } },
385+
children: [
386+
{
387+
name: "span",
388+
attributes: { style: { fontWeight: "bold" } },
389+
value: "Hello",
390+
},
391+
{ name: "#text", value: " world!" },
392+
],
393+
},
394+
],
395+
};
396+
renderRichText(
397+
{ html: xfaHtml, dir: "ltr", className: "foo" },
398+
container
399+
);
400+
expect(splitParts(container.innerHTML)).toEqual(
401+
splitParts(
402+
'<div style="color: red;" class="richText foo">' +
403+
'<p style="font-size: 20px;">' +
404+
'<span style="font-weight: bold;">Hello</span> world!</p></div>'
405+
)
406+
);
407+
});
408+
});
345409
});

test/unit/pdf_spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
PDFDateString,
5151
PixelsPerInch,
5252
RenderingCancelledException,
53+
renderRichText,
5354
setLayerDimensions,
5455
stopEvent,
5556
SupportedImageMimeTypes,
@@ -116,6 +117,7 @@ const expectedAPI = Object.freeze({
116117
PermissionFlag,
117118
PixelsPerInch,
118119
RenderingCancelledException,
120+
renderRichText,
119121
ResponseException,
120122
setLayerDimensions,
121123
shadow,

web/pdfjs.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const {
5555
PermissionFlag,
5656
PixelsPerInch,
5757
RenderingCancelledException,
58+
renderRichText,
5859
ResponseException,
5960
setLayerDimensions,
6061
shadow,
@@ -112,6 +113,7 @@ export {
112113
PermissionFlag,
113114
PixelsPerInch,
114115
RenderingCancelledException,
116+
renderRichText,
115117
ResponseException,
116118
setLayerDimensions,
117119
shadow,

0 commit comments

Comments
 (0)