@jasy/pdf 1.0.0-alpha.2 → 1.0.0-alpha.4

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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -14
  3. package/dist/api/args.d.ts +1 -1
  4. package/dist/api/args.js +2 -5
  5. package/dist/api/color.d.ts +4 -4
  6. package/dist/api/color.js +11 -17
  7. package/dist/api/content.d.ts +8 -8
  8. package/dist/api/content.js +23 -24
  9. package/dist/api/descriptor.d.ts +2 -2
  10. package/dist/api/descriptor.js +75 -31
  11. package/dist/api/index.d.ts +8 -8
  12. package/dist/api/index.js +8 -24
  13. package/dist/api/insets.js +4 -8
  14. package/dist/api/layout.d.ts +18 -16
  15. package/dist/api/layout.js +41 -52
  16. package/dist/api/structure.d.ts +65 -13
  17. package/dist/api/structure.js +140 -88
  18. package/dist/api/table.d.ts +5 -5
  19. package/dist/api/table.js +28 -24
  20. package/dist/api/text.d.ts +27 -2
  21. package/dist/api/text.js +45 -27
  22. package/dist/assets/font-data.d.ts +2 -0
  23. package/dist/assets/font-data.js +21 -0
  24. package/dist/assets/font-data.ts +37 -0
  25. package/dist/common/color.js +1 -5
  26. package/dist/constants/page-sizes.js +3 -6
  27. package/dist/constants/pdf-parts.js +1 -4
  28. package/dist/crypto/security-handler.d.ts +46 -0
  29. package/dist/crypto/security-handler.js +129 -0
  30. package/dist/crypto/webcrypto.d.ts +11 -0
  31. package/dist/crypto/webcrypto.js +62 -0
  32. package/dist/elements/container-element.d.ts +4 -4
  33. package/dist/elements/container-element.js +9 -13
  34. package/dist/elements/image-element.d.ts +18 -2
  35. package/dist/elements/image-element.js +81 -105
  36. package/dist/elements/index.d.ts +12 -11
  37. package/dist/elements/index.js +12 -29
  38. package/dist/elements/layout/default-text-style-element.d.ts +30 -0
  39. package/dist/elements/layout/default-text-style-element.js +47 -0
  40. package/dist/elements/layout/deferred-element.d.ts +3 -3
  41. package/dist/elements/layout/deferred-element.js +4 -8
  42. package/dist/elements/layout/expanded-element.d.ts +3 -3
  43. package/dist/elements/layout/expanded-element.js +10 -14
  44. package/dist/elements/layout/padding-element.d.ts +3 -3
  45. package/dist/elements/layout/padding-element.js +9 -14
  46. package/dist/elements/layout/positioned-element.d.ts +17 -4
  47. package/dist/elements/layout/positioned-element.js +29 -25
  48. package/dist/elements/layout/repeating-header-element.d.ts +3 -3
  49. package/dist/elements/layout/repeating-header-element.js +8 -12
  50. package/dist/elements/layout/sized-container-element.d.ts +2 -2
  51. package/dist/elements/layout/sized-container-element.js +6 -11
  52. package/dist/elements/line-element.d.ts +3 -3
  53. package/dist/elements/line-element.js +5 -10
  54. package/dist/elements/page-element.d.ts +8 -6
  55. package/dist/elements/page-element.js +20 -23
  56. package/dist/elements/pdf-document-element.d.ts +10 -4
  57. package/dist/elements/pdf-document-element.js +11 -10
  58. package/dist/elements/pdf-element.d.ts +12 -3
  59. package/dist/elements/pdf-element.js +10 -19
  60. package/dist/elements/rectangle-element.d.ts +5 -5
  61. package/dist/elements/rectangle-element.js +19 -25
  62. package/dist/elements/row-element.d.ts +3 -3
  63. package/dist/elements/row-element.js +7 -11
  64. package/dist/elements/text-element.d.ts +37 -11
  65. package/dist/elements/text-element.js +64 -39
  66. package/dist/index.d.ts +3 -3
  67. package/dist/index.js +3 -19
  68. package/dist/ir/display-list.d.ts +4 -2
  69. package/dist/ir/display-list.js +1 -2
  70. package/dist/layout/box-constraints.js +2 -6
  71. package/dist/layout/fragmentation.d.ts +8 -1
  72. package/dist/layout/fragmentation.js +22 -10
  73. package/dist/platform/browser-fs.d.ts +2 -0
  74. package/dist/platform/browser-fs.js +9 -0
  75. package/dist/platform/browser-image.d.ts +5 -0
  76. package/dist/platform/browser-image.js +13 -0
  77. package/dist/platform/node-fs.d.ts +2 -0
  78. package/dist/platform/node-fs.js +10 -0
  79. package/dist/platform/node-image.d.ts +5 -0
  80. package/dist/platform/node-image.js +9 -0
  81. package/dist/renderer/container-renderer.d.ts +3 -3
  82. package/dist/renderer/container-renderer.js +12 -27
  83. package/dist/renderer/default-text-style-renderer.d.ts +6 -0
  84. package/dist/renderer/default-text-style-renderer.js +10 -0
  85. package/dist/renderer/deferred-renderer.d.ts +3 -3
  86. package/dist/renderer/deferred-renderer.js +8 -23
  87. package/dist/renderer/expanded-renderer.d.ts +3 -3
  88. package/dist/renderer/expanded-renderer.js +6 -21
  89. package/dist/renderer/image-renderer.d.ts +3 -3
  90. package/dist/renderer/image-renderer.js +77 -75
  91. package/dist/renderer/index.d.ts +10 -10
  92. package/dist/renderer/index.js +10 -26
  93. package/dist/renderer/line-renderer.d.ts +3 -3
  94. package/dist/renderer/line-renderer.js +13 -28
  95. package/dist/renderer/padding-renderer.d.ts +3 -3
  96. package/dist/renderer/padding-renderer.js +6 -21
  97. package/dist/renderer/page-renderer.d.ts +2 -2
  98. package/dist/renderer/page-renderer.js +61 -77
  99. package/dist/renderer/pdf-backend.d.ts +2 -2
  100. package/dist/renderer/pdf-backend.js +21 -19
  101. package/dist/renderer/pdf-config.js +4 -7
  102. package/dist/renderer/pdf-document-class.d.ts +5 -5
  103. package/dist/renderer/pdf-document-class.js +24 -41
  104. package/dist/renderer/pdf-document-renderer.d.ts +3 -3
  105. package/dist/renderer/pdf-document-renderer.js +71 -85
  106. package/dist/renderer/pdf-renderer.d.ts +2 -2
  107. package/dist/renderer/pdf-renderer.js +85 -93
  108. package/dist/renderer/positioned-renderer.d.ts +3 -3
  109. package/dist/renderer/positioned-renderer.js +8 -23
  110. package/dist/renderer/rectangle-renderer.d.ts +3 -3
  111. package/dist/renderer/rectangle-renderer.js +45 -52
  112. package/dist/renderer/repeating-header-renderer.d.ts +3 -3
  113. package/dist/renderer/repeating-header-renderer.js +11 -26
  114. package/dist/renderer/row-renderer.d.ts +3 -3
  115. package/dist/renderer/row-renderer.js +12 -27
  116. package/dist/renderer/text-renderer.d.ts +6 -5
  117. package/dist/renderer/text-renderer.js +33 -42
  118. package/dist/text/line-breaker.d.ts +8 -5
  119. package/dist/text/line-breaker.js +67 -16
  120. package/dist/text/text-style.d.ts +25 -0
  121. package/dist/text/text-style.js +29 -0
  122. package/dist/utils/afm-parser.js +3 -13
  123. package/dist/utils/bytes.d.ts +24 -0
  124. package/dist/utils/bytes.js +76 -0
  125. package/dist/utils/flex-layout.d.ts +2 -2
  126. package/dist/utils/flex-layout.js +15 -20
  127. package/dist/utils/font-metrics.d.ts +1 -1
  128. package/dist/utils/font-metrics.js +1 -2
  129. package/dist/utils/font-path.js +3 -6
  130. package/dist/utils/image-helper.d.ts +6 -5
  131. package/dist/utils/image-helper.js +101 -111
  132. package/dist/utils/md5.d.ts +4 -0
  133. package/dist/utils/md5.js +79 -0
  134. package/dist/utils/pdf-object-manager.d.ts +18 -6
  135. package/dist/utils/pdf-object-manager.js +0 -0
  136. package/dist/utils/renderer-registry.js +1 -5
  137. package/dist/utils/ttf-parser.d.ts +2 -2
  138. package/dist/utils/ttf-parser.js +32 -36
  139. package/dist/utils/ttf-subsetter.d.ts +1 -1
  140. package/dist/utils/ttf-subsetter.js +40 -42
  141. package/dist/utils/utf8-to-windows1252-encoder.js +1 -4
  142. package/dist/validators/element-validator.d.ts +2 -2
  143. package/dist/validators/element-validator.js +9 -13
  144. package/package.json +14 -2
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Color = void 0;
4
- class Color {
1
+ export class Color {
5
2
  constructor(r, g, b, alpha = 1) {
6
3
  this.r = this.clampColorValue(r, "r");
7
4
  this.g = this.clampColorValue(g, "g");
@@ -59,4 +56,3 @@ class Color {
59
56
  return `${(r / 255).toFixed(3)} ${(g / 255).toFixed(3)} ${(b / 255).toFixed(3)}`;
60
57
  }
61
58
  }
62
- exports.Color = Color;
@@ -1,8 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pageFormats = exports.PageSize = void 0;
4
1
  // Definition of all possible page sizes
5
- var PageSize;
2
+ export var PageSize;
6
3
  (function (PageSize) {
7
4
  PageSize["A0"] = "a0";
8
5
  PageSize["A1"] = "a1";
@@ -45,9 +42,9 @@ var PageSize;
45
42
  PageSize["LEDGER"] = "ledger";
46
43
  PageSize["TABLOID"] = "tabloid";
47
44
  PageSize["CREDIT_CARD"] = "credit-card";
48
- })(PageSize || (exports.PageSize = PageSize = {}));
45
+ })(PageSize || (PageSize = {}));
49
46
  // Definition of page formats
50
- exports.pageFormats = {
47
+ export const pageFormats = {
51
48
  [PageSize.A0]: [2383.94, 3370.39],
52
49
  [PageSize.A1]: [1683.78, 2383.94],
53
50
  [PageSize.A2]: [1190.55, 1683.78],
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PDF_PARTS = void 0;
4
- exports.PDF_PARTS = {
1
+ export const PDF_PARTS = {
5
2
  HEADER: "%PDF-1.3",
6
3
  TRAILER: (size) => `trailer\n<< /Size ${size} >>\nstartxref\n%%EOF`,
7
4
  XREF: "xref", // Placeholder for xref elements
@@ -0,0 +1,46 @@
1
+ /** A handler encrypts/decrypts the strings + streams of a document and describes itself as an `/Encrypt` dict. */
2
+ export interface SecurityHandler {
3
+ /** The `/Encrypt` dictionary body (between `<<` and `>>`). Its own strings are never encrypted. */
4
+ encryptDict(): string;
5
+ /** Encrypt one string/stream. `ref` is unused for V5/R6 (one file key) but kept for future per-object schemes. */
6
+ encrypt(data: Uint8Array, ref?: {
7
+ num: number;
8
+ gen: number;
9
+ }): Promise<Uint8Array>;
10
+ /** Decrypt one string/stream (the groundwork the future "open/edit existing PDF" path reuses). */
11
+ decrypt(data: Uint8Array, ref?: {
12
+ num: number;
13
+ gen: number;
14
+ }): Promise<Uint8Array>;
15
+ }
16
+ /** What the user grants; everything defaults to allowed. Maps to the `/P` bitfield (ISO 32000-2 Table 22). */
17
+ export interface Permissions {
18
+ printing?: boolean;
19
+ copying?: boolean;
20
+ modifying?: boolean;
21
+ annotating?: boolean;
22
+ }
23
+ export interface EncryptOptions {
24
+ /** Only "aes-256" today - the seam for future algorithms. */
25
+ algorithm?: "aes-256";
26
+ userPassword: string;
27
+ /** Full-rights password; defaults to the user password if omitted. */
28
+ ownerPassword?: string;
29
+ permissions?: Permissions;
30
+ }
31
+ declare class StandardAes256 implements SecurityHandler {
32
+ private readonly fileKey;
33
+ private readonly dict;
34
+ private constructor();
35
+ static create(opts: EncryptOptions): Promise<StandardAes256>;
36
+ encryptDict(): string;
37
+ encrypt(data: Uint8Array): Promise<Uint8Array>;
38
+ decrypt(data: Uint8Array): Promise<Uint8Array>;
39
+ /** Recover the file key from a password as a reader does: validate it against /U FIRST (so a wrong
40
+ * password is rejected, not silently turned into a garbage key), then decrypt /UE. The groundwork the
41
+ * future "open/edit existing PDF" path reuses. Throws on a wrong password. */
42
+ static recoverFileKey(userPassword: string, u: Uint8Array, ue: Uint8Array): Promise<Uint8Array>;
43
+ }
44
+ /** The factory = the seam. Today it always returns the AES-256 handler. */
45
+ export declare function createSecurityHandler(opts: EncryptOptions): Promise<SecurityHandler>;
46
+ export { StandardAes256 };
@@ -0,0 +1,129 @@
1
+ // PDF Standard security handler. The PDFObjectManager talks to the `SecurityHandler` interface, never to a
2
+ // concrete algorithm - so adding another scheme later (a new revision, or per-object RC4/AES-128) is just a
3
+ // second implementation + a factory branch, with the writer untouched. Today we ship exactly one:
4
+ // `StandardAes256` = AES-256, V5/R6 (ISO 32000-2 / PDF 2.0), the newest standard PDF encryption.
5
+ import { aesCbcDecrypt, aesCbcDecryptNoPad, aesCbcEncrypt, aesCbcEncryptNoPad, randomBytes, sha, } from "./webcrypto.js";
6
+ const utf8 = (s) => new TextEncoder().encode(s).subarray(0, 127); // R6 truncates passwords to 127 bytes
7
+ const concat = (...parts) => {
8
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
9
+ let o = 0;
10
+ for (const p of parts) {
11
+ out.set(p, o);
12
+ o += p.length;
13
+ }
14
+ return out;
15
+ };
16
+ const toHex = (a) => [...a].map((b) => b.toString(16).padStart(2, "0")).join("");
17
+ // Constant-time-ish equality for password validation (no early-out on the first differing byte).
18
+ const bytesEqual = (a, b) => {
19
+ if (a.length !== b.length)
20
+ return false;
21
+ let diff = 0;
22
+ for (let i = 0; i < a.length; i++)
23
+ diff |= a[i] ^ b[i];
24
+ return diff === 0;
25
+ };
26
+ // ISO 32000-2 Algorithm 2.B: the hardened password hash. Loops AES-128-CBC + SHA-256/384/512 until the
27
+ // stopping rule. `udata` is the 48-byte /U for the owner computations, empty for the user ones.
28
+ async function hash2B(password, salt, udata) {
29
+ let k = await sha(256, concat(password, salt, udata));
30
+ let e = new Uint8Array(0);
31
+ // At least 64 rounds; then stop once the last byte of E <= round - 32 (ISO 32000-2 Algorithm 2.B).
32
+ for (let round = 0; round < 64 || e[e.length - 1] > round - 32; round++) {
33
+ const block = concat(password, k, udata);
34
+ const k1 = new Uint8Array(block.length * 64);
35
+ for (let i = 0; i < 64; i++)
36
+ k1.set(block, i * block.length);
37
+ e = await aesCbcEncryptNoPad(k.subarray(0, 16), k.subarray(16, 32), k1);
38
+ let sum = 0;
39
+ for (let i = 0; i < 16; i++)
40
+ sum += e[i]; // big-endian 128-bit mod 3 == byte-sum mod 3 (256 ≡ 1 mod 3)
41
+ k = await sha([256, 384, 512][sum % 3], e);
42
+ }
43
+ return k.subarray(0, 32);
44
+ }
45
+ function permissionBits(p = {}) {
46
+ const { printing = true, copying = true, modifying = true, annotating = true } = p;
47
+ let bits = 0xfffff000 | 0b11000000; // reserved-1 bits (positions 7-8 and 13-32)
48
+ if (printing)
49
+ bits |= 4 | 2048;
50
+ if (modifying)
51
+ bits |= 8 | 1024;
52
+ if (copying)
53
+ bits |= 16 | 512;
54
+ if (annotating)
55
+ bits |= 32 | 256;
56
+ return bits | 0; // int32
57
+ }
58
+ class StandardAes256 {
59
+ constructor(fileKey, dict) {
60
+ this.fileKey = fileKey;
61
+ this.dict = dict;
62
+ }
63
+ static async create(opts) {
64
+ const user = utf8(opts.userPassword);
65
+ const owner = utf8(opts.ownerPassword ?? opts.userPassword);
66
+ const fileKey = randomBytes(32);
67
+ const p = permissionBits(opts.permissions);
68
+ // Algorithm 8: /U and /UE.
69
+ const uVS = randomBytes(8);
70
+ const uKS = randomBytes(8);
71
+ const empty = new Uint8Array(0);
72
+ const u = concat(await hash2B(user, uVS, empty), uVS, uKS); // 48 bytes
73
+ const ue = await aesCbcEncryptNoPad(await hash2B(user, uKS, empty), new Uint8Array(16), fileKey);
74
+ // Algorithm 9: /O and /OE (these also fold in /U).
75
+ const oVS = randomBytes(8);
76
+ const oKS = randomBytes(8);
77
+ const o = concat(await hash2B(owner, oVS, u), oVS, oKS); // 48 bytes
78
+ const oe = await aesCbcEncryptNoPad(await hash2B(owner, oKS, u), new Uint8Array(16), fileKey);
79
+ // Algorithm 10: /Perms (a 16-byte block, ECB == single-block CBC with a zero IV).
80
+ const perms = new Uint8Array(16);
81
+ perms[0] = p & 0xff;
82
+ perms[1] = (p >> 8) & 0xff;
83
+ perms[2] = (p >> 16) & 0xff;
84
+ perms[3] = (p >> 24) & 0xff;
85
+ perms[4] = perms[5] = perms[6] = perms[7] = 0xff;
86
+ perms[8] = 0x46; // 'F' - metadata (XMP) stays unencrypted (standard; keeps it indexer-readable)
87
+ perms[9] = 0x61; // 'a'
88
+ perms[10] = 0x64; // 'd'
89
+ perms[11] = 0x62; // 'b'
90
+ perms.set(randomBytes(4), 12);
91
+ const permsEnc = await aesCbcEncryptNoPad(fileKey, new Uint8Array(16), perms);
92
+ const dict = `/Filter /Standard /V 5 /R 6 /Length 256 /P ${p} /EncryptMetadata false ` +
93
+ `/CF << /StdCF << /CFM /AESV3 /AuthEvent /DocOpen /Length 32 >> >> /StmF /StdCF /StrF /StdCF ` +
94
+ `/U <${toHex(u)}> /UE <${toHex(ue)}> /O <${toHex(o)}> /OE <${toHex(oe)}> /Perms <${toHex(permsEnc)}>`;
95
+ return new StandardAes256(fileKey, dict);
96
+ }
97
+ encryptDict() {
98
+ return this.dict;
99
+ }
100
+ async encrypt(data) {
101
+ const iv = randomBytes(16);
102
+ const ct = await aesCbcEncrypt(this.fileKey, iv, data);
103
+ return concat(iv, ct);
104
+ }
105
+ async decrypt(data) {
106
+ return aesCbcDecrypt(this.fileKey, data.subarray(0, 16), data.subarray(16));
107
+ }
108
+ /** Recover the file key from a password as a reader does: validate it against /U FIRST (so a wrong
109
+ * password is rejected, not silently turned into a garbage key), then decrypt /UE. The groundwork the
110
+ * future "open/edit existing PDF" path reuses. Throws on a wrong password. */
111
+ static async recoverFileKey(userPassword, u, ue) {
112
+ const pw = utf8(userPassword);
113
+ // Algorithm 11: hash(password + validation salt) must equal the first 32 bytes of /U.
114
+ const check = await hash2B(pw, u.subarray(32, 40), new Uint8Array(0));
115
+ if (!bytesEqual(check, u.subarray(0, 32))) {
116
+ throw new Error("@jasy/pdf: wrong password.");
117
+ }
118
+ const intermediate = await hash2B(pw, u.subarray(40, 48), new Uint8Array(0));
119
+ return aesCbcDecryptNoPad(intermediate, new Uint8Array(16), ue);
120
+ }
121
+ }
122
+ /** The factory = the seam. Today it always returns the AES-256 handler. */
123
+ export async function createSecurityHandler(opts) {
124
+ if (opts.algorithm && opts.algorithm !== "aes-256") {
125
+ throw new Error(`@jasy/pdf: unsupported encryption algorithm "${opts.algorithm}" (only "aes-256").`);
126
+ }
127
+ return StandardAes256.create(opts);
128
+ }
129
+ export { StandardAes256 };
@@ -0,0 +1,11 @@
1
+ export declare function randomBytes(n: number): Uint8Array;
2
+ export declare function sha(bits: 256 | 384 | 512, data: Uint8Array): Promise<Uint8Array>;
3
+ /** AES-CBC with PKCS#7 padding (the AESV3 mode for PDF strings/streams). */
4
+ export declare function aesCbcEncrypt(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
5
+ export declare function aesCbcDecrypt(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
6
+ /** AES-CBC WITHOUT padding (`data.length` must be a multiple of 16). WebCrypto pads unconditionally, so we
7
+ * encrypt and drop the extra full pad block - the leading blocks are the true unpadded CBC. */
8
+ export declare function aesCbcEncryptNoPad(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
9
+ /** AES-CBC no-padding DECRYPT (`data` a multiple of 16). We append one cipher block crafted to decrypt to a
10
+ * full 0x10 PKCS#7 pad (so WebCrypto accepts and strips it), leaving the original. */
11
+ export declare function aesCbcDecryptNoPad(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
@@ -0,0 +1,62 @@
1
+ // Isomorphic crypto via the platform WebCrypto (`globalThis.crypto.subtle`) - native in the browser AND in
2
+ // Node 20+, with zero dependencies. This is the primitive layer the PDF Standard security handler (AES-256,
3
+ // R6) builds on. Everything is async (so is `render()`), so that is no constraint.
4
+ //
5
+ // One wrinkle: WebCrypto's AES-CBC ALWAYS applies PKCS#7 padding and offers no raw/no-pad mode. The PDF R6
6
+ // key derivation (Algorithm 2.B, /UE, /OE, /Perms) needs UNPADDED AES, so we synthesize it from the padded
7
+ // primitive (encrypt: drop the trailing pad block; decrypt: append a block that decrypts to a full pad, then
8
+ // let WebCrypto strip it). Document strings/streams use the padded primitive directly - that IS AESV3.
9
+ const subtle = () => {
10
+ const c = globalThis.crypto;
11
+ if (!c?.subtle) {
12
+ throw new Error("@jasy/pdf: PDF encryption needs WebCrypto (globalThis.crypto.subtle), unavailable here.");
13
+ }
14
+ return c.subtle;
15
+ };
16
+ export function randomBytes(n) {
17
+ const b = new Uint8Array(n);
18
+ globalThis.crypto.getRandomValues(b);
19
+ return b;
20
+ }
21
+ // WebCrypto's types want a BufferSource backed by ArrayBuffer; our Uint8Arrays are ArrayBuffer-backed, so
22
+ // this cast at the platform boundary is safe (TS 5.7+ just can't prove it isn't a SharedArrayBuffer).
23
+ const buf = (a) => a;
24
+ export async function sha(bits, data) {
25
+ return new Uint8Array(await subtle().digest(`SHA-${bits}`, buf(data)));
26
+ }
27
+ async function aesKey(key, usage) {
28
+ return subtle().importKey("raw", buf(key), { name: "AES-CBC" }, false, [usage]);
29
+ }
30
+ /** AES-CBC with PKCS#7 padding (the AESV3 mode for PDF strings/streams). */
31
+ export async function aesCbcEncrypt(key, iv, data) {
32
+ return new Uint8Array(await subtle().encrypt({ name: "AES-CBC", iv: buf(iv) }, await aesKey(key, "encrypt"), buf(data)));
33
+ }
34
+ export async function aesCbcDecrypt(key, iv, data) {
35
+ return new Uint8Array(await subtle().decrypt({ name: "AES-CBC", iv: buf(iv) }, await aesKey(key, "decrypt"), buf(data)));
36
+ }
37
+ /** AES-CBC WITHOUT padding (`data.length` must be a multiple of 16). WebCrypto pads unconditionally, so we
38
+ * encrypt and drop the extra full pad block - the leading blocks are the true unpadded CBC. */
39
+ export async function aesCbcEncryptNoPad(key, iv, data) {
40
+ if (data.length % 16 !== 0)
41
+ throw new Error("aesCbcEncryptNoPad: data must be a multiple of 16 bytes.");
42
+ return (await aesCbcEncrypt(key, iv, data)).subarray(0, data.length);
43
+ }
44
+ const xor16 = (a, b) => {
45
+ const o = new Uint8Array(16);
46
+ for (let i = 0; i < 16; i++)
47
+ o[i] = a[i] ^ b[i];
48
+ return o;
49
+ };
50
+ /** AES-CBC no-padding DECRYPT (`data` a multiple of 16). We append one cipher block crafted to decrypt to a
51
+ * full 0x10 PKCS#7 pad (so WebCrypto accepts and strips it), leaving the original. */
52
+ export async function aesCbcDecryptNoPad(key, iv, data) {
53
+ if (data.length === 0 || data.length % 16 !== 0)
54
+ throw new Error("aesCbcDecryptNoPad: data must be a non-zero multiple of 16 bytes.");
55
+ const prev = data.length >= 16 ? data.subarray(data.length - 16) : iv;
56
+ // ECB(x) == first block of CBC(iv=0, x); we want the appended block to decrypt to 0x10*16 after the CBC xor.
57
+ const appended = await aesCbcEncryptNoPad(key, new Uint8Array(16), xor16(new Uint8Array(16).fill(16), prev));
58
+ const withPad = new Uint8Array(data.length + 16);
59
+ withPad.set(data);
60
+ withPad.set(appended, data.length);
61
+ return aesCbcDecrypt(key, iv, withPad);
62
+ }
@@ -1,7 +1,7 @@
1
- import { MainAlign, CrossAlign } from "../utils/flex-layout";
2
- import { BoxConstraints, Offset, Size } from "../layout/box-constraints";
3
- import { Fragmentable, FragmentResult } from "../layout/fragmentation";
4
- import { LayoutContext, SizedElement, SizedPDFElement, WithChildren } from "./pdf-element";
1
+ import { MainAlign, CrossAlign } from "../utils/flex-layout.js";
2
+ import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
3
+ import { Fragmentable, FragmentResult } from "../layout/fragmentation.js";
4
+ import { LayoutContext, SizedElement, SizedPDFElement, WithChildren } from "./pdf-element.js";
5
5
  interface ContainerElementParams extends SizedElement, WithChildren {
6
6
  /** Space between children. */
7
7
  gap?: number;
@@ -1,16 +1,13 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ContainerElement = void 0;
4
- const flex_layout_1 = require("../utils/flex-layout");
5
- const fragmentation_1 = require("../layout/fragmentation");
6
- const pdf_element_1 = require("./pdf-element");
7
- class ContainerElement extends pdf_element_1.SizedPDFElement {
1
+ import { FlexLayoutHelper, VERTICAL_AXIS } from "../utils/flex-layout.js";
2
+ import { packChildren } from "../layout/fragmentation.js";
3
+ import { SizedPDFElement, } from "./pdf-element.js";
4
+ export class ContainerElement extends SizedPDFElement {
8
5
  constructor({ x, y, width, height, children, gap, main, cross }) {
9
6
  super({ x, y, width, height });
10
7
  this.children = children;
11
- this.gap = gap !== null && gap !== void 0 ? gap : 0;
12
- this.main = main !== null && main !== void 0 ? main : "start";
13
- this.cross = cross !== null && cross !== void 0 ? cross : "stretch";
8
+ this.gap = gap ?? 0;
9
+ this.main = main ?? "start";
10
+ this.cross = cross ?? "stretch";
14
11
  }
15
12
  /**
16
13
  * Splits the vertical stack across pages. Children are measured against the content
@@ -24,7 +21,7 @@ class ContainerElement extends pdf_element_1.SizedPDFElement {
24
21
  * in that case we don't fragment and hand the whole container back as `fitted`.
25
22
  */
26
23
  fragment(maxHeight, width, ctx) {
27
- const { fitted, remainder } = (0, fragmentation_1.packChildren)(this.children, maxHeight, width, ctx, this.gap);
24
+ const { fitted, remainder } = packChildren(this.children, maxHeight, width, ctx, this.gap);
28
25
  // Fits as one region: hand the whole container back so the page renders unchanged
29
26
  // (its normal layout distributes flex / fills the page).
30
27
  if (remainder.length === 0)
@@ -64,7 +61,7 @@ class ContainerElement extends pdf_element_1.SizedPDFElement {
64
61
  // Vertical flex stack (main = height, cross = width). The shared helper measures
65
62
  // fixed children, distributes the leftover to flex children, and places everything
66
63
  // in source order.
67
- result = flex_layout_1.FlexLayoutHelper.layout(this.children, flex_layout_1.VERTICAL_AXIS, mainAvail, crossAvail, this.y, this.x, { gap: this.gap, main: this.main, cross: this.cross }, ctx);
64
+ result = FlexLayoutHelper.layout(this.children, VERTICAL_AXIS, mainAvail, crossAvail, this.y, this.x, { gap: this.gap, main: this.main, cross: this.cross }, ctx);
68
65
  }
69
66
  // Bounded: fill the offered extent. Unbounded: shrink to the children (height = the
70
67
  // stack, width = the widest child). Top-left coordinates; the container draws nothing,
@@ -88,4 +85,3 @@ class ContainerElement extends pdf_element_1.SizedPDFElement {
88
85
  };
89
86
  }
90
87
  }
91
- exports.ContainerElement = ContainerElement;
@@ -1,5 +1,5 @@
1
- import { BoxConstraints, Offset, Size } from "../layout/box-constraints";
2
- import { LayoutContext, SizedPDFElement } from "./pdf-element";
1
+ import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
2
+ import { LayoutContext, SizedPDFElement } from "./pdf-element.js";
3
3
  export declare enum BoxFit {
4
4
  none = "NONE",
5
5
  contain = "CONTAIN",
@@ -29,6 +29,22 @@ export declare class CustomLocalImage extends CustomImage {
29
29
  height: number;
30
30
  }>;
31
31
  }
32
+ /**
33
+ * An image straight from raw bytes (a browser upload / fetch, no filesystem). The PDF filter is sniffed
34
+ * from the magic bytes: JPEG embeds raw (DCTDecode, no decode step), PNG is decoded to RGB (FlateDecode).
35
+ */
36
+ export declare class CustomBytesImage extends CustomImage {
37
+ private bytes;
38
+ private fileRawData;
39
+ constructor(bytes: Uint8Array);
40
+ init(): Promise<void>;
41
+ getImageType(): Promise<string>;
42
+ getFileData(): string;
43
+ getImageDimensions(): Promise<{
44
+ width: number;
45
+ height: number;
46
+ }>;
47
+ }
32
48
  interface ImageElementParams {
33
49
  image: CustomImage;
34
50
  width?: number;
@@ -1,131 +1,108 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
- return new (P || (P = Promise))(function (resolve, reject) {
38
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
- step((generator = generator.apply(thisArg, _arguments || [])).next());
42
- });
43
- };
44
- Object.defineProperty(exports, "__esModule", { value: true });
45
- exports.ImageElement = exports.CustomLocalImage = exports.CustomImage = exports.BoxFit = void 0;
46
- const image_helper_1 = require("../utils/image-helper");
47
- const pdf_element_1 = require("./pdf-element");
48
- var BoxFit;
1
+ import { getImageDimensions } from "../utils/image-helper.js";
2
+ import { latin1FromBytes } from "../utils/bytes.js";
3
+ import { readFileBytesAsync } from "../platform/node-fs.js";
4
+ import { SizedPDFElement } from "./pdf-element.js";
5
+ // path.extname without node:path (browser-safe): the substring from the last dot, if it sits after the
6
+ // last slash (so a dot in a directory name does not count). Enough for image file extensions.
7
+ function extname(p) {
8
+ const dot = p.lastIndexOf(".");
9
+ const slash = p.lastIndexOf("/");
10
+ return dot > slash + 1 ? p.slice(dot) : "";
11
+ }
12
+ export var BoxFit;
49
13
  (function (BoxFit) {
50
14
  BoxFit["none"] = "NONE";
51
15
  BoxFit["contain"] = "CONTAIN";
52
16
  BoxFit["cover"] = "COVER";
53
17
  BoxFit["fill"] = "FILL";
54
- })(BoxFit || (exports.BoxFit = BoxFit = {}));
55
- class CustomImage {
18
+ })(BoxFit || (BoxFit = {}));
19
+ export class CustomImage {
56
20
  }
57
- exports.CustomImage = CustomImage;
58
- class CustomLocalImage extends CustomImage {
21
+ export class CustomLocalImage extends CustomImage {
59
22
  constructor(imagePath) {
60
23
  super();
61
24
  this.imagePath = imagePath;
62
25
  }
63
- init() {
64
- return __awaiter(this, void 0, void 0, function* () {
65
- try {
66
- // Loading image and convert it to base64
67
- yield this.loadImage(this.imagePath);
68
- }
69
- catch (error) {
70
- console.error("Error loading image:", error);
71
- }
72
- });
26
+ async init() {
27
+ try {
28
+ // Loading image and convert it to base64
29
+ await this.loadImage(this.imagePath);
30
+ }
31
+ catch (error) {
32
+ console.error("Error loading image:", error);
33
+ }
34
+ }
35
+ async getImageType() {
36
+ const ext = extname(this.imagePath).toLowerCase();
37
+ switch (ext) {
38
+ case ".jpg":
39
+ case ".jpeg":
40
+ return "DCTDecode"; // For JPEG
41
+ case ".png":
42
+ return "FlateDecode"; // For PNG
43
+ case ".bmp":
44
+ throw new Error("BMP is not directly supported. Please convert to PNG or JPEG.");
45
+ case ".webp":
46
+ throw new Error("WebP is not directly supported. Please convert to PNG or JPEG.");
47
+ default:
48
+ throw new Error(`Unsupported image format: ${ext}`);
49
+ }
50
+ }
51
+ async loadImage(imagePath) {
52
+ const result = await readFileBytesAsync(imagePath);
53
+ //const result = await convertImageToGrayscaleBuffer(imagePath);
54
+ this.fileBuffer = result;
55
+ this.fileRawData = latin1FromBytes(result);
56
+ return result;
73
57
  }
74
- getImageType() {
75
- return __awaiter(this, void 0, void 0, function* () {
76
- const path = yield Promise.resolve().then(() => __importStar(require("path"))); // Dynamic import
77
- const ext = path.extname(this.imagePath).toLowerCase();
78
- switch (ext) {
79
- case ".jpg":
80
- case ".jpeg":
81
- return "DCTDecode"; // For JPEG
82
- case ".png":
83
- return "FlateDecode"; // For PNG
84
- case ".bmp":
85
- throw new Error("BMP is not directly supported. Please convert to PNG or JPEG.");
86
- case ".webp":
87
- throw new Error("WebP is not directly supported. Please convert to PNG or JPEG.");
88
- default:
89
- throw new Error(`Unsupported image format: ${ext}`);
90
- }
91
- });
58
+ getFileData() {
59
+ return this.fileRawData;
60
+ }
61
+ async getImageDimensions() {
62
+ if (!this.fileBuffer) {
63
+ throw new Error("You must first call the `loadAndConvertImage` method");
64
+ }
65
+ // Since now (30.09.2024) we using "Jimp" - So we don't need our custom method to get the image dimension.
66
+ // But at the moment I let it still here...
67
+ const dimensions = await getImageDimensions(this.fileBuffer);
68
+ return dimensions;
69
+ }
70
+ }
71
+ /**
72
+ * An image straight from raw bytes (a browser upload / fetch, no filesystem). The PDF filter is sniffed
73
+ * from the magic bytes: JPEG embeds raw (DCTDecode, no decode step), PNG is decoded to RGB (FlateDecode).
74
+ */
75
+ export class CustomBytesImage extends CustomImage {
76
+ constructor(bytes) {
77
+ super();
78
+ this.bytes = bytes;
79
+ this.fileRawData = latin1FromBytes(bytes);
92
80
  }
93
- loadImage(imagePath) {
94
- return __awaiter(this, void 0, void 0, function* () {
95
- const fs = yield Promise.resolve().then(() => __importStar(require("fs/promises"))); // Dynamic import
96
- const result = yield fs.readFile(imagePath);
97
- //const result = await convertImageToGrayscaleBuffer(imagePath);
98
- this.fileBuffer = result;
99
- this.fileRawData = result.toString("binary");
100
- return result;
101
- });
81
+ async init() { }
82
+ async getImageType() {
83
+ const b = this.bytes;
84
+ if (b[0] === 0xff && b[1] === 0xd8)
85
+ return "DCTDecode"; // JPEG
86
+ if (b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47)
87
+ return "FlateDecode"; // PNG
88
+ throw new Error("Unsupported image bytes (only JPEG and PNG are supported).");
102
89
  }
103
90
  getFileData() {
104
91
  return this.fileRawData;
105
92
  }
106
- getImageDimensions() {
107
- return __awaiter(this, void 0, void 0, function* () {
108
- if (!this.fileBuffer) {
109
- throw new Error("You must first call the `loadAndConvertImage` method");
110
- }
111
- // Since now (30.09.2024) we using "Jimp" - So we don't need our custom method to get the image dimension.
112
- // But at the moment I let it still here...
113
- const dimensions = yield (0, image_helper_1.getImageDimensions)(this.fileBuffer);
114
- return dimensions;
115
- });
93
+ async getImageDimensions() {
94
+ return getImageDimensions(this.bytes);
116
95
  }
117
96
  }
118
- exports.CustomLocalImage = CustomLocalImage;
119
- class ImageElement extends pdf_element_1.SizedPDFElement {
97
+ export class ImageElement extends SizedPDFElement {
120
98
  constructor({ image, width, height, fit = BoxFit.none, radius }) {
121
99
  super({ x: 0, y: 0, width });
122
100
  this.image = image;
123
101
  this.height = height;
124
102
  this.fit = fit;
125
- this.radius = radius !== null && radius !== void 0 ? radius : 0;
103
+ this.radius = radius ?? 0;
126
104
  }
127
105
  calculateLayout(constraints, offset, _ctx) {
128
- var _a, _b;
129
106
  this.x = offset.x;
130
107
  this.y = offset.y;
131
108
  // A bounded axis overrides the intrinsic/explicit size; otherwise keep our own.
@@ -134,7 +111,7 @@ class ImageElement extends pdf_element_1.SizedPDFElement {
134
111
  if (constraints.hasBoundedHeight)
135
112
  this.height = constraints.maxHeight;
136
113
  // Top-left coordinates; the fit logic (renderer) and the Y-flip (seam) run later.
137
- return { width: (_a = this.width) !== null && _a !== void 0 ? _a : 0, height: (_b = this.height) !== null && _b !== void 0 ? _b : 0 };
114
+ return { width: this.width ?? 0, height: this.height ?? 0 };
138
115
  }
139
116
  getProps() {
140
117
  return {
@@ -148,4 +125,3 @@ class ImageElement extends pdf_element_1.SizedPDFElement {
148
125
  };
149
126
  }
150
127
  }
151
- exports.ImageElement = ImageElement;
@@ -1,11 +1,12 @@
1
- export { PDFElement } from "./pdf-element";
2
- export * from "./text-element";
3
- export * from "./pdf-document-element";
4
- export * from "./page-element";
5
- export * from "./rectangle-element";
6
- export * from "./image-element";
7
- export * from "./layout/expanded-element";
8
- export * from "./layout/padding-element";
9
- export * from "./layout/positioned-element";
10
- export * from "./line-element";
11
- export * from "./row-element";
1
+ export { PDFElement } from "./pdf-element.js";
2
+ export * from "./text-element.js";
3
+ export * from "./pdf-document-element.js";
4
+ export * from "./page-element.js";
5
+ export * from "./rectangle-element.js";
6
+ export * from "./image-element.js";
7
+ export * from "./layout/expanded-element.js";
8
+ export * from "./layout/padding-element.js";
9
+ export * from "./layout/default-text-style-element.js";
10
+ export * from "./layout/positioned-element.js";
11
+ export * from "./line-element.js";
12
+ export * from "./row-element.js";