@richscripts/richtexteditor 2.14.0 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -10
- package/angular/build-manifest.json +23 -0
- package/angular/index.d.ts +71 -76
- package/angular/index.mjs +2 -0
- package/angular/package.json +4 -0
- package/headless/index.d.ts +3 -1
- package/headless/index.js +72 -16
- package/index.d.ts +33 -54
- package/integrations/initialization-scope.js +27 -0
- package/integrations/native-mount.js +30 -0
- package/integrations/shared.d.ts +23 -1
- package/integrations/shared.js +58 -22
- package/package.json +25 -3
- package/react/index.js +118 -37
- package/react/index.mjs +29 -0
- package/richtexteditor/all_plugins.js +7113 -1111
- package/richtexteditor/all_plugins.min.js +62 -59
- package/richtexteditor/lang/emoji/LICENSE-CLDR.txt +41 -0
- package/richtexteditor/lang/emoji/rte-emoji-de.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-es.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-fr.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-it.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-ja.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-ko.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-nl.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-pt-pt.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-pt.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-ru.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-zh-Hans.js +3 -0
- package/richtexteditor/lang/emoji/rte-emoji-zh-Hant.js +3 -0
- package/richtexteditor/lang/rte-lang-af.js +1 -0
- package/richtexteditor/lang/rte-lang-ar.js +1 -0
- package/richtexteditor/lang/rte-lang-bg.js +1 -0
- package/richtexteditor/lang/rte-lang-bn.js +1 -0
- package/richtexteditor/lang/rte-lang-bs.js +1 -0
- package/richtexteditor/lang/rte-lang-ca.js +1 -0
- package/richtexteditor/lang/rte-lang-cs.js +1 -0
- package/richtexteditor/lang/rte-lang-cy.js +1 -0
- package/richtexteditor/lang/rte-lang-da.js +1 -0
- package/richtexteditor/lang/rte-lang-de.js +1017 -2
- package/richtexteditor/lang/rte-lang-el.js +1 -0
- package/richtexteditor/lang/rte-lang-en.js +1 -0
- package/richtexteditor/lang/rte-lang-es.js +1023 -2
- package/richtexteditor/lang/rte-lang-et.js +1 -0
- package/richtexteditor/lang/rte-lang-fa.js +1 -0
- package/richtexteditor/lang/rte-lang-fi.js +1 -0
- package/richtexteditor/lang/rte-lang-fil.js +1 -0
- package/richtexteditor/lang/rte-lang-fj.js +1 -0
- package/richtexteditor/lang/rte-lang-fr.js +1023 -2
- package/richtexteditor/lang/rte-lang-ga.js +1 -0
- package/richtexteditor/lang/rte-lang-gu.js +1 -0
- package/richtexteditor/lang/rte-lang-he.js +1 -0
- package/richtexteditor/lang/rte-lang-hi.js +1 -0
- package/richtexteditor/lang/rte-lang-hr.js +1 -0
- package/richtexteditor/lang/rte-lang-ht.js +1 -0
- package/richtexteditor/lang/rte-lang-hu.js +1 -0
- package/richtexteditor/lang/rte-lang-id.js +1 -0
- package/richtexteditor/lang/rte-lang-is.js +1 -0
- package/richtexteditor/lang/rte-lang-it.js +1023 -2
- package/richtexteditor/lang/rte-lang-ja.js +1023 -2
- package/richtexteditor/lang/rte-lang-kn.js +1 -0
- package/richtexteditor/lang/rte-lang-ko.js +1023 -2
- package/richtexteditor/lang/rte-lang-lt.js +1 -0
- package/richtexteditor/lang/rte-lang-lv.js +1 -0
- package/richtexteditor/lang/rte-lang-mg.js +1 -0
- package/richtexteditor/lang/rte-lang-mi.js +1 -0
- package/richtexteditor/lang/rte-lang-ml.js +1 -0
- package/richtexteditor/lang/rte-lang-mr.js +1 -0
- package/richtexteditor/lang/rte-lang-ms.js +1 -0
- package/richtexteditor/lang/rte-lang-mt.js +1 -0
- package/richtexteditor/lang/rte-lang-mww.js +1 -0
- package/richtexteditor/lang/rte-lang-nb.js +1 -0
- package/richtexteditor/lang/rte-lang-nl.js +1023 -2
- package/richtexteditor/lang/rte-lang-otq.js +1 -0
- package/richtexteditor/lang/rte-lang-pa.js +1 -0
- package/richtexteditor/lang/rte-lang-pl.js +1 -0
- package/richtexteditor/lang/rte-lang-pt-pt.js +1023 -2
- package/richtexteditor/lang/rte-lang-pt.js +1023 -2
- package/richtexteditor/lang/rte-lang-ro.js +1 -0
- package/richtexteditor/lang/rte-lang-ru.js +1023 -2
- package/richtexteditor/lang/rte-lang-sk.js +1 -0
- package/richtexteditor/lang/rte-lang-sl.js +1 -0
- package/richtexteditor/lang/rte-lang-sm.js +1 -0
- package/richtexteditor/lang/rte-lang-sr-Cyrl.js +1 -0
- package/richtexteditor/lang/rte-lang-sr-Latn.js +1 -0
- package/richtexteditor/lang/rte-lang-sv.js +1 -0
- package/richtexteditor/lang/rte-lang-sw.js +1 -0
- package/richtexteditor/lang/rte-lang-ta.js +1 -0
- package/richtexteditor/lang/rte-lang-te.js +1 -0
- package/richtexteditor/lang/rte-lang-th.js +1 -0
- package/richtexteditor/lang/rte-lang-tlh-Latn.js +1 -0
- package/richtexteditor/lang/rte-lang-tlh-Piqd.js +1 -0
- package/richtexteditor/lang/rte-lang-to.js +1 -0
- package/richtexteditor/lang/rte-lang-tr.js +1 -0
- package/richtexteditor/lang/rte-lang-ty.js +1 -0
- package/richtexteditor/lang/rte-lang-uk.js +1 -0
- package/richtexteditor/lang/rte-lang-ur.js +1 -0
- package/richtexteditor/lang/rte-lang-vi.js +1 -0
- package/richtexteditor/lang/rte-lang-yua.js +1 -0
- package/richtexteditor/lang/rte-lang-zh-Hans.js +1017 -2
- package/richtexteditor/lang/rte-lang-zh-Hant.js +1017 -2
- package/richtexteditor/plugins/a11yenhance.js +2 -1
- package/richtexteditor/plugins/a11yenhance.min.js +1 -1
- package/richtexteditor/plugins/accessibilitychecker.js +252 -60
- package/richtexteditor/plugins/accessibilitychecker.min.js +1 -1
- package/richtexteditor/plugins/aitoolkit.js +966 -303
- package/richtexteditor/plugins/aitoolkit.min.js +7 -7
- package/richtexteditor/plugins/all_plugins.js +7113 -1111
- package/richtexteditor/plugins/all_plugins.min.js +62 -59
- package/richtexteditor/plugins/autocorrect.js +66 -31
- package/richtexteditor/plugins/autocorrect.min.js +1 -1
- package/richtexteditor/plugins/autoembed.js +17 -0
- package/richtexteditor/plugins/autoembed.min.js +1 -1
- package/richtexteditor/plugins/blocktypes.js +18 -9
- package/richtexteditor/plugins/blocktypes.min.js +1 -1
- package/richtexteditor/plugins/bookmarkcard.js +12 -5
- package/richtexteditor/plugins/bookmarkcard.min.js +2 -2
- package/richtexteditor/plugins/changecase.js +72 -19
- package/richtexteditor/plugins/changecase.min.js +1 -1
- package/richtexteditor/plugins/charlimit.js +62 -2
- package/richtexteditor/plugins/charlimit.min.js +2 -2
- package/richtexteditor/plugins/comments.js +458 -59
- package/richtexteditor/plugins/comments.min.js +4 -3
- package/richtexteditor/plugins/contentminimap.js +25 -6
- package/richtexteditor/plugins/contentminimap.min.js +1 -1
- package/richtexteditor/plugins/crossreference.js +1 -1
- package/richtexteditor/plugins/crossreference.min.js +1 -1
- package/richtexteditor/plugins/dialoga11y.js +26 -4
- package/richtexteditor/plugins/dialoga11y.min.js +1 -1
- package/richtexteditor/plugins/dictation.js +1 -1
- package/richtexteditor/plugins/documentimport.js +220 -51
- package/richtexteditor/plugins/documentimport.min.js +4 -4
- package/richtexteditor/plugins/documentoutline.js +88 -38
- package/richtexteditor/plugins/documentoutline.min.js +1 -1
- package/richtexteditor/plugins/docxexport.js +204 -32
- package/richtexteditor/plugins/docxexport.min.js +2 -2
- package/richtexteditor/plugins/draghandle.js +267 -3
- package/richtexteditor/plugins/draghandle.min.js +1 -1
- package/richtexteditor/plugins/emailtoolkit.js +28 -14
- package/richtexteditor/plugins/emailtoolkit.min.js +1 -1
- package/richtexteditor/plugins/emojiautocomplete.js +110 -56
- package/richtexteditor/plugins/emojiautocomplete.min.js +1 -1
- package/richtexteditor/plugins/footnotes.js +65 -11
- package/richtexteditor/plugins/footnotes.min.js +1 -1
- package/richtexteditor/plugins/formatpainter.js +10 -0
- package/richtexteditor/plugins/formatpainter.min.js +1 -1
- package/richtexteditor/plugins/formattingmarks.js +53 -8
- package/richtexteditor/plugins/formattingmarks.min.js +1 -1
- package/richtexteditor/plugins/gapcursor.js +60 -0
- package/richtexteditor/plugins/gapcursor.min.js +1 -1
- package/richtexteditor/plugins/ghostcomplete.js +1 -1
- package/richtexteditor/plugins/html2pdf.js +1 -1
- package/richtexteditor/plugins/html2pdf.min.js +1 -1
- package/richtexteditor/plugins/imageeditor.js +12 -6
- package/richtexteditor/plugins/imageeditor.min.js +1 -1
- package/richtexteditor/plugins/importstyles.js +1 -1
- package/richtexteditor/plugins/inlinecode.js +25 -0
- package/richtexteditor/plugins/inlinecode.min.js +1 -1
- package/richtexteditor/plugins/insertcode.js +21 -3
- package/richtexteditor/plugins/insertcode.min.js +4 -4
- package/richtexteditor/plugins/insertemoji.js +112 -15
- package/richtexteditor/plugins/insertemoji.min.js +1 -1
- package/richtexteditor/plugins/insertgallery.js +42 -36
- package/richtexteditor/plugins/insertgallery.min.js +1 -1
- package/richtexteditor/plugins/inserttemplate.js +33 -17
- package/richtexteditor/plugins/inserttemplate.min.js +1 -1
- package/richtexteditor/plugins/keyboarda11y.js +66 -3
- package/richtexteditor/plugins/keyboarda11y.min.js +1 -1
- package/richtexteditor/plugins/layouttable.js +20 -0
- package/richtexteditor/plugins/layouttable.min.js +1 -1
- package/richtexteditor/plugins/linkchecker.js +29 -9
- package/richtexteditor/plugins/linkchecker.min.js +1 -1
- package/richtexteditor/plugins/localdraft.js +51 -7
- package/richtexteditor/plugins/localdraft.min.js +1 -1
- package/richtexteditor/plugins/markdownexport.js +159 -0
- package/richtexteditor/plugins/markdownexport.min.js +2 -2
- package/richtexteditor/plugins/mathdialog.js +11 -5
- package/richtexteditor/plugins/mathdialog.min.js +1 -1
- package/richtexteditor/plugins/mentionplugin.js +88 -25
- package/richtexteditor/plugins/mentionplugin.min.js +1 -1
- package/richtexteditor/plugins/mermaiddiagram.js +9 -3
- package/richtexteditor/plugins/mermaiddiagram.min.js +2 -2
- package/richtexteditor/plugins/multilevellist.js +4 -0
- package/richtexteditor/plugins/multilevellist.min.js +1 -1
- package/richtexteditor/plugins/pdfexport.js +235 -35
- package/richtexteditor/plugins/pdfexport.min.js +12 -12
- package/richtexteditor/plugins/permanentpen.js +3 -3
- package/richtexteditor/plugins/permanentpen.min.js +1 -1
- package/richtexteditor/plugins/readabilitystats.js +25 -11
- package/richtexteditor/plugins/readabilitystats.min.js +1 -1
- package/richtexteditor/plugins/readaloud.js +29 -15
- package/richtexteditor/plugins/readaloud.min.js +1 -1
- package/richtexteditor/plugins/restrictedediting.js +166 -0
- package/richtexteditor/plugins/restrictedediting.min.js +2 -2
- package/richtexteditor/plugins/revisionhistory.js +169 -37
- package/richtexteditor/plugins/revisionhistory.min.js +3 -3
- package/richtexteditor/plugins/rubyannotation.js +19 -9
- package/richtexteditor/plugins/rubyannotation.min.js +1 -1
- package/richtexteditor/plugins/sanitizer.js +86 -4
- package/richtexteditor/plugins/sanitizer.min.js +1 -1
- package/richtexteditor/plugins/slashcommand.js +30 -16
- package/richtexteditor/plugins/slashcommand.min.js +2 -2
- package/richtexteditor/plugins/smartchips.js +36 -22
- package/richtexteditor/plugins/smartchips.min.js +2 -2
- package/richtexteditor/plugins/spellcheck.js +17 -8
- package/richtexteditor/plugins/spellcheck.min.js +2 -2
- package/richtexteditor/plugins/stateinspector.js +19 -9
- package/richtexteditor/plugins/stateinspector.min.js +2 -2
- package/richtexteditor/plugins/structured-content-bridge.js +56 -24
- package/richtexteditor/plugins/structured-content-bridge.min.js +7 -7
- package/richtexteditor/plugins/tableofcontents.js +5 -2
- package/richtexteditor/plugins/tableofcontents.min.js +1 -1
- package/richtexteditor/plugins/tabletools.js +388 -3
- package/richtexteditor/plugins/tabletools.min.js +1 -1
- package/richtexteditor/plugins/textdirection.js +18 -5
- package/richtexteditor/plugins/textdirection.min.js +1 -1
- package/richtexteditor/plugins/textpartlanguage.js +161 -32
- package/richtexteditor/plugins/textpartlanguage.min.js +1 -1
- package/richtexteditor/plugins/todolist.js +90 -6
- package/richtexteditor/plugins/todolist.min.js +1 -1
- package/richtexteditor/plugins/trackedchanges.js +1812 -25
- package/richtexteditor/plugins/trackedchanges.min.js +5 -3
- package/richtexteditor/plugins/wordexport.js +2 -2
- package/richtexteditor/plugins/yjscollab.js +9 -4
- package/richtexteditor/plugins/yjscollab.min.js +1 -1
- package/richtexteditor/plugins/zz_richtextbox_dialog_style.js +22 -13
- package/richtexteditor/plugins/zz_richtextbox_dialog_style.min.js +1 -1
- package/richtexteditor/rte-config.js +131 -50
- package/richtexteditor/rte-headless.js +19 -4
- package/richtexteditor/rte-webcomponent.js +89 -16
- package/richtexteditor/rte.js +133 -52
- package/richtexteditor/runtime/colorpicker_more_ns.htm +93 -16
- package/scripts/build-angular.mjs +97 -0
- package/svelte/index.d.ts +2 -1
- package/svelte/index.js +80 -9
- package/vue/index.js +130 -34
- package/angular/component.ts +0 -297
- package/angular/index.ts +0 -24
|
@@ -36,6 +36,21 @@ RTE_DefaultConfig.plugin_pdfexport = RTE_Plugin_PdfExport;
|
|
|
36
36
|
if (typeof RTE_DefaultConfig.pdfExport === "undefined") RTE_DefaultConfig.pdfExport = true;
|
|
37
37
|
|
|
38
38
|
function RTE_Plugin_PdfExport() {
|
|
39
|
+
// An ordered list item's number in its list style: 1, a..z then aa (CSS lower-alpha), i/I roman.
|
|
40
|
+
function listNumber(n, kind) {
|
|
41
|
+
if (kind === "a" || kind === "A") {
|
|
42
|
+
var t = "";
|
|
43
|
+
for (var k = n; k > 0; k = Math.floor((k - 1) / 26)) t = String.fromCharCode(97 + (k - 1) % 26) + t;
|
|
44
|
+
return t ? (kind === "A" ? t.toUpperCase() : t) : String(n); // 0 and below: a number, never ""
|
|
45
|
+
}
|
|
46
|
+
if ((kind === "i" || kind === "I") && n > 0 && n < 4000) {
|
|
47
|
+
var r = "", vals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1], sy = ["m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i"];
|
|
48
|
+
for (var q = 0; q < vals.length; q++) while (n >= vals[q]) { r += sy[q]; n -= vals[q]; }
|
|
49
|
+
return kind === "I" ? r.toUpperCase() : r;
|
|
50
|
+
}
|
|
51
|
+
return String(n);
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
var obj = this;
|
|
40
55
|
var config, editor;
|
|
41
56
|
|
|
@@ -264,8 +279,11 @@ function RTE_Plugin_PdfExport() {
|
|
|
264
279
|
var s = {
|
|
265
280
|
bold: inherited.bold, italic: inherited.italic, underline: inherited.underline,
|
|
266
281
|
strike: inherited.strike, size: inherited.size, color: inherited.color,
|
|
267
|
-
mono: inherited.mono, link: inherited.link
|
|
282
|
+
mono: inherited.mono, link: inherited.link, lang: inherited.lang
|
|
268
283
|
};
|
|
284
|
+
// 2026-10-17 a run's language: the nearest lang attribute. tools/pdf/inline-lang.mjs
|
|
285
|
+
var nodeLang = node.getAttribute ? node.getAttribute("lang") : "";
|
|
286
|
+
if (nodeLang) s.lang = nodeLang;
|
|
269
287
|
var tag = node.tagName ? node.tagName.toLowerCase() : "";
|
|
270
288
|
if (tag === "b" || tag === "strong") s.bold = true;
|
|
271
289
|
if (tag === "i" || tag === "em") s.italic = true;
|
|
@@ -415,6 +433,19 @@ function RTE_Plugin_PdfExport() {
|
|
|
415
433
|
return px * 0.75;
|
|
416
434
|
}
|
|
417
435
|
|
|
436
|
+
// The language a block is written in: its own lang, the nearest ancestor's inside the content, or - when its
|
|
437
|
+
// whole text is one <span lang> - that span's. "" when none is set.
|
|
438
|
+
function blockLang(node) {
|
|
439
|
+
for (var n = node; n && n.nodeType === 1 && n.tagName.toLowerCase() !== "body"; n = n.parentNode) {
|
|
440
|
+
if (n.getAttribute("lang")) return n.getAttribute("lang");
|
|
441
|
+
}
|
|
442
|
+
var el = null, k = node.childNodes;
|
|
443
|
+
for (var i = 0; i < k.length; i++) {
|
|
444
|
+
if (k[i].nodeType === 3 && k[i].nodeValue.replace(/[\s\u200b\u00a0]/g, "")) return "";
|
|
445
|
+
if (k[i].nodeType === 1) { if (el) return ""; el = k[i]; }
|
|
446
|
+
}
|
|
447
|
+
return el && el.getAttribute("lang") ? el.getAttribute("lang") : "";
|
|
448
|
+
}
|
|
418
449
|
function block(node, style, indent, listMarker, listId, listOrdered, roleOverride) {
|
|
419
450
|
var tag = node.tagName.toLowerCase();
|
|
420
451
|
var s = styleOf(node, style);
|
|
@@ -430,6 +461,8 @@ function RTE_Plugin_PdfExport() {
|
|
|
430
461
|
var lines = self.wrap(runs, width, tag === "pre" ? 1.25 : 1.4);
|
|
431
462
|
self.blocks.push({
|
|
432
463
|
type: "text", lines: lines, indent: indent, align: align,
|
|
464
|
+
// 2026-10-15 the block's own language, for /Lang on its structure element. tools/pdf/paragraph-lang.mjs
|
|
465
|
+
lang: blockLang(node),
|
|
433
466
|
spaceBefore: HEADING_SCALE[tag] ? s.size * 0.6 : self.baseSize * 0.35,
|
|
434
467
|
spaceAfter: HEADING_SCALE[tag] ? s.size * 0.3 : self.baseSize * 0.35,
|
|
435
468
|
marker: listMarker || null, markerStyle: s,
|
|
@@ -470,7 +503,15 @@ function RTE_Plugin_PdfExport() {
|
|
|
470
503
|
|
|
471
504
|
if (tag === "hr") { self.blocks.push({ type: "rule", indent: indent }); continue; }
|
|
472
505
|
if (tag === "ul" || tag === "ol") {
|
|
473
|
-
var counter = parseInt(c.getAttribute("start")
|
|
506
|
+
var counter = parseInt(c.getAttribute("start"), 10);
|
|
507
|
+
if (isNaN(counter)) counter = 1;
|
|
508
|
+
// 2026-10-04 a lettered / Roman list is numbered the way the editor shows it (it was
|
|
509
|
+
// always 1. 2. 3.). tools/pdf/list-markers.mjs
|
|
510
|
+
var lst = String((c.style && c.style.listStyleType) || "").toLowerCase(), typ = c.getAttribute("type") || "";
|
|
511
|
+
var kind = /^(lower-alpha|lower-latin)$/.test(lst) || typ === "a" ? "a"
|
|
512
|
+
: /^(upper-alpha|upper-latin)$/.test(lst) || typ === "A" ? "A"
|
|
513
|
+
: lst === "lower-roman" || typ === "i" ? "i"
|
|
514
|
+
: lst === "upper-roman" || typ === "I" ? "I" : "1";
|
|
474
515
|
// Every item of one list shares an id so the tag tree can
|
|
475
516
|
// rebuild a single <L> around them; without it each item
|
|
476
517
|
// becomes an isolated paragraph and a screen reader never
|
|
@@ -479,7 +520,11 @@ function RTE_Plugin_PdfExport() {
|
|
|
479
520
|
for (var li = 0; li < c.childNodes.length; li++) {
|
|
480
521
|
var item = c.childNodes[li];
|
|
481
522
|
if (item.nodeType !== 1 || item.tagName.toLowerCase() !== "li") continue;
|
|
482
|
-
var marker = tag === "ol" ? (counter
|
|
523
|
+
var marker = tag === "ol" ? listNumber(counter++, kind) + "." : "\u2022";
|
|
524
|
+
// 2026-10-15 a task item shows whether it is done: every item got the plain bullet, so a printed
|
|
525
|
+
// checklist lost every tick. The standard fonts have no ballot box, so "[x]" / "[ ]".
|
|
526
|
+
// tools/pdf/task-list-marks.mjs
|
|
527
|
+
if (c.classList && c.classList.contains("rte-task-list")) marker = item.getAttribute("data-checked") === "true" ? "[x]" : "[ ]";
|
|
483
528
|
// The item's own text, then any nested list one level in.
|
|
484
529
|
block(item, styleOf(item, style), indent + self.baseSize * 1.6, marker, listId, tag === "ol");
|
|
485
530
|
walkNestedLists(item, styleOf(item, style), indent + self.baseSize * 1.6);
|
|
@@ -495,6 +540,19 @@ function RTE_Plugin_PdfExport() {
|
|
|
495
540
|
if (isBlockTag(tag)) {
|
|
496
541
|
var own = (tag === "blockquote" ? self.baseSize * 1.5 : 0) + cssIndentPt(c);
|
|
497
542
|
var childRole = (tag === "blockquote") ? "BlockQuote" : roleOverride;
|
|
543
|
+
// 2026-10-15 a block holding just an image - the shape the editor gives every inserted picture,
|
|
544
|
+
// <p><img></p> - is a Figure with its alt text. Only an <img> sitting directly at block level was;
|
|
545
|
+
// inside a <p> it was an inline run, so the PDF had no Figure and no /Alt (WCAG 1.1.1, PDF/UA).
|
|
546
|
+
// tools/pdf/figure-alt.mjs
|
|
547
|
+
var soleImgs = c.getElementsByTagName("img");
|
|
548
|
+
if (soleImgs.length === 1 && !(c.textContent || "").replace(/[\s\u200b\u00a0]/g, "") && !hasBlockChildren(c)) {
|
|
549
|
+
var fdims = imageDims(soleImgs[0], self.contentWidth - indent - own);
|
|
550
|
+
if (fdims.w) {
|
|
551
|
+
var falign = String((c.style && c.style.textAlign) || "").toLowerCase();
|
|
552
|
+
self.blocks.push({ type: "text", role: "Figure", alt: soleImgs[0].getAttribute("alt") || "", lines: [{ parts: [{ img: soleImgs[0], w: fdims.w, h: fdims.h, style: style }], width: fdims.w, size: fdims.h, height: fdims.h, image: true }], indent: indent + own, align: falign === "center" || falign === "right" ? falign : "left", spaceBefore: 4, spaceAfter: 4 });
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
498
556
|
if (hasBlockChildren(c)) { walk(c, styleOf(c, style), indent + own, childRole); continue; }
|
|
499
557
|
block(c, style, indent + own, null, null, false, childRole);
|
|
500
558
|
continue;
|
|
@@ -533,6 +591,10 @@ function RTE_Plugin_PdfExport() {
|
|
|
533
591
|
var rows = [];
|
|
534
592
|
var trs = node.getElementsByTagName("tr");
|
|
535
593
|
var maxCells = 0;
|
|
594
|
+
// 2026-10-15 rowspan: covered[col] = rows a cell above still covers there. Cells in the rows below skip those
|
|
595
|
+
// columns (they were placed from column 0, shifting a row left under the wrong header), and each covered
|
|
596
|
+
// position becomes an empty placeholder cell so the grid stays aligned. tools/pdf/table-rowspan.mjs
|
|
597
|
+
var covered = {};
|
|
536
598
|
for (var r = 0; r < trs.length; r++) {
|
|
537
599
|
var cells = [];
|
|
538
600
|
for (var c = 0; c < trs[r].childNodes.length; c++) {
|
|
@@ -540,38 +602,77 @@ function RTE_Plugin_PdfExport() {
|
|
|
540
602
|
if (cell.nodeType !== 1) continue;
|
|
541
603
|
var tag = cell.tagName.toLowerCase();
|
|
542
604
|
if (tag !== "td" && tag !== "th") continue;
|
|
543
|
-
|
|
605
|
+
// 2026-10-15 colspan: a cell starts at its grid column and spans its columns. tools/pdf/table-colspan.mjs
|
|
606
|
+
var span = Math.max(1, Math.min(50, parseInt(cell.getAttribute("colspan"), 10) || 1));
|
|
607
|
+
var rspan = Math.max(1, Math.min(500, parseInt(cell.getAttribute("rowspan"), 10) || 1));
|
|
608
|
+
cells.push({ node: cell, header: tag === "th", span: span, rowspan: rspan });
|
|
544
609
|
}
|
|
545
|
-
|
|
610
|
+
var placed = [], gridCols = 0;
|
|
611
|
+
for (var gc = 0; gc < cells.length; gc++) {
|
|
612
|
+
while (covered[gridCols] > 0) { placed.push({ placeholder: true, start: gridCols, span: 1, header: false }); gridCols++; }
|
|
613
|
+
cells[gc].start = gridCols;
|
|
614
|
+
placed.push(cells[gc]);
|
|
615
|
+
if (cells[gc].rowspan > 1) for (var rc = gridCols; rc < gridCols + cells[gc].span; rc++) covered[rc] = cells[gc].rowspan;
|
|
616
|
+
gridCols += cells[gc].span;
|
|
617
|
+
}
|
|
618
|
+
// covered columns after the row's last cell
|
|
619
|
+
for (var tail in covered) if (+tail >= gridCols && covered[tail] > 0) { placed.push({ placeholder: true, start: +tail, span: 1, header: false }); if (+tail + 1 > gridCols) gridCols = +tail + 1; }
|
|
620
|
+
// this row is done: each covering cell has one row fewer left (a cell covering from THIS row still needs
|
|
621
|
+
// its own count, so it was set to rowspan and drops to rowspan - 1 here)
|
|
622
|
+
for (var dc in covered) { covered[dc]--; if (covered[dc] <= 0) delete covered[dc]; }
|
|
623
|
+
placed.sort(function (p1, p2) { return p1.start - p2.start; });
|
|
624
|
+
cells = cells.length ? placed : cells;
|
|
625
|
+
if (cells.length) { rows.push(cells); if (gridCols > maxCells) maxCells = gridCols; }
|
|
546
626
|
}
|
|
547
627
|
if (!rows.length) return;
|
|
548
628
|
|
|
549
629
|
var width = this.contentWidth - indent;
|
|
550
630
|
var colWidth = width / maxCells;
|
|
631
|
+
// 2026-10-15 column widths follow the <colgroup> (% or px), scaled to the table's width on the page: every
|
|
632
|
+
// table was split into equal columns. A table without one keeps equal columns. tools/pdf/table-col-widths.mjs
|
|
633
|
+
var colWidths = [], cg = [], cw = 0;
|
|
634
|
+
for (var ck = 0; ck < node.children.length; ck++) if (node.children[ck].tagName && node.children[ck].tagName.toLowerCase() === "colgroup") {
|
|
635
|
+
for (var cj = 0; cj < node.children[ck].children.length; cj++) if (node.children[ck].children[cj].tagName.toLowerCase() === "col") cg.push(node.children[ck].children[cj]);
|
|
636
|
+
}
|
|
637
|
+
if (cg.length === maxCells) {
|
|
638
|
+
for (var cq = 0; cq < cg.length; cq++) {
|
|
639
|
+
var cv = (cg[cq].style && cg[cq].style.width) || cg[cq].getAttribute("width") || "", cm = /^\s*([\d.]+)(%|px)?\s*$/.exec(cv);
|
|
640
|
+
if (!cm || !(parseFloat(cm[1]) > 0)) { colWidths = []; break; }
|
|
641
|
+
colWidths.push(parseFloat(cm[1]));
|
|
642
|
+
}
|
|
643
|
+
for (var cs2 = 0; cs2 < colWidths.length; cs2++) cw += colWidths[cs2];
|
|
644
|
+
if (cw > 0) colWidths = colWidths.map(function (v) { return v / cw * width; });
|
|
645
|
+
}
|
|
646
|
+
if (colWidths.length !== maxCells) { colWidths = []; for (var ce = 0; ce < maxCells; ce++) colWidths.push(colWidth); }
|
|
647
|
+
function spanWidth(start, span) { var s = 0; for (var sw = start; sw < start + span && sw < colWidths.length; sw++) s += colWidths[sw]; return s; }
|
|
551
648
|
var pad = 4;
|
|
552
649
|
var laid = [];
|
|
553
650
|
for (var i = 0; i < rows.length; i++) {
|
|
554
651
|
var out = [], tallest = 0;
|
|
555
652
|
for (var j = 0; j < rows[i].length; j++) {
|
|
653
|
+
if (rows[i][j].placeholder) { out.push({ lines: [], header: false, start: rows[i][j].start, span: 1, placeholder: true }); continue; }
|
|
556
654
|
var cs = styleOf(rows[i][j].node, style);
|
|
557
655
|
if (rows[i][j].header) cs.bold = true;
|
|
558
|
-
var lines = self.wrap(self.runsOf(rows[i][j].node, cs),
|
|
656
|
+
var lines = self.wrap(self.runsOf(rows[i][j].node, cs), spanWidth(rows[i][j].start, rows[i][j].span) - pad * 2, 1.35);
|
|
559
657
|
var h = 0;
|
|
560
658
|
for (var k = 0; k < lines.length; k++) h += lines[k].height;
|
|
561
659
|
if (h > tallest) tallest = h;
|
|
562
|
-
out.push({ lines: lines, header: rows[i][j].header });
|
|
660
|
+
out.push({ lines: lines, header: rows[i][j].header, start: rows[i][j].start, span: rows[i][j].span, rowspan: rows[i][j].rowspan || 1 });
|
|
563
661
|
}
|
|
564
|
-
laid.push({ cells: out, height: tallest + pad * 2, header: rows[i][0] && rows[i][0].header });
|
|
662
|
+
laid.push({ cells: out, height: tallest + pad * 2, header: rows[i][0] && !rows[i][0].placeholder && rows[i][0].header });
|
|
565
663
|
}
|
|
566
664
|
this.blocks.push({
|
|
567
|
-
type: "table", rows: laid, colWidth: colWidth, cols: maxCells,
|
|
665
|
+
type: "table", rows: laid, colWidth: colWidth, colWidths: colWidths, cols: maxCells,
|
|
568
666
|
indent: indent, pad: pad,
|
|
569
667
|
spaceBefore: this.baseSize * 0.5, spaceAfter: this.baseSize * 0.5
|
|
570
668
|
});
|
|
571
669
|
};
|
|
572
670
|
|
|
573
671
|
// ------------------------------------------------------------- rendering
|
|
672
|
+
// 2026-10-15 the document language, read by the tagger to mark blocks in other languages
|
|
673
|
+
var pdfDocLang = "en";
|
|
574
674
|
function buildPdf(options) {
|
|
675
|
+
pdfDocLang = String((options && options.lang) || document.documentElement.lang || "en").toLowerCase();
|
|
575
676
|
var familyKey = String(options.font || config.pdfExportFont || "helvetica").toLowerCase();
|
|
576
677
|
if (!FONT_FAMILIES[familyKey]) familyKey = "helvetica";
|
|
577
678
|
var baseSize = Number(options.fontSize || config.pdfExportBaseFontSize || 11);
|
|
@@ -583,6 +684,11 @@ function RTE_Plugin_PdfExport() {
|
|
|
583
684
|
// overlays, watermarks and formatting marks into the PDF.
|
|
584
685
|
var host = document.createElement("div");
|
|
585
686
|
host.innerHTML = editor.getHTMLCode() || "";
|
|
687
|
+
var liveLists = editor.getEditable().querySelectorAll("ol,ul"), exportLists = host.querySelectorAll("ol,ul");
|
|
688
|
+
if (liveLists.length === exportLists.length) for (var li = 0; li < liveLists.length; li++) {
|
|
689
|
+
var listStyle = liveLists[li].ownerDocument.defaultView.getComputedStyle(liveLists[li]).listStyleType;
|
|
690
|
+
if (listStyle) exportLists[li].style.listStyleType = listStyle;
|
|
691
|
+
}
|
|
586
692
|
|
|
587
693
|
var layout = new Layout(geom, baseSize, familyKey);
|
|
588
694
|
var blocks = layout.run(host);
|
|
@@ -716,33 +822,67 @@ function RTE_Plugin_PdfExport() {
|
|
|
716
822
|
}
|
|
717
823
|
var pagesId = pdf.alloc();
|
|
718
824
|
|
|
719
|
-
var allMarks = [];
|
|
825
|
+
var allMarks = [], heads = [], linkAnnots = [];
|
|
720
826
|
for (var pi = 0; pi < pages.length; pi++) {
|
|
721
827
|
var tagger = new Tagger(pi);
|
|
722
828
|
var draw = renderPage(pages[pi], geom, familyKey, baseSize, imageRef, pi + 1, pages.length, options, tagger);
|
|
829
|
+
for (var hd = 0; hd < (draw.headings || []).length; hd++) { draw.headings[hd].page = pi; heads.push(draw.headings[hd]); }
|
|
723
830
|
pdf.set(contentIds[pi], { dict: "<< /Length 0 >>", stream: draw.content });
|
|
724
831
|
allMarks.push(draw.marks);
|
|
725
832
|
for (var a = 0; a < draw.links.length; a++) {
|
|
726
833
|
var lk = draw.links[a];
|
|
727
|
-
|
|
834
|
+
// 2026-10-15 each link annotation is in the tag tree: /StructParent (keys after the pages'), /Contents
|
|
835
|
+
// naming it, and a Link element built in buildStructTree. They were click targets a screen reader
|
|
836
|
+
// could not find (PDF/UA 7.18, Matterhorn 28-011 / 28-004). tools/pdf/link-tags.mjs
|
|
837
|
+
var lsp = pages.length + linkAnnots.length;
|
|
838
|
+
var lid = pdf.add("<< /Type /Annot /Subtype /Link /Rect [" +
|
|
728
839
|
fixed(lk.x0) + " " + fixed(lk.y0) + " " + fixed(lk.x1) + " " + fixed(lk.y1) +
|
|
729
|
-
"] /Border [0 0 0] /
|
|
840
|
+
"] /Border [0 0 0] /StructParent " + lsp + " /Contents (" + escapeString(toWinAnsi(String(lk.text || lk.href).replace(/\s+/g, " ").trim() || lk.href)) + ")" +
|
|
841
|
+
" /A << /S /URI /URI (" + escapeString(toWinAnsi(lk.href)) + ") >> >>");
|
|
842
|
+
annotIds[pi].push(lid);
|
|
843
|
+
linkAnnots.push({ id: lid, page: pi, key: lsp, owner: lk.owner });
|
|
730
844
|
}
|
|
731
845
|
}
|
|
732
846
|
|
|
733
|
-
var struct = buildStructTree(pdf, allMarks, pageIds);
|
|
847
|
+
var struct = buildStructTree(pdf, allMarks, pageIds, linkAnnots);
|
|
734
848
|
|
|
735
849
|
for (var pj = 0; pj < pages.length; pj++) {
|
|
736
850
|
pdf.set(pageIds[pj], "<< /Type /Page /Parent " + pagesId + " 0 R /MediaBox [0 0 " +
|
|
737
851
|
fixed(geom.width) + " " + fixed(geom.height) + "] /Resources << /Font << " + fontRes.join(" ") + " >>" +
|
|
738
852
|
(imageRes.length ? " /XObject << " + imageRes.join(" ") + " >>" : "") +
|
|
739
853
|
" >> /Contents " + contentIds[pj] + " 0 R /StructParents " + pj +
|
|
740
|
-
(annotIds[pj].length ? " /Annots [" + annotIds[pj].map(function (id) { return id + " 0 R"; }).join(" ") + "]" : "") +
|
|
854
|
+
(annotIds[pj].length ? " /Annots [" + annotIds[pj].map(function (id) { return id + " 0 R"; }).join(" ") + "] /Tabs /S" : "") +
|
|
741
855
|
" >>");
|
|
742
856
|
}
|
|
743
857
|
pdf.set(pagesId, "<< /Type /Pages /Count " + pages.length + " /Kids [" +
|
|
744
858
|
pageIds.map(function (id) { return id + " 0 R"; }).join(" ") + "] >>");
|
|
745
859
|
|
|
860
|
+
// 2026-10-15 bookmarks from the headings, nested by level, each opening its heading's page at the heading
|
|
861
|
+
// (Word and Chrome export them; a long report had nothing but scrolling - WCAG 2.4.5, PDF2).
|
|
862
|
+
// tools/pdf/outline-bookmarks.mjs
|
|
863
|
+
var outlineId = 0;
|
|
864
|
+
if (heads.length) {
|
|
865
|
+
outlineId = pdf.alloc();
|
|
866
|
+
var oroot = { id: outlineId, kids: [] }, ostack = [oroot], onodes = [];
|
|
867
|
+
for (var oh = 0; oh < heads.length; oh++) {
|
|
868
|
+
var on = { h: heads[oh], id: pdf.alloc(), kids: [], parent: null };
|
|
869
|
+
while (ostack.length > 1 && ostack[ostack.length - 1].h.level >= on.h.level) ostack.pop();
|
|
870
|
+
on.parent = ostack[ostack.length - 1];
|
|
871
|
+
on.parent.kids.push(on);
|
|
872
|
+
ostack.push(on);
|
|
873
|
+
onodes.push(on);
|
|
874
|
+
}
|
|
875
|
+
var ocount = function (n) { var c = n.kids.length; for (var k = 0; k < n.kids.length; k++) c += ocount(n.kids[k]); return c; };
|
|
876
|
+
for (var oi = 0; oi < onodes.length; oi++) {
|
|
877
|
+
var n = onodes[oi], sib = n.parent.kids, at = sib.indexOf(n);
|
|
878
|
+
pdf.set(n.id, "<< /Title (" + escapeString(toWinAnsi(n.h.title)) + ") /Parent " + n.parent.id + " 0 R" +
|
|
879
|
+
(at > 0 ? " /Prev " + sib[at - 1].id + " 0 R" : "") + (at < sib.length - 1 ? " /Next " + sib[at + 1].id + " 0 R" : "") +
|
|
880
|
+
(n.kids.length ? " /First " + n.kids[0].id + " 0 R /Last " + n.kids[n.kids.length - 1].id + " 0 R /Count " + ocount(n) : "") +
|
|
881
|
+
" /Dest [" + pageIds[n.h.page] + " 0 R /XYZ null " + fixed(n.h.y) + " null] >>");
|
|
882
|
+
}
|
|
883
|
+
pdf.set(outlineId, "<< /Type /Outlines /First " + oroot.kids[0].id + " 0 R /Last " + oroot.kids[oroot.kids.length - 1].id + " 0 R /Count " + ocount(oroot) + " >>");
|
|
884
|
+
}
|
|
885
|
+
|
|
746
886
|
var title = options.title || config.pdfExportTitle || document.title || "Document";
|
|
747
887
|
var author = options.author || config.pdfExportAuthor || "";
|
|
748
888
|
// /MarkInfo and /StructTreeRoot declare the file tagged. DisplayDocTitle
|
|
@@ -752,6 +892,7 @@ function RTE_Plugin_PdfExport() {
|
|
|
752
892
|
var catalogId = pdf.add("<< /Type /Catalog /Pages " + pagesId + " 0 R" +
|
|
753
893
|
" /Lang (" + escapeString(String(options.lang || document.documentElement.lang || "en")) + ")" +
|
|
754
894
|
" /MarkInfo << /Marked true >>" +
|
|
895
|
+
(outlineId ? " /Outlines " + outlineId + " 0 R" : "") +
|
|
755
896
|
" /StructTreeRoot " + struct.rootId + " 0 R" +
|
|
756
897
|
" /ViewerPreferences << /DisplayDocTitle true >> >>");
|
|
757
898
|
var infoId = pdf.add("<< /Title (" + escapeString(toWinAnsi(String(title))) + ")" +
|
|
@@ -766,13 +907,13 @@ function RTE_Plugin_PdfExport() {
|
|
|
766
907
|
// The tree is built from the marks rather than from the block list because a
|
|
767
908
|
// single block can be split across pages: its structure element then holds
|
|
768
909
|
// several marked-content references, each naming the page it lives on.
|
|
769
|
-
function buildStructTree(pdf, allMarks, pageIds) {
|
|
910
|
+
function buildStructTree(pdf, allMarks, pageIds, linkAnnots) {
|
|
770
911
|
var rootId = pdf.alloc();
|
|
771
912
|
var nodes = []; // { id, role, kids, extra, children, parent }
|
|
772
913
|
var parentTree = []; // per page: array of struct ids by MCID
|
|
773
914
|
|
|
774
915
|
function node(role, extra) {
|
|
775
|
-
var n = { id: pdf.alloc(), role: role, kids: [], extra: extra || "", children: [], parent: null };
|
|
916
|
+
var n = { id: pdf.alloc(), role: role, kids: [], extra: extra || "", children: [], parent: null, seq: [] };
|
|
776
917
|
nodes.push(n);
|
|
777
918
|
return n;
|
|
778
919
|
}
|
|
@@ -785,6 +926,7 @@ function RTE_Plugin_PdfExport() {
|
|
|
785
926
|
var tableNodes = new Map(); // block -> Table node
|
|
786
927
|
var rowNodes = new Map(); // row -> TR node
|
|
787
928
|
var cellNodes = new Map(); // row -> { col -> TD/TH node }
|
|
929
|
+
var linkNodes = new Map(); // link owner -> Link node (2026-10-17, tools/pdf/link-order.mjs)
|
|
788
930
|
|
|
789
931
|
for (var p = 0; p < allMarks.length; p++) {
|
|
790
932
|
var marks = allMarks[p];
|
|
@@ -792,7 +934,8 @@ function RTE_Plugin_PdfExport() {
|
|
|
792
934
|
for (var m = 0; m < marks.length; m++) {
|
|
793
935
|
var owner = marks[m].owner, mcid = marks[m].mcid;
|
|
794
936
|
var target = resolve(owner);
|
|
795
|
-
target.kids.push({ page: p, mcid: mcid });
|
|
937
|
+
target.kids.push({ page: p, mcid: mcid });
|
|
938
|
+
target.seq.push({ mcr: { page: p, mcid: mcid } });
|
|
796
939
|
parentTree[p][mcid] = target.id;
|
|
797
940
|
}
|
|
798
941
|
}
|
|
@@ -803,9 +946,25 @@ function RTE_Plugin_PdfExport() {
|
|
|
803
946
|
if (child.parent) return;
|
|
804
947
|
child.parent = parent;
|
|
805
948
|
parent.children.push(child);
|
|
949
|
+
parent.seq.push({ elem: child });
|
|
950
|
+
}
|
|
951
|
+
// a Link element per link annotation, under the paragraph / cell holding it
|
|
952
|
+
var linkKeys = [];
|
|
953
|
+
for (var la = 0; la < (linkAnnots || []).length; la++) {
|
|
954
|
+
var lnk;
|
|
955
|
+
if (linkAnnots[la].owner && linkAnnots[la].owner.kind === "link") lnk = resolve(linkAnnots[la].owner);
|
|
956
|
+
else { lnk = node("Link"); attach(resolve(linkAnnots[la].owner), lnk); }
|
|
957
|
+
lnk.objr = linkAnnots[la];
|
|
958
|
+
linkKeys.push(linkAnnots[la].key + " " + lnk.id + " 0 R");
|
|
806
959
|
}
|
|
807
960
|
|
|
808
961
|
function resolve(owner) {
|
|
962
|
+
// 2026-10-17 a link's words: their own Link element under the paragraph or cell, created where they occur
|
|
963
|
+
if (owner.kind === "link") {
|
|
964
|
+
var ln = linkNodes.get(owner);
|
|
965
|
+
if (!ln) { ln = node("Link"); linkNodes.set(owner, ln); attach(resolve(owner.parent), ln); }
|
|
966
|
+
return ln;
|
|
967
|
+
}
|
|
809
968
|
if (owner.kind === "cell") {
|
|
810
969
|
var table = tableNodes.get(owner.block);
|
|
811
970
|
if (!table) { table = node("Table"); tableNodes.set(owner.block, table); attach(docNode, table); }
|
|
@@ -816,7 +975,10 @@ function RTE_Plugin_PdfExport() {
|
|
|
816
975
|
if (!perRow[owner.col]) {
|
|
817
976
|
// A header cell must be TH, not TD: it is what lets a screen
|
|
818
977
|
// reader announce "Revenue, 1,240" instead of just "1,240".
|
|
819
|
-
|
|
978
|
+
// 2026-10-15 table attributes belong in /A << /O /Table >> (where PDF/UA checkers read them; /Scope
|
|
979
|
+
// sat on the element), with /ColSpan for a merged cell. tools/pdf/table-colspan.mjs
|
|
980
|
+
var tattr = (owner.header ? " /Scope /Column" : "") + (owner.span > 1 ? " /ColSpan " + owner.span : "") + (owner.rowspan > 1 ? " /RowSpan " + owner.rowspan : "");
|
|
981
|
+
perRow[owner.col] = node(owner.header ? "TH" : "TD", tattr ? " /A << /O /Table" + tattr + " >>" : "");
|
|
820
982
|
attach(row, perRow[owner.col]);
|
|
821
983
|
}
|
|
822
984
|
return perRow[owner.col];
|
|
@@ -849,6 +1011,11 @@ function RTE_Plugin_PdfExport() {
|
|
|
849
1011
|
var extra = block.role === "Figure"
|
|
850
1012
|
? " /Alt (" + escapeString(toWinAnsi(block.alt || "Image")) + ")"
|
|
851
1013
|
: "";
|
|
1014
|
+
// 2026-10-15 a block in another language says so, or it is read with the document's
|
|
1015
|
+
// pronunciation (WCAG 3.1.2, PDF/UA 7.2). tools/pdf/paragraph-lang.mjs
|
|
1016
|
+
var docLang = pdfDocLang;
|
|
1017
|
+
var bl = String(block.lang || "").replace(/[^A-Za-z0-9-]/g, "");
|
|
1018
|
+
if (bl && bl.toLowerCase() !== docLang) extra += " /Lang (" + bl + ")";
|
|
852
1019
|
body = node(block.role || "P", extra);
|
|
853
1020
|
}
|
|
854
1021
|
bodyNodes.set(block, body);
|
|
@@ -857,14 +1024,17 @@ function RTE_Plugin_PdfExport() {
|
|
|
857
1024
|
return body;
|
|
858
1025
|
}
|
|
859
1026
|
|
|
860
|
-
// Emit. /K
|
|
1027
|
+
// Emit. /K lists child elements and marked-content references in reading order (2026-10-17: child elements
|
|
1028
|
+
// used to come first, so a Link sat at the start of its paragraph). tools/pdf/link-order.mjs
|
|
861
1029
|
for (var n = 0; n < nodes.length; n++) {
|
|
862
1030
|
var nd = nodes[n];
|
|
863
1031
|
var kids = [];
|
|
864
|
-
for (var
|
|
865
|
-
|
|
866
|
-
kids.push(
|
|
1032
|
+
for (var k = 0; k < nd.seq.length; k++) {
|
|
1033
|
+
var sq = nd.seq[k];
|
|
1034
|
+
if (sq.elem) kids.push(sq.elem.id + " 0 R");
|
|
1035
|
+
else kids.push("<< /Type /MCR /Pg " + pageIds[sq.mcr.page] + " 0 R /MCID " + sq.mcr.mcid + " >>");
|
|
867
1036
|
}
|
|
1037
|
+
if (nd.objr) kids.push("<< /Type /OBJR /Obj " + nd.objr.id + " 0 R /Pg " + pageIds[nd.objr.page] + " 0 R >>");
|
|
868
1038
|
var parentRef = nd.parent ? nd.parent.id : rootId;
|
|
869
1039
|
pdf.set(nd.id, "<< /Type /StructElem /S /" + nd.role + " /P " + parentRef + " 0 R" + nd.extra +
|
|
870
1040
|
(kids.length ? " /K [" + kids.join(" ") + "]" : "") + " >>");
|
|
@@ -879,10 +1049,10 @@ function RTE_Plugin_PdfExport() {
|
|
|
879
1049
|
for (var q = 0; q < arr.length; q++) refs.push((arr[q] || docNode.id) + " 0 R");
|
|
880
1050
|
numsParts.push(pt + " [" + refs.join(" ") + "]");
|
|
881
1051
|
}
|
|
882
|
-
var parentTreeId = pdf.add("<< /Nums [" + numsParts.join(" ") + "] >>");
|
|
1052
|
+
var parentTreeId = pdf.add("<< /Nums [" + numsParts.concat(linkKeys).join(" ") + "] >>");
|
|
883
1053
|
|
|
884
1054
|
pdf.set(rootId, "<< /Type /StructTreeRoot /K [" + docNode.id + " 0 R]" +
|
|
885
|
-
" /ParentTree " + parentTreeId + " 0 R /ParentTreeNextKey " + parentTree.length + " >>");
|
|
1055
|
+
" /ParentTree " + parentTreeId + " 0 R /ParentTreeNextKey " + (parentTree.length + linkKeys.length) + " >>");
|
|
886
1056
|
|
|
887
1057
|
return { rootId: rootId };
|
|
888
1058
|
}
|
|
@@ -921,11 +1091,16 @@ function RTE_Plugin_PdfExport() {
|
|
|
921
1091
|
};
|
|
922
1092
|
|
|
923
1093
|
function renderPage(ops, geom, familyKey, baseSize, imageRef, pageNumber, pageCount, options, tagger) {
|
|
924
|
-
var out = [], links = [];
|
|
1094
|
+
var out = [], links = [], headings = [];
|
|
925
1095
|
var left = geom.margin.left;
|
|
926
1096
|
|
|
927
1097
|
for (var i = 0; i < ops.length; i++) {
|
|
928
1098
|
var op = ops[i];
|
|
1099
|
+
// 2026-10-15 where each heading starts, for the bookmarks. tools/pdf/outline-bookmarks.mjs
|
|
1100
|
+
if (op.op === "line" && op.first && op.block && /^H[1-6]$/.test(op.block.role || "")) {
|
|
1101
|
+
var htext = (op.block.lines || []).map(function (ln) { return (ln.parts || []).map(function (pt) { return pt.text || ""; }).join(""); }).join(" ").replace(/\s+/g, " ").trim();
|
|
1102
|
+
if (htext) headings.push({ level: +op.block.role.charAt(1), title: htext, y: op.y });
|
|
1103
|
+
}
|
|
929
1104
|
if (op.op === "rule") {
|
|
930
1105
|
// A horizontal rule is decoration, not content.
|
|
931
1106
|
tagger.artifact(out, function () {
|
|
@@ -949,7 +1124,7 @@ function RTE_Plugin_PdfExport() {
|
|
|
949
1124
|
});
|
|
950
1125
|
}
|
|
951
1126
|
|
|
952
|
-
return { content: out.join("\n"), links: links, marks: tagger.marks };
|
|
1127
|
+
return { content: out.join("\n"), links: links, marks: tagger.marks, headings: headings };
|
|
953
1128
|
}
|
|
954
1129
|
|
|
955
1130
|
function headerFooter(options) {
|
|
@@ -1011,7 +1186,9 @@ function RTE_Plugin_PdfExport() {
|
|
|
1011
1186
|
// (just indent/align), so the owner has to be the cell descriptor the
|
|
1012
1187
|
// caller supplied. Passing the synthetic block instead makes every cell
|
|
1013
1188
|
// a top-level paragraph and the table loses its structure entirely.
|
|
1014
|
-
|
|
1189
|
+
var lineOwner = op.cell || { kind: "body", block: block };
|
|
1190
|
+
var lineOpen = false;
|
|
1191
|
+
if (tagger) { tagger.open(out, line.image ? "Figure" : "Span", lineOwner); lineOpen = true; }
|
|
1015
1192
|
|
|
1016
1193
|
// Wrapping split the text into words, but emitting one BT/Tj block per
|
|
1017
1194
|
// WORD is wrong twice over: the content stream balloons, and text
|
|
@@ -1029,7 +1206,7 @@ function RTE_Plugin_PdfExport() {
|
|
|
1029
1206
|
var last = segments.length ? segments[segments.length - 1] : null;
|
|
1030
1207
|
var mergeable = last && !last.img && last.alias === alias && last.style.size === st.size &&
|
|
1031
1208
|
sameColor(last.style.color, st.color) && last.style.underline === st.underline &&
|
|
1032
|
-
last.style.strike === st.strike && last.style.link === st.link;
|
|
1209
|
+
last.style.strike === st.strike && last.style.link === st.link && (last.style.lang || "") === (st.lang || "");
|
|
1033
1210
|
if (mergeable) { last.text += part.text; last.w += part.w; }
|
|
1034
1211
|
else { segments.push({ text: part.text, style: st, alias: alias, x: cursor, w: part.w }); }
|
|
1035
1212
|
cursor += part.w;
|
|
@@ -1050,8 +1227,25 @@ function RTE_Plugin_PdfExport() {
|
|
|
1050
1227
|
// would only add an empty operation.
|
|
1051
1228
|
if (!seg.text || /^\s+$/.test(seg.text)) continue;
|
|
1052
1229
|
var sst = seg.style;
|
|
1230
|
+
// 2026-10-17 a link's words are marked content of their own, owned by the Link element, so the Link holds
|
|
1231
|
+
// its text at its place in the paragraph (PDF/UA 7.18.5). tools/pdf/link-order.mjs
|
|
1232
|
+
var linkOwner = null;
|
|
1233
|
+
if (tagger && !line.image && sst.link) {
|
|
1234
|
+
if (lineOpen) { tagger.close(out); lineOpen = false; }
|
|
1235
|
+
linkOwner = { kind: "link", parent: lineOwner };
|
|
1236
|
+
tagger.open(out, "Span", linkOwner);
|
|
1237
|
+
} else if (tagger && !lineOpen) {
|
|
1238
|
+
tagger.open(out, "Span", lineOwner); lineOpen = true;
|
|
1239
|
+
}
|
|
1240
|
+
// 2026-10-17 a run in another language than its block (a French phrase in an English sentence) is drawn
|
|
1241
|
+
// inside marked content with a /Lang property, so it is read with the right pronunciation (WCAG 3.1.2,
|
|
1242
|
+
// PDF/UA 7.2). The paragraph's own /Lang is on its structure element. tools/pdf/inline-lang.mjs
|
|
1243
|
+
var runLang = String(sst.lang || "").replace(/[^A-Za-z0-9-]/g, "");
|
|
1244
|
+
var langMark = runLang && runLang.toLowerCase() !== String(block.lang || pdfDocLang || "").toLowerCase();
|
|
1245
|
+
if (langMark) out.push("/Span << /Lang (" + runLang + ") >> BDC");
|
|
1053
1246
|
out.push("BT /" + seg.alias + " " + fixed(sst.size) + " Tf " + colorOp(sst.color) + " " +
|
|
1054
1247
|
fixed(seg.x) + " " + fixed(baseline) + " Td (" + escapeString(toWinAnsi(seg.text)) + ") Tj ET");
|
|
1248
|
+
if (langMark) out.push("EMC");
|
|
1055
1249
|
if (sst.underline) {
|
|
1056
1250
|
out.push(colorStroke(sst.color) + " 0.5 w " + fixed(seg.x) + " " + fixed(baseline - sst.size * 0.12) +
|
|
1057
1251
|
" m " + fixed(seg.x + seg.w) + " " + fixed(baseline - sst.size * 0.12) + " l S");
|
|
@@ -1061,10 +1255,11 @@ function RTE_Plugin_PdfExport() {
|
|
|
1061
1255
|
" m " + fixed(seg.x + seg.w) + " " + fixed(baseline + sst.size * 0.28) + " l S");
|
|
1062
1256
|
}
|
|
1063
1257
|
if (sst.link) {
|
|
1064
|
-
links.push({ href: sst.link, x0: seg.x, y0: baseline - 2, x1: seg.x + seg.w, y1: baseline + sst.size });
|
|
1258
|
+
links.push({ href: sst.link, x0: seg.x, y0: baseline - 2, x1: seg.x + seg.w, y1: baseline + sst.size, text: seg.text || "", owner: linkOwner || lineOwner });
|
|
1065
1259
|
}
|
|
1260
|
+
if (linkOwner) tagger.close(out);
|
|
1066
1261
|
}
|
|
1067
|
-
if (tagger) tagger.close(out);
|
|
1262
|
+
if (tagger && lineOpen) tagger.close(out);
|
|
1068
1263
|
}
|
|
1069
1264
|
|
|
1070
1265
|
function sameColor(a, b) {
|
|
@@ -1078,28 +1273,33 @@ function RTE_Plugin_PdfExport() {
|
|
|
1078
1273
|
var top = op.y, bottom = op.y - row.height;
|
|
1079
1274
|
|
|
1080
1275
|
for (var c = 0; c < row.cells.length; c++) {
|
|
1081
|
-
var
|
|
1276
|
+
var cspan = row.cells[c].span || 1, cstart = row.cells[c].start != null ? row.cells[c].start : c;
|
|
1277
|
+
var cellX = x, cellW = 0;
|
|
1278
|
+
for (var wi = 0; wi < cstart + cspan; wi++) {
|
|
1279
|
+
var colw = block.colWidths && block.colWidths[wi] != null ? block.colWidths[wi] : block.colWidth;
|
|
1280
|
+
if (wi < cstart) cellX += colw; else cellW += colw;
|
|
1281
|
+
}
|
|
1082
1282
|
// Shading and borders are decoration; only the cell TEXT belongs to
|
|
1083
1283
|
// the reading order.
|
|
1084
1284
|
var paint = function () {
|
|
1085
1285
|
if (row.header) {
|
|
1086
1286
|
out.push("0.94 0.95 0.97 rg " + fixed(cellX) + " " + fixed(bottom) + " " +
|
|
1087
|
-
fixed(
|
|
1287
|
+
fixed(cellW) + " " + fixed(row.height) + " re f");
|
|
1088
1288
|
}
|
|
1089
1289
|
out.push("0.75 0.78 0.82 RG 0.5 w " + fixed(cellX) + " " + fixed(bottom) + " " +
|
|
1090
|
-
fixed(
|
|
1290
|
+
fixed(cellW) + " " + fixed(row.height) + " re S");
|
|
1091
1291
|
};
|
|
1092
1292
|
if (tagger) tagger.artifact(out, paint); else paint();
|
|
1093
1293
|
|
|
1094
1294
|
// One structure element per CELL, identified by the row object and
|
|
1095
1295
|
// column index so a table split across pages still resolves to the
|
|
1096
1296
|
// right TD (and a repeated header row to the right TH).
|
|
1097
|
-
var cellOwner = { kind: "cell", block: block, row: row, col: c, header: !!row.cells[c].header };
|
|
1297
|
+
var cellOwner = { kind: "cell", block: block, row: row, col: cstart, span: cspan, rowspan: row.cells[c].rowspan || 1, header: !!row.cells[c].header };
|
|
1098
1298
|
var cy = top - block.pad;
|
|
1099
1299
|
var lines = row.cells[c].lines;
|
|
1100
1300
|
for (var l = 0; l < lines.length; l++) {
|
|
1101
1301
|
renderLine({ line: lines[l], y: cy, block: { indent: 0, align: "left" }, first: l === 0, cell: cellOwner },
|
|
1102
|
-
{ width: cellX +
|
|
1302
|
+
{ width: cellX + cellW + geom.margin.right, margin: { right: geom.margin.right } },
|
|
1103
1303
|
out, cellX + block.pad, "helvetica", imageRef, links, tagger);
|
|
1104
1304
|
cy -= lines[l].height;
|
|
1105
1305
|
}
|