reamkit 1.1.0 → 1.3.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 +11 -5
- package/dist/esm/{layout → core}/arc-to-bezier.d.ts +1 -1
- package/dist/esm/{layout → core}/arc-to-bezier.js +2 -2
- package/dist/esm/core/converter/project.js +2 -0
- package/dist/esm/core/converter/ream.js +4 -3
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +60 -0
- package/dist/esm/{layout → core/drawingml}/chart-geometry.d.ts +2 -2
- package/dist/esm/{layout → core/drawingml}/chart-geometry.js +79 -117
- package/dist/esm/core/drawingml/chart-parser.d.ts +3 -0
- package/dist/esm/core/drawingml/chart-parser.js +28 -1
- package/dist/esm/{layout → core/drawingml}/preset-geometry.d.ts +2 -2
- package/dist/esm/{layout → core/drawingml}/preset-geometry.js +3 -3
- package/dist/esm/core/drawingml/shape-render.d.ts +8 -0
- package/dist/esm/core/drawingml/shape-render.js +71 -0
- package/dist/esm/core/ir/features.d.ts +1 -0
- package/dist/esm/core/ir/features.js +1 -0
- package/dist/esm/core/ir/flow.d.ts +3 -0
- package/dist/esm/core/links.d.ts +6 -0
- package/dist/esm/core/links.js +19 -0
- package/dist/esm/core/numbering/apply.js +5 -2
- package/dist/esm/core/opc/index.d.ts +1 -0
- package/dist/esm/core/opc/package.js +3 -2
- package/dist/esm/core/opc/relationship-types.d.ts +1 -0
- package/dist/esm/core/opc/relationship-types.js +5 -2
- package/dist/esm/core/style-cascade/index.d.ts +1 -0
- package/dist/esm/core/style-cascade/index.js +1 -0
- package/dist/esm/core/style-cascade/table.d.ts +2 -0
- package/dist/esm/core/style-cascade/table.js +161 -0
- package/dist/esm/core/vector.d.ts +2 -0
- package/dist/esm/core/vector.js +19 -1
- package/dist/esm/excel/print-model.d.ts +3 -0
- package/dist/esm/excel/print-model.js +4 -8
- package/dist/esm/excel/sheet-drawing.d.ts +9 -0
- package/dist/esm/excel/sheet-drawing.js +113 -0
- package/dist/esm/excel/worksheet-parser.d.ts +1 -0
- package/dist/esm/excel/worksheet-parser.js +4 -1
- package/dist/esm/excel/xlsx-reader.js +41 -0
- package/dist/esm/excel/xlsx-to-pdf.js +25 -18
- package/dist/esm/html/html-writer.js +188 -16
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/layout/page-doc.d.ts +4 -0
- package/dist/esm/layout/styled-layout.d.ts +21 -3
- package/dist/esm/layout/styled-layout.js +745 -259
- package/dist/esm/pdf/encryption.d.ts +34 -0
- package/dist/esm/pdf/encryption.js +143 -0
- package/dist/esm/pdf/index.d.ts +2 -1
- package/dist/esm/pdf/struct-tree.d.ts +7 -1
- package/dist/esm/pdf/struct-tree.js +22 -6
- package/dist/esm/pdf/styled-page-emitter.d.ts +3 -1
- package/dist/esm/pdf/styled-page-emitter.js +185 -35
- package/dist/esm/pdf/styled-page-renderer.d.ts +1 -0
- package/dist/esm/pdf/styled-page-renderer.js +17 -2
- package/dist/esm/pdf/writer.d.ts +2 -0
- package/dist/esm/pdf/writer.js +6 -1
- package/dist/esm/pdf/xmp.d.ts +1 -0
- package/dist/esm/pdf/xmp.js +4 -0
- package/dist/esm/svg/svg-writer.js +2 -6
- package/dist/esm/word/document-parser.d.ts +3 -0
- package/dist/esm/word/document-parser.js +214 -23
- package/dist/esm/word/docx-reader.js +37 -15
- package/dist/esm/word/docx-to-pdf.js +23 -13
- package/dist/esm/word/drawing-parser.d.ts +4 -1
- package/dist/esm/word/drawing-parser.js +53 -2
- package/dist/esm/word/index.d.ts +2 -2
- package/dist/esm/word/styles-parser.js +134 -1
- package/dist/esm/word/table-parser.js +35 -0
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PdfValue } from './objects.js';
|
|
2
|
+
export interface PdfPermissions {
|
|
3
|
+
/** Bit 3 — print the document (default true). */
|
|
4
|
+
readonly printing?: boolean;
|
|
5
|
+
/** Bit 4 — modify contents (default true). */
|
|
6
|
+
readonly modifying?: boolean;
|
|
7
|
+
/** Bit 5 — copy text and graphics (default true). */
|
|
8
|
+
readonly copying?: boolean;
|
|
9
|
+
/** Bit 6 — add/modify annotations (default true). */
|
|
10
|
+
readonly annotating?: boolean;
|
|
11
|
+
/** Bit 9 — fill in form fields (default true). */
|
|
12
|
+
readonly fillingForms?: boolean;
|
|
13
|
+
/** Bit 10 — extraction for accessibility (default true; forced by PDF/UA). */
|
|
14
|
+
readonly contentAccessibility?: boolean;
|
|
15
|
+
/** Bit 11 — assemble (insert/rotate/delete pages) (default true). */
|
|
16
|
+
readonly documentAssembly?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface PdfEncryptOptions {
|
|
19
|
+
/** Required to OPEN the document. Empty string = opens without a prompt. */
|
|
20
|
+
readonly userPassword?: string;
|
|
21
|
+
/** Overrides the permission restrictions. Defaults to the user password. */
|
|
22
|
+
readonly ownerPassword?: string;
|
|
23
|
+
readonly permissions?: PdfPermissions;
|
|
24
|
+
}
|
|
25
|
+
export interface PreparedEncryption {
|
|
26
|
+
readonly fileKey: Uint8Array;
|
|
27
|
+
readonly encryptDict: PdfValue;
|
|
28
|
+
}
|
|
29
|
+
export declare function permissionBits(p: PdfPermissions | undefined): number;
|
|
30
|
+
export declare function aesCbcNoPadDecrypt(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
31
|
+
export declare function hardHash(password: Uint8Array, salt: Uint8Array, userData: Uint8Array): Promise<Uint8Array>;
|
|
32
|
+
export declare function preparePdfEncryption(options: PdfEncryptOptions): Promise<PreparedEncryption>;
|
|
33
|
+
export declare function decryptBytes(fileKey: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
34
|
+
export declare function encryptObjectGraph(value: PdfValue, fileKey: Uint8Array): Promise<PdfValue>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { PdfHexString, PdfStream, dict, name } from "./objects.js";
|
|
2
|
+
//#region src/pdf/encryption.ts
|
|
3
|
+
var encoder = new TextEncoder();
|
|
4
|
+
var subtle = globalThis.crypto.subtle;
|
|
5
|
+
function permissionBits(p) {
|
|
6
|
+
let bits = -4;
|
|
7
|
+
const off = (bit) => bits &= ~(1 << bit - 1);
|
|
8
|
+
if (p?.printing === false) {
|
|
9
|
+
off(3);
|
|
10
|
+
off(12);
|
|
11
|
+
}
|
|
12
|
+
if (p?.modifying === false) off(4);
|
|
13
|
+
if (p?.copying === false) off(5);
|
|
14
|
+
if (p?.annotating === false) off(6);
|
|
15
|
+
if (p?.fillingForms === false) off(9);
|
|
16
|
+
if (p?.contentAccessibility === false) off(10);
|
|
17
|
+
if (p?.documentAssembly === false) off(11);
|
|
18
|
+
return bits | 0;
|
|
19
|
+
}
|
|
20
|
+
function passwordBytes(password) {
|
|
21
|
+
return encoder.encode(password).slice(0, 127);
|
|
22
|
+
}
|
|
23
|
+
function concat(...parts) {
|
|
24
|
+
const out = new Uint8Array(parts.reduce((s, p) => s + p.length, 0));
|
|
25
|
+
let off = 0;
|
|
26
|
+
for (const p of parts) {
|
|
27
|
+
out.set(p, off);
|
|
28
|
+
off += p.length;
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
async function sha(algo, data) {
|
|
33
|
+
return new Uint8Array(await subtle.digest(algo, data));
|
|
34
|
+
}
|
|
35
|
+
async function aesCbcNoPad(key, iv, data) {
|
|
36
|
+
const k = await subtle.importKey("raw", key, "AES-CBC", false, ["encrypt"]);
|
|
37
|
+
return new Uint8Array(await subtle.encrypt({
|
|
38
|
+
name: "AES-CBC",
|
|
39
|
+
iv
|
|
40
|
+
}, k, data)).slice(0, data.length);
|
|
41
|
+
}
|
|
42
|
+
async function hardHash(password, salt, userData) {
|
|
43
|
+
let k = await sha("SHA-256", concat(password, salt, userData));
|
|
44
|
+
for (let round = 0;; round++) {
|
|
45
|
+
const block = concat(password, k, userData);
|
|
46
|
+
const k1 = new Uint8Array(block.length * 64);
|
|
47
|
+
for (let i = 0; i < 64; i++) k1.set(block, i * block.length);
|
|
48
|
+
const e = await aesCbcNoPad(k.slice(0, 16), k.slice(16, 32), k1);
|
|
49
|
+
let mod = 0;
|
|
50
|
+
for (let i = 0; i < 16; i++) mod = (mod + e[i]) % 3;
|
|
51
|
+
k = (await sha(mod === 0 ? "SHA-256" : mod === 1 ? "SHA-384" : "SHA-512", e)).slice(0, 64);
|
|
52
|
+
if (round >= 63 && e[e.length - 1] <= round - 31) break;
|
|
53
|
+
}
|
|
54
|
+
return k.slice(0, 32);
|
|
55
|
+
}
|
|
56
|
+
async function preparePdfEncryption(options) {
|
|
57
|
+
const user = passwordBytes(options.userPassword ?? "");
|
|
58
|
+
const owner = passwordBytes(options.ownerPassword ?? options.userPassword ?? "");
|
|
59
|
+
const fileKey = new Uint8Array(32);
|
|
60
|
+
globalThis.crypto.getRandomValues(fileKey);
|
|
61
|
+
const salts = new Uint8Array(36);
|
|
62
|
+
globalThis.crypto.getRandomValues(salts);
|
|
63
|
+
const uvs = salts.slice(0, 8);
|
|
64
|
+
const uks = salts.slice(8, 16);
|
|
65
|
+
const ovs = salts.slice(16, 24);
|
|
66
|
+
const oks = salts.slice(24, 32);
|
|
67
|
+
const U = concat(await hardHash(user, uvs, new Uint8Array(0)), uvs, uks);
|
|
68
|
+
const UE = await aesCbcNoPad(await hardHash(user, uks, new Uint8Array(0)), new Uint8Array(16), fileKey);
|
|
69
|
+
const O = concat(await hardHash(owner, ovs, U), ovs, oks);
|
|
70
|
+
const OE = await aesCbcNoPad(await hardHash(owner, oks, U), new Uint8Array(16), fileKey);
|
|
71
|
+
const p = permissionBits(options.permissions);
|
|
72
|
+
const permsPlain = new Uint8Array(16);
|
|
73
|
+
new DataView(permsPlain.buffer).setInt32(0, p, true);
|
|
74
|
+
permsPlain.set([
|
|
75
|
+
255,
|
|
76
|
+
255,
|
|
77
|
+
255,
|
|
78
|
+
255
|
|
79
|
+
], 4);
|
|
80
|
+
permsPlain.set([
|
|
81
|
+
84,
|
|
82
|
+
97,
|
|
83
|
+
100,
|
|
84
|
+
98
|
|
85
|
+
], 8);
|
|
86
|
+
permsPlain.set(salts.slice(32, 36), 12);
|
|
87
|
+
const perms = await aesCbcNoPad(fileKey, new Uint8Array(16), permsPlain);
|
|
88
|
+
return {
|
|
89
|
+
fileKey,
|
|
90
|
+
encryptDict: dict({
|
|
91
|
+
Filter: name("Standard"),
|
|
92
|
+
V: 5,
|
|
93
|
+
R: 6,
|
|
94
|
+
Length: 256,
|
|
95
|
+
CF: dict({ StdCF: dict({
|
|
96
|
+
CFM: name("AESV3"),
|
|
97
|
+
AuthEvent: name("DocOpen"),
|
|
98
|
+
Length: 32
|
|
99
|
+
}) }),
|
|
100
|
+
StmF: name("StdCF"),
|
|
101
|
+
StrF: name("StdCF"),
|
|
102
|
+
U: new PdfHexString(U),
|
|
103
|
+
UE: new PdfHexString(UE),
|
|
104
|
+
O: new PdfHexString(O),
|
|
105
|
+
OE: new PdfHexString(OE),
|
|
106
|
+
P: p,
|
|
107
|
+
Perms: new PdfHexString(perms),
|
|
108
|
+
EncryptMetadata: true
|
|
109
|
+
})
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
async function encryptBytes(fileKey, data) {
|
|
113
|
+
const iv = new Uint8Array(16);
|
|
114
|
+
globalThis.crypto.getRandomValues(iv);
|
|
115
|
+
const k = await subtle.importKey("raw", fileKey, "AES-CBC", false, ["encrypt"]);
|
|
116
|
+
return concat(iv, new Uint8Array(await subtle.encrypt({
|
|
117
|
+
name: "AES-CBC",
|
|
118
|
+
iv
|
|
119
|
+
}, k, data)));
|
|
120
|
+
}
|
|
121
|
+
async function encryptObjectGraph(value, fileKey) {
|
|
122
|
+
if (typeof value === "string") return new PdfHexString(await encryptBytes(fileKey, encoder.encode(value)));
|
|
123
|
+
if (value instanceof PdfHexString) return new PdfHexString(await encryptBytes(fileKey, value.bytes));
|
|
124
|
+
if (value instanceof PdfStream) {
|
|
125
|
+
const data = await encryptBytes(fileKey, value.data);
|
|
126
|
+
const d = new Map(value.dict);
|
|
127
|
+
d.set("Length", data.length);
|
|
128
|
+
return new PdfStream(d, data);
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(value)) {
|
|
131
|
+
const out = [];
|
|
132
|
+
for (const v of value) out.push(await encryptObjectGraph(v, fileKey));
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
if (value instanceof Map) {
|
|
136
|
+
const out = /* @__PURE__ */ new Map();
|
|
137
|
+
for (const [k, v] of value) out.set(k, await encryptObjectGraph(v, fileKey));
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
export { encryptObjectGraph, preparePdfEncryption };
|
package/dist/esm/pdf/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { renderPlainTextPdf } from './text-page-renderer.js';
|
|
2
2
|
export type { TextRenderOptions, FontInput } from './text-page-renderer.js';
|
|
3
|
-
export { renderStyledPdf } from './styled-page-renderer.js';
|
|
3
|
+
export { renderStyledPdf, renderStyledPdfEncrypted } from './styled-page-renderer.js';
|
|
4
|
+
export type { PdfEncryptOptions, PdfPermissions } from './encryption.js';
|
|
4
5
|
export type { DocumentInfo, StyledRenderOptions, PdfALevel } from './styled-page-renderer.js';
|
|
5
6
|
export { signPdf } from './signature.js';
|
|
6
7
|
export type { SignaturePlaceholder, SignerCredentials, SignatureOptions } from './signature.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PdfRef } from './objects.js';
|
|
2
2
|
import { PdfDocument } from './writer.js';
|
|
3
|
-
export type StructType = 'Document' | 'Part' | 'Sect' | 'H1' | 'H2' | 'H3' | 'H4' | 'H5' | 'H6' | 'P' | 'L' | 'LI' | 'Lbl' | 'LBody' | 'Table' | 'TR' | 'TH' | 'TD' | 'Caption' | 'Figure' | 'Span';
|
|
3
|
+
export type StructType = 'Document' | 'Part' | 'Sect' | 'H1' | 'H2' | 'H3' | 'H4' | 'H5' | 'H6' | 'P' | 'L' | 'LI' | 'Lbl' | 'LBody' | 'Table' | 'TR' | 'TH' | 'TD' | 'Caption' | 'Figure' | 'Link' | 'Note' | 'Span';
|
|
4
4
|
interface Mcref {
|
|
5
5
|
readonly pageIndex: number;
|
|
6
6
|
readonly mcid: number;
|
|
@@ -10,6 +10,10 @@ export declare class StructNode {
|
|
|
10
10
|
type: StructType;
|
|
11
11
|
readonly children: Array<StructNode>;
|
|
12
12
|
readonly mcrefs: Array<Mcref>;
|
|
13
|
+
readonly objrs: Array<{
|
|
14
|
+
readonly annotRef: PdfRef;
|
|
15
|
+
readonly pageIndex: number;
|
|
16
|
+
}>;
|
|
13
17
|
parent: StructNode | null;
|
|
14
18
|
ref: PdfRef | null;
|
|
15
19
|
alt: string | null;
|
|
@@ -26,6 +30,8 @@ export declare class StructTreeBuilder {
|
|
|
26
30
|
create(type: StructType, parent: StructNode | null): StructNode;
|
|
27
31
|
node(id: number): StructNode;
|
|
28
32
|
addMcref(nodeId: number, pageIndex: number, mcid: number): void;
|
|
33
|
+
private readonly annotParents;
|
|
34
|
+
addAnnotParent(key: number, nodeId: number): void;
|
|
29
35
|
emit(doc: PdfDocument, pageRefs: ReadonlyArray<PdfRef>): PdfRef;
|
|
30
36
|
}
|
|
31
37
|
export {};
|
|
@@ -3,6 +3,7 @@ import { PDF_NULL, dict, name, unicodeString } from "./objects.js";
|
|
|
3
3
|
var StructNode = class {
|
|
4
4
|
children = [];
|
|
5
5
|
mcrefs = [];
|
|
6
|
+
objrs = [];
|
|
6
7
|
parent = null;
|
|
7
8
|
ref = null;
|
|
8
9
|
alt = null;
|
|
@@ -39,6 +40,13 @@ var StructTreeBuilder = class {
|
|
|
39
40
|
mcid
|
|
40
41
|
});
|
|
41
42
|
}
|
|
43
|
+
annotParents = [];
|
|
44
|
+
addAnnotParent(key, nodeId) {
|
|
45
|
+
this.annotParents.push({
|
|
46
|
+
key,
|
|
47
|
+
nodeId
|
|
48
|
+
});
|
|
49
|
+
}
|
|
42
50
|
emit(doc, pageRefs) {
|
|
43
51
|
const order = [];
|
|
44
52
|
const walk = (n) => {
|
|
@@ -52,6 +60,7 @@ var StructTreeBuilder = class {
|
|
|
52
60
|
Type: name("StructElem"),
|
|
53
61
|
S: name(n.type)
|
|
54
62
|
});
|
|
63
|
+
if (n.type === "Note") d.set("ID", `note-${n.id}`);
|
|
55
64
|
n.ref = doc.add(d);
|
|
56
65
|
dicts.set(n.id, d);
|
|
57
66
|
}
|
|
@@ -60,7 +69,7 @@ var StructTreeBuilder = class {
|
|
|
60
69
|
for (const n of order) {
|
|
61
70
|
const d = dicts.get(n.id);
|
|
62
71
|
d.set("P", n.parent ? n.parent.ref : rootRef);
|
|
63
|
-
const firstPage = n.mcrefs.length > 0 ? n.mcrefs[0].pageIndex : void 0;
|
|
72
|
+
const firstPage = n.mcrefs.length > 0 ? n.mcrefs[0].pageIndex : n.objrs.length > 0 ? n.objrs[0].pageIndex : void 0;
|
|
64
73
|
if (firstPage !== void 0) d.set("Pg", pageRefs[firstPage]);
|
|
65
74
|
const kids = [];
|
|
66
75
|
for (const c of n.children) kids.push(c.ref);
|
|
@@ -69,6 +78,11 @@ var StructTreeBuilder = class {
|
|
|
69
78
|
Pg: pageRefs[m.pageIndex],
|
|
70
79
|
MCID: m.mcid
|
|
71
80
|
}));
|
|
81
|
+
for (const o of n.objrs) kids.push(dict({
|
|
82
|
+
Type: name("OBJR"),
|
|
83
|
+
Pg: pageRefs[o.pageIndex],
|
|
84
|
+
Obj: o.annotRef
|
|
85
|
+
}));
|
|
72
86
|
if (kids.length === 1) d.set("K", kids[0]);
|
|
73
87
|
else if (kids.length > 1) d.set("K", kids);
|
|
74
88
|
if (n.alt !== null) d.set("Alt", unicodeString(n.alt));
|
|
@@ -90,14 +104,16 @@ var StructTreeBuilder = class {
|
|
|
90
104
|
}
|
|
91
105
|
arr[m.mcid] = n.ref;
|
|
92
106
|
}
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
for (const k of sortedKeys) {
|
|
96
|
-
const arr = perPage.get(k);
|
|
107
|
+
const entries = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const [k, arr] of perPage) {
|
|
97
109
|
const dense = [];
|
|
98
110
|
for (let i = 0; i < arr.length; i++) dense.push(arr[i] ?? PDF_NULL);
|
|
99
|
-
|
|
111
|
+
entries.set(k, dense);
|
|
100
112
|
}
|
|
113
|
+
for (const a of this.annotParents) entries.set(a.key, this.node(a.nodeId).ref);
|
|
114
|
+
const sortedKeys = [...entries.keys()].sort((a, b) => a - b);
|
|
115
|
+
const nums = [];
|
|
116
|
+
for (const k of sortedKeys) nums.push(k, entries.get(k));
|
|
101
117
|
const parentTreeRef = doc.add(dict({ Nums: nums }));
|
|
102
118
|
rootDict.set("K", this.root.ref);
|
|
103
119
|
rootDict.set("ParentTree", parentTreeRef);
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { LaidOutPdfDocument, StyledRenderOptions } from '../layout/styled-layout.js';
|
|
2
2
|
import { PdfDocument } from './writer.js';
|
|
3
|
-
|
|
3
|
+
import { PdfEncryptOptions } from './encryption.js';
|
|
4
|
+
type EmitOptions = Pick<StyledRenderOptions, 'attachments' | 'info' | 'language' | 'pdfUA' | 'signaturePlaceholder'>;
|
|
4
5
|
export declare function emitStyledPdf(laid: LaidOutPdfDocument, options: EmitOptions, doc: PdfDocument): Uint8Array;
|
|
6
|
+
export declare function emitStyledPdfEncrypted(laid: LaidOutPdfDocument, options: EmitOptions, doc: PdfDocument, encrypt: PdfEncryptOptions): Promise<Uint8Array>;
|
|
5
7
|
export interface PageTagging {
|
|
6
8
|
next: number;
|
|
7
9
|
assigned: boolean;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { dict, name, ref, stream, unicodeString } from "./objects.js";
|
|
2
2
|
import { embedTtfFont } from "./cid-font.js";
|
|
3
|
+
import { preparePdfEncryption } from "./encryption.js";
|
|
3
4
|
import { reorderVisual } from "../core/bidi/algorithm.js";
|
|
4
5
|
import { reverseByCodePoint } from "../core/bidi/index.js";
|
|
5
6
|
import "../layout/styled-layout.js";
|
|
6
7
|
import { paintPlan } from "../layout/page-doc.js";
|
|
7
8
|
import { emitVectorShape } from "./vector-graphics.js";
|
|
9
|
+
import { sanitizeHref } from "../core/links.js";
|
|
8
10
|
import { embedAssociatedFile } from "./embedded-file.js";
|
|
9
11
|
import { buildSrgbIccProfile } from "./icc-profile.js";
|
|
10
12
|
import { addImage } from "./image-xobject.js";
|
|
@@ -13,8 +15,27 @@ import { buildXmpPacket } from "./xmp.js";
|
|
|
13
15
|
//#region src/pdf/styled-page-emitter.ts
|
|
14
16
|
var encoder = new TextEncoder();
|
|
15
17
|
function emitStyledPdf(laid, options, doc) {
|
|
18
|
+
const a = assembleStyledPdf(laid, options, doc);
|
|
19
|
+
return doc.build(a.catalogRef, a.infoRef, a.buildOptions);
|
|
20
|
+
}
|
|
21
|
+
async function emitStyledPdfEncrypted(laid, options, doc, encrypt) {
|
|
22
|
+
const a = assembleStyledPdf(laid, options, doc);
|
|
23
|
+
const prepared = await preparePdfEncryption(encrypt);
|
|
24
|
+
await doc.encryptAll(prepared.fileKey);
|
|
25
|
+
const encryptRef = doc.add(prepared.encryptDict);
|
|
26
|
+
return doc.build(a.catalogRef, a.infoRef, {
|
|
27
|
+
...a.buildOptions,
|
|
28
|
+
id: true,
|
|
29
|
+
encrypt: encryptRef
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function assembleStyledPdf(laid, options, doc) {
|
|
16
33
|
const { pages: renderedPages } = laid;
|
|
17
34
|
const { structBuilder, sectionCtxs, pdfaProfile, tagged } = laid.pdf;
|
|
35
|
+
const docInfo = options.pdfUA && !options.info?.title ? {
|
|
36
|
+
...options.info,
|
|
37
|
+
title: "Untitled document"
|
|
38
|
+
} : options.info;
|
|
18
39
|
const embeddedFonts = embedFontResources(doc, laid, pdfaProfile);
|
|
19
40
|
const embeddedImages = embedImageResources(doc, laid);
|
|
20
41
|
const pagesDict = dict({
|
|
@@ -24,6 +45,8 @@ function emitStyledPdf(laid, options, doc) {
|
|
|
24
45
|
});
|
|
25
46
|
const pagesRef = doc.add(pagesDict);
|
|
26
47
|
let firstPageDict;
|
|
48
|
+
let firstPageAnnots;
|
|
49
|
+
let annotParentCount = 0;
|
|
27
50
|
const fontResourceDict = buildFontResourceDict(laid.fontResources, embeddedFonts);
|
|
28
51
|
const xobjectResourceDict = buildXObjectResourceDict(laid.imageResources, embeddedImages);
|
|
29
52
|
const resourcesDict = dict({
|
|
@@ -43,6 +66,7 @@ function emitStyledPdf(laid, options, doc) {
|
|
|
43
66
|
});
|
|
44
67
|
}
|
|
45
68
|
const pageRefs = [];
|
|
69
|
+
const referencedAnchors = /* @__PURE__ */ new Set();
|
|
46
70
|
renderedPages.forEach((page, pageIndex) => {
|
|
47
71
|
let pageTagging;
|
|
48
72
|
if (structBuilder) {
|
|
@@ -54,7 +78,7 @@ function emitStyledPdf(laid, options, doc) {
|
|
|
54
78
|
tagFor: (structId) => builder.node(structId).type
|
|
55
79
|
};
|
|
56
80
|
}
|
|
57
|
-
const contentBytes = emitPageContent(page, pageTagging);
|
|
81
|
+
const { content: contentBytes, links } = emitPageContent(page, pageTagging);
|
|
58
82
|
const contentsRef = doc.add(stream({}, contentBytes));
|
|
59
83
|
const pageEntries = {
|
|
60
84
|
Type: name("Page"),
|
|
@@ -68,6 +92,53 @@ function emitStyledPdf(laid, options, doc) {
|
|
|
68
92
|
Resources: resourcesDict,
|
|
69
93
|
Contents: ref(contentsRef.id)
|
|
70
94
|
};
|
|
95
|
+
if (links.length > 0) {
|
|
96
|
+
const annots = [];
|
|
97
|
+
for (const l of links) {
|
|
98
|
+
if (structBuilder && l.structId === void 0) continue;
|
|
99
|
+
const entries = {
|
|
100
|
+
Type: name("Annot"),
|
|
101
|
+
Subtype: name("Link"),
|
|
102
|
+
Rect: [
|
|
103
|
+
l.rect[0],
|
|
104
|
+
l.rect[1],
|
|
105
|
+
l.rect[2],
|
|
106
|
+
l.rect[3]
|
|
107
|
+
],
|
|
108
|
+
Border: [
|
|
109
|
+
0,
|
|
110
|
+
0,
|
|
111
|
+
0
|
|
112
|
+
],
|
|
113
|
+
F: 4,
|
|
114
|
+
A: l.href !== void 0 ? dict({
|
|
115
|
+
S: name("URI"),
|
|
116
|
+
URI: l.href
|
|
117
|
+
}) : dict({
|
|
118
|
+
S: name("GoTo"),
|
|
119
|
+
D: l.anchor ?? ""
|
|
120
|
+
}),
|
|
121
|
+
Contents: l.text !== "" ? l.text : l.href ?? l.anchor ?? ""
|
|
122
|
+
};
|
|
123
|
+
if (l.anchor !== void 0) referencedAnchors.add(l.anchor);
|
|
124
|
+
if (structBuilder && l.structId !== void 0) entries["StructParent"] = renderedPages.length + annotParentCount;
|
|
125
|
+
const annotRef = doc.add(dict(entries));
|
|
126
|
+
if (structBuilder && l.structId !== void 0) {
|
|
127
|
+
const linkNode = structBuilder.create("Link", structBuilder.node(l.structId));
|
|
128
|
+
linkNode.objrs.push({
|
|
129
|
+
annotRef,
|
|
130
|
+
pageIndex
|
|
131
|
+
});
|
|
132
|
+
structBuilder.addAnnotParent(renderedPages.length + annotParentCount, linkNode.id);
|
|
133
|
+
annotParentCount++;
|
|
134
|
+
}
|
|
135
|
+
annots.push(ref(annotRef.id));
|
|
136
|
+
}
|
|
137
|
+
if (annots.length > 0) {
|
|
138
|
+
pageEntries["Annots"] = annots;
|
|
139
|
+
if (pageIndex === 0) firstPageAnnots = annots;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
71
142
|
if (transparencyGroup) pageEntries["Group"] = transparencyGroup;
|
|
72
143
|
if (pageTagging?.assigned) {
|
|
73
144
|
pageEntries["StructParents"] = pageIndex;
|
|
@@ -98,29 +169,28 @@ function emitStyledPdf(laid, options, doc) {
|
|
|
98
169
|
}
|
|
99
170
|
pagesDict.set("Count", pageRefs.length);
|
|
100
171
|
pagesDict.set("Kids", pageRefs);
|
|
172
|
+
const namesTreeEntries = {};
|
|
101
173
|
const catalogEntries = {
|
|
102
174
|
Type: name("Catalog"),
|
|
103
175
|
Pages: ref(pagesRef.id)
|
|
104
176
|
};
|
|
105
|
-
if (pdfaProfile) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}, buildXmpPacket(xmpFromInfo(options.info, pdfaProfile)))).id);
|
|
117
|
-
}
|
|
177
|
+
if (pdfaProfile) catalogEntries["OutputIntents"] = [ref(doc.add(dict({
|
|
178
|
+
Type: name("OutputIntent"),
|
|
179
|
+
S: name("GTS_PDFA1"),
|
|
180
|
+
OutputConditionIdentifier: "sRGB",
|
|
181
|
+
Info: "sRGB IEC61966-2.1",
|
|
182
|
+
DestOutputProfile: ref(pdfaIccRef.id)
|
|
183
|
+
})).id)];
|
|
184
|
+
if (pdfaProfile || options.pdfUA) catalogEntries["Metadata"] = ref(doc.add(stream({
|
|
185
|
+
Type: name("Metadata"),
|
|
186
|
+
Subtype: name("XML")
|
|
187
|
+
}, buildXmpPacket(xmpFromInfo(docInfo, pdfaProfile, options.pdfUA === true)))).id);
|
|
118
188
|
if (tagged && structBuilder) {
|
|
119
189
|
const structRootRef = structBuilder.emit(doc, pageRefs);
|
|
120
190
|
catalogEntries["MarkInfo"] = dict({ Marked: true });
|
|
121
191
|
catalogEntries["StructTreeRoot"] = ref(structRootRef.id);
|
|
122
192
|
catalogEntries["Lang"] = options.language ?? "en-US";
|
|
123
|
-
if (
|
|
193
|
+
if (docInfo?.title) catalogEntries["ViewerPreferences"] = dict({ DisplayDocTitle: true });
|
|
124
194
|
}
|
|
125
195
|
if (options.attachments?.length && (!pdfaProfile || pdfaProfile.part === 3)) {
|
|
126
196
|
const embedded = options.attachments.map((file) => ({
|
|
@@ -131,25 +201,48 @@ function emitStyledPdf(laid, options, doc) {
|
|
|
131
201
|
const sorted = [...embedded].sort((a, b) => a.file.name < b.file.name ? -1 : a.file.name > b.file.name ? 1 : 0);
|
|
132
202
|
const names = [];
|
|
133
203
|
for (const e of sorted) names.push(e.file.name, ref(e.ref.id));
|
|
134
|
-
|
|
204
|
+
namesTreeEntries["EmbeddedFiles"] = ref(doc.add(dict({ Names: names })).id);
|
|
135
205
|
}
|
|
206
|
+
const bookmarkDests = laid.pdf.bookmarks;
|
|
207
|
+
const destNames = [...referencedAnchors].filter((n) => bookmarkDests.has(n)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
208
|
+
if (destNames.length > 0) {
|
|
209
|
+
const names = [];
|
|
210
|
+
for (const destName of destNames) {
|
|
211
|
+
const pos = bookmarkDests.get(destName);
|
|
212
|
+
const pageRef = pageRefs[pos.pageIdx];
|
|
213
|
+
if (!pageRef) continue;
|
|
214
|
+
names.push(destName, [
|
|
215
|
+
ref(pageRef.id),
|
|
216
|
+
name("FitH"),
|
|
217
|
+
pos.yTopPt
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
220
|
+
namesTreeEntries["Dests"] = ref(doc.add(dict({ Names: names })).id);
|
|
221
|
+
}
|
|
222
|
+
if (Object.keys(namesTreeEntries).length > 0) catalogEntries["Names"] = dict(namesTreeEntries);
|
|
136
223
|
if (options.signaturePlaceholder && firstPageDict) {
|
|
137
224
|
const { fieldRef, acroForm } = addSignaturePlaceholder(doc, options.signaturePlaceholder, pageRefs[0]);
|
|
138
|
-
|
|
225
|
+
if (firstPageAnnots) firstPageAnnots.push(ref(fieldRef.id));
|
|
226
|
+
else firstPageDict.set("Annots", [ref(fieldRef.id)]);
|
|
139
227
|
catalogEntries["AcroForm"] = acroForm;
|
|
140
228
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
229
|
+
return {
|
|
230
|
+
catalogRef: doc.add(dict(catalogEntries)),
|
|
231
|
+
infoRef: buildInfoDict(doc, docInfo),
|
|
232
|
+
buildOptions: { ...pdfaProfile ? {
|
|
233
|
+
version: pdfaProfile.version,
|
|
234
|
+
id: true
|
|
235
|
+
} : {} }
|
|
236
|
+
};
|
|
147
237
|
}
|
|
148
238
|
var DEFAULT_PRODUCER = "Ream";
|
|
149
|
-
function xmpFromInfo(info, p) {
|
|
239
|
+
function xmpFromInfo(info, p, pdfUA = false) {
|
|
150
240
|
return {
|
|
151
|
-
|
|
152
|
-
|
|
241
|
+
...p ? {
|
|
242
|
+
pdfaPart: String(p.part),
|
|
243
|
+
pdfaConformance: p.level.toUpperCase()
|
|
244
|
+
} : {},
|
|
245
|
+
...pdfUA ? { pdfuaPart: "1" } : {},
|
|
153
246
|
producer: info?.producer ?? DEFAULT_PRODUCER,
|
|
154
247
|
...info?.title ? { title: info.title } : {},
|
|
155
248
|
...info?.author ? { author: info.author } : {},
|
|
@@ -195,14 +288,14 @@ function defaultPageCtx() {
|
|
|
195
288
|
contentWidth: 451,
|
|
196
289
|
pageContentHeight: 698,
|
|
197
290
|
headerSet: {
|
|
198
|
-
default: [],
|
|
199
|
-
first: [],
|
|
200
|
-
even: []
|
|
291
|
+
default: { commands: [] },
|
|
292
|
+
first: { commands: [] },
|
|
293
|
+
even: { commands: [] }
|
|
201
294
|
},
|
|
202
295
|
footerSet: {
|
|
203
|
-
default: [],
|
|
204
|
-
first: [],
|
|
205
|
-
even: []
|
|
296
|
+
default: { commands: [] },
|
|
297
|
+
first: { commands: [] },
|
|
298
|
+
even: { commands: [] }
|
|
206
299
|
},
|
|
207
300
|
titlePg: false,
|
|
208
301
|
evenAndOddHeaders: false
|
|
@@ -270,7 +363,11 @@ function emitTaggedRuns(out, cmds, tagging, body) {
|
|
|
270
363
|
}
|
|
271
364
|
function emitPageContent(page, tagging) {
|
|
272
365
|
const commands = page.commands;
|
|
273
|
-
|
|
366
|
+
const links = [];
|
|
367
|
+
if (commands.length === 0) return {
|
|
368
|
+
content: new Uint8Array(0),
|
|
369
|
+
links
|
|
370
|
+
};
|
|
274
371
|
const H = page.height;
|
|
275
372
|
const out = [];
|
|
276
373
|
const plan = paintPlan(commands);
|
|
@@ -453,6 +550,47 @@ function emitPageContent(page, tagging) {
|
|
|
453
550
|
const line = cmd.line;
|
|
454
551
|
const originX = cmd.originX;
|
|
455
552
|
const baselineY = H - cmd.baselineY;
|
|
553
|
+
const lineFs = line.maxFontSizePt || 12;
|
|
554
|
+
const linkAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
|
|
555
|
+
const linkDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
|
|
556
|
+
let linkHref;
|
|
557
|
+
let linkAnchor;
|
|
558
|
+
let linkText = "";
|
|
559
|
+
let linkX0 = 0;
|
|
560
|
+
let linkX1 = 0;
|
|
561
|
+
const flushLink = () => {
|
|
562
|
+
if (linkHref === void 0 && linkAnchor === void 0) return;
|
|
563
|
+
const safe = linkHref !== void 0 ? sanitizeHref(linkHref) : void 0;
|
|
564
|
+
const target = safe !== void 0 ? { href: safe } : linkAnchor !== void 0 ? { anchor: linkAnchor } : void 0;
|
|
565
|
+
if (target) links.push({
|
|
566
|
+
...target,
|
|
567
|
+
text: linkText.trim(),
|
|
568
|
+
rect: [
|
|
569
|
+
linkX0,
|
|
570
|
+
baselineY - linkDescent,
|
|
571
|
+
linkX1,
|
|
572
|
+
baselineY + linkAscent
|
|
573
|
+
],
|
|
574
|
+
...cmd.structId !== void 0 ? { structId: cmd.structId } : {}
|
|
575
|
+
});
|
|
576
|
+
linkHref = void 0;
|
|
577
|
+
linkAnchor = void 0;
|
|
578
|
+
linkText = "";
|
|
579
|
+
};
|
|
580
|
+
const trackLink = (href, anchor, text, x0, x1) => {
|
|
581
|
+
if (href !== linkHref || anchor !== linkAnchor) {
|
|
582
|
+
flushLink();
|
|
583
|
+
if (href !== void 0 || anchor !== void 0) {
|
|
584
|
+
linkHref = href;
|
|
585
|
+
linkAnchor = anchor;
|
|
586
|
+
linkX0 = x0;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (href !== void 0 || anchor !== void 0) {
|
|
590
|
+
linkX1 = x1;
|
|
591
|
+
linkText += text;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
456
594
|
const extraPerSpace = computeJustifyExtra(line);
|
|
457
595
|
const hasImageToken = line.tokens.some((t) => t.kind === "image");
|
|
458
596
|
const hasMathToken = line.tokens.some((t) => t.kind === "math");
|
|
@@ -467,11 +605,13 @@ function emitPageContent(page, tagging) {
|
|
|
467
605
|
for (const ti of order) {
|
|
468
606
|
const tok = line.tokens[ti];
|
|
469
607
|
if (tok.kind === "image") {
|
|
608
|
+
flushLink();
|
|
470
609
|
emitImageToken(tok, x, baselineY);
|
|
471
610
|
x += tok.widthPt;
|
|
472
611
|
continue;
|
|
473
612
|
}
|
|
474
613
|
if (tok.kind === "math") {
|
|
614
|
+
flushLink();
|
|
475
615
|
emitMathToken(tok, x, baselineY);
|
|
476
616
|
x += tok.widthPt;
|
|
477
617
|
continue;
|
|
@@ -483,21 +623,28 @@ function emitPageContent(page, tagging) {
|
|
|
483
623
|
switchFontIfNeeded(tok);
|
|
484
624
|
out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY)} Tm`);
|
|
485
625
|
out.push(`<${encodeToken(tok)}> Tj`);
|
|
626
|
+
const tokenX0 = x;
|
|
486
627
|
x += tok.widthPt;
|
|
487
628
|
if (tok.isSpace) x += extraPerSpace;
|
|
629
|
+
trackLink(tok.href, tok.anchor, tok.text, tokenX0, x);
|
|
488
630
|
}
|
|
631
|
+
flushLink();
|
|
489
632
|
} else {
|
|
490
633
|
if (!inBT) {
|
|
491
634
|
out.push("BT");
|
|
492
635
|
inBT = true;
|
|
493
636
|
}
|
|
494
637
|
out.push(`1 0 0 1 ${formatNumber(originX)} ${formatNumber(baselineY)} Tm`);
|
|
638
|
+
let x = originX;
|
|
495
639
|
for (const tok of line.tokens) {
|
|
496
640
|
if (tok.kind !== "text") continue;
|
|
497
641
|
switchFontIfNeeded(tok);
|
|
498
642
|
const hex = tok.font.measure.encodeTextAsCidHex(tok.text);
|
|
499
643
|
out.push(`<${hex}> Tj`);
|
|
644
|
+
trackLink(tok.href, tok.anchor, tok.text, x, x + tok.widthPt);
|
|
645
|
+
x += tok.widthPt;
|
|
500
646
|
}
|
|
647
|
+
flushLink();
|
|
501
648
|
}
|
|
502
649
|
};
|
|
503
650
|
if (tagging) for (const cmd of lines) {
|
|
@@ -527,7 +674,10 @@ function emitPageContent(page, tagging) {
|
|
|
527
674
|
if (inBT) out.push("ET");
|
|
528
675
|
}
|
|
529
676
|
}
|
|
530
|
-
return
|
|
677
|
+
return {
|
|
678
|
+
content: encoder.encode(out.join("\n")),
|
|
679
|
+
links
|
|
680
|
+
};
|
|
531
681
|
}
|
|
532
682
|
function lineVisualOrder(line) {
|
|
533
683
|
return reorderVisual(line.tokens.map((t) => t.bidiLevel));
|
|
@@ -554,4 +704,4 @@ function formatNumber(n) {
|
|
|
554
704
|
return n.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
555
705
|
}
|
|
556
706
|
//#endregion
|
|
557
|
-
export { emitStyledPdf };
|
|
707
|
+
export { emitStyledPdf, emitStyledPdfEncrypted };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BodyElement } from '../core/document-model/index.js';
|
|
2
2
|
import { StyledRenderOptions } from '../layout/styled-layout.js';
|
|
3
3
|
export declare function renderStyledPdf(body: ReadonlyArray<BodyElement>, options: StyledRenderOptions): Uint8Array;
|
|
4
|
+
export declare function renderStyledPdfEncrypted(body: ReadonlyArray<BodyElement>, options: StyledRenderOptions): Promise<Uint8Array>;
|
|
4
5
|
export * from '../layout/page-doc.js';
|
|
5
6
|
export * from '../layout/styled-layout.js';
|