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.
Files changed (68) hide show
  1. package/README.md +11 -5
  2. package/dist/esm/{layout → core}/arc-to-bezier.d.ts +1 -1
  3. package/dist/esm/{layout → core}/arc-to-bezier.js +2 -2
  4. package/dist/esm/core/converter/project.js +2 -0
  5. package/dist/esm/core/converter/ream.js +4 -3
  6. package/dist/esm/core/document-model/index.d.ts +1 -1
  7. package/dist/esm/core/document-model/types.d.ts +60 -0
  8. package/dist/esm/{layout → core/drawingml}/chart-geometry.d.ts +2 -2
  9. package/dist/esm/{layout → core/drawingml}/chart-geometry.js +79 -117
  10. package/dist/esm/core/drawingml/chart-parser.d.ts +3 -0
  11. package/dist/esm/core/drawingml/chart-parser.js +28 -1
  12. package/dist/esm/{layout → core/drawingml}/preset-geometry.d.ts +2 -2
  13. package/dist/esm/{layout → core/drawingml}/preset-geometry.js +3 -3
  14. package/dist/esm/core/drawingml/shape-render.d.ts +8 -0
  15. package/dist/esm/core/drawingml/shape-render.js +71 -0
  16. package/dist/esm/core/ir/features.d.ts +1 -0
  17. package/dist/esm/core/ir/features.js +1 -0
  18. package/dist/esm/core/ir/flow.d.ts +3 -0
  19. package/dist/esm/core/links.d.ts +6 -0
  20. package/dist/esm/core/links.js +19 -0
  21. package/dist/esm/core/numbering/apply.js +5 -2
  22. package/dist/esm/core/opc/index.d.ts +1 -0
  23. package/dist/esm/core/opc/package.js +3 -2
  24. package/dist/esm/core/opc/relationship-types.d.ts +1 -0
  25. package/dist/esm/core/opc/relationship-types.js +5 -2
  26. package/dist/esm/core/style-cascade/index.d.ts +1 -0
  27. package/dist/esm/core/style-cascade/index.js +1 -0
  28. package/dist/esm/core/style-cascade/table.d.ts +2 -0
  29. package/dist/esm/core/style-cascade/table.js +161 -0
  30. package/dist/esm/core/vector.d.ts +2 -0
  31. package/dist/esm/core/vector.js +19 -1
  32. package/dist/esm/excel/print-model.d.ts +3 -0
  33. package/dist/esm/excel/print-model.js +4 -8
  34. package/dist/esm/excel/sheet-drawing.d.ts +9 -0
  35. package/dist/esm/excel/sheet-drawing.js +113 -0
  36. package/dist/esm/excel/worksheet-parser.d.ts +1 -0
  37. package/dist/esm/excel/worksheet-parser.js +4 -1
  38. package/dist/esm/excel/xlsx-reader.js +41 -0
  39. package/dist/esm/excel/xlsx-to-pdf.js +25 -18
  40. package/dist/esm/html/html-writer.js +188 -16
  41. package/dist/esm/index.d.ts +1 -0
  42. package/dist/esm/layout/page-doc.d.ts +4 -0
  43. package/dist/esm/layout/styled-layout.d.ts +21 -3
  44. package/dist/esm/layout/styled-layout.js +745 -259
  45. package/dist/esm/pdf/encryption.d.ts +34 -0
  46. package/dist/esm/pdf/encryption.js +143 -0
  47. package/dist/esm/pdf/index.d.ts +2 -1
  48. package/dist/esm/pdf/struct-tree.d.ts +7 -1
  49. package/dist/esm/pdf/struct-tree.js +22 -6
  50. package/dist/esm/pdf/styled-page-emitter.d.ts +3 -1
  51. package/dist/esm/pdf/styled-page-emitter.js +185 -35
  52. package/dist/esm/pdf/styled-page-renderer.d.ts +1 -0
  53. package/dist/esm/pdf/styled-page-renderer.js +17 -2
  54. package/dist/esm/pdf/writer.d.ts +2 -0
  55. package/dist/esm/pdf/writer.js +6 -1
  56. package/dist/esm/pdf/xmp.d.ts +1 -0
  57. package/dist/esm/pdf/xmp.js +4 -0
  58. package/dist/esm/svg/svg-writer.js +2 -6
  59. package/dist/esm/word/document-parser.d.ts +3 -0
  60. package/dist/esm/word/document-parser.js +214 -23
  61. package/dist/esm/word/docx-reader.js +37 -15
  62. package/dist/esm/word/docx-to-pdf.js +23 -13
  63. package/dist/esm/word/drawing-parser.d.ts +4 -1
  64. package/dist/esm/word/drawing-parser.js +53 -2
  65. package/dist/esm/word/index.d.ts +2 -2
  66. package/dist/esm/word/styles-parser.js +134 -1
  67. package/dist/esm/word/table-parser.js +35 -0
  68. 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 };
@@ -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 sortedKeys = [...perPage.keys()].sort((a, b) => a - b);
94
- const nums = [];
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
- nums.push(k, dense);
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
- type EmitOptions = Pick<StyledRenderOptions, 'attachments' | 'info' | 'language' | 'signaturePlaceholder'>;
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
- catalogEntries["OutputIntents"] = [ref(doc.add(dict({
107
- Type: name("OutputIntent"),
108
- S: name("GTS_PDFA1"),
109
- OutputConditionIdentifier: "sRGB",
110
- Info: "sRGB IEC61966-2.1",
111
- DestOutputProfile: ref(pdfaIccRef.id)
112
- })).id)];
113
- catalogEntries["Metadata"] = ref(doc.add(stream({
114
- Type: name("Metadata"),
115
- Subtype: name("XML")
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 (options.info?.title) catalogEntries["ViewerPreferences"] = dict({ DisplayDocTitle: true });
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
- catalogEntries["Names"] = dict({ EmbeddedFiles: ref(doc.add(dict({ Names: names })).id) });
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
- firstPageDict.set("Annots", [ref(fieldRef.id)]);
225
+ if (firstPageAnnots) firstPageAnnots.push(ref(fieldRef.id));
226
+ else firstPageDict.set("Annots", [ref(fieldRef.id)]);
139
227
  catalogEntries["AcroForm"] = acroForm;
140
228
  }
141
- const catalogRef = doc.add(dict(catalogEntries));
142
- const infoRef = buildInfoDict(doc, options.info);
143
- return doc.build(catalogRef, infoRef, { ...pdfaProfile ? {
144
- version: pdfaProfile.version,
145
- id: true
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
- pdfaPart: p ? String(p.part) : "1",
152
- pdfaConformance: p ? p.level.toUpperCase() : "B",
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
- if (commands.length === 0) return new Uint8Array(0);
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 encoder.encode(out.join("\n"));
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';