@cj-tech-master/excelts 9.1.0 → 9.2.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 (147) hide show
  1. package/README.md +16 -1
  2. package/dist/browser/modules/archive/compression/crc32.js +1 -1
  3. package/dist/browser/modules/archive/crypto/aes.d.ts +0 -8
  4. package/dist/browser/modules/archive/crypto/aes.js +1 -20
  5. package/dist/browser/modules/archive/crypto/index.d.ts +2 -1
  6. package/dist/browser/modules/archive/crypto/index.js +3 -1
  7. package/dist/browser/modules/csv/parse/row-processor.d.ts +1 -1
  8. package/dist/browser/modules/csv/worker/worker-script.generated.js +1 -1
  9. package/dist/browser/modules/excel/utils/cell-matrix.js +1 -0
  10. package/dist/browser/modules/excel/utils/encryptor.browser.d.ts +4 -5
  11. package/dist/browser/modules/excel/utils/encryptor.browser.js +7 -12
  12. package/dist/browser/modules/excel/utils/encryptor.d.ts +1 -1
  13. package/dist/browser/modules/excel/utils/encryptor.js +4 -7
  14. package/dist/browser/modules/pdf/builder/document-builder.d.ts +517 -0
  15. package/dist/browser/modules/pdf/builder/document-builder.js +1493 -0
  16. package/dist/browser/modules/pdf/builder/form-appearance.d.ts +56 -0
  17. package/dist/browser/modules/pdf/builder/form-appearance.js +140 -0
  18. package/dist/browser/modules/pdf/builder/image-utils.d.ts +39 -0
  19. package/dist/browser/modules/pdf/builder/image-utils.js +129 -0
  20. package/dist/browser/modules/pdf/builder/pdf-editor.d.ts +230 -0
  21. package/dist/browser/modules/pdf/builder/pdf-editor.js +1574 -0
  22. package/dist/browser/modules/pdf/builder/resource-merger.d.ts +41 -0
  23. package/dist/browser/modules/pdf/builder/resource-merger.js +258 -0
  24. package/dist/browser/modules/pdf/core/digital-signature.d.ts +109 -0
  25. package/dist/browser/modules/pdf/core/digital-signature.js +659 -0
  26. package/dist/browser/modules/pdf/core/encryption.js +8 -7
  27. package/dist/browser/modules/pdf/core/pdf-object.d.ts +11 -0
  28. package/dist/browser/modules/pdf/core/pdf-object.js +38 -0
  29. package/dist/browser/modules/pdf/core/pdf-stream.d.ts +32 -0
  30. package/dist/browser/modules/pdf/core/pdf-stream.js +66 -0
  31. package/dist/browser/modules/pdf/core/pdf-writer.d.ts +55 -1
  32. package/dist/browser/modules/pdf/core/pdf-writer.js +271 -6
  33. package/dist/browser/modules/pdf/core/pdfa.d.ts +62 -0
  34. package/dist/browser/modules/pdf/core/pdfa.js +261 -0
  35. package/dist/browser/modules/pdf/index.d.ts +11 -0
  36. package/dist/browser/modules/pdf/index.js +9 -0
  37. package/dist/browser/modules/pdf/reader/bookmark-extractor.d.ts +35 -0
  38. package/dist/browser/modules/pdf/reader/bookmark-extractor.js +324 -0
  39. package/dist/browser/modules/pdf/reader/pdf-decrypt.js +6 -5
  40. package/dist/browser/modules/pdf/reader/pdf-reader.d.ts +17 -0
  41. package/dist/browser/modules/pdf/reader/pdf-reader.js +26 -2
  42. package/dist/browser/modules/pdf/reader/table-extractor.d.ts +69 -0
  43. package/dist/browser/modules/pdf/reader/table-extractor.js +365 -0
  44. package/dist/browser/modules/pdf/render/layout-engine.d.ts +21 -1
  45. package/dist/browser/modules/pdf/render/layout-engine.js +112 -5
  46. package/dist/browser/modules/pdf/render/page-renderer.d.ts +2 -9
  47. package/dist/browser/modules/pdf/render/page-renderer.js +62 -103
  48. package/dist/browser/modules/pdf/render/pdf-exporter.js +2 -61
  49. package/dist/browser/modules/pdf/render/style-converter.d.ts +4 -0
  50. package/dist/browser/modules/pdf/render/style-converter.js +1 -1
  51. package/dist/browser/modules/pdf/types.d.ts +14 -1
  52. package/dist/browser/modules/stream/browser/readable.js +8 -2
  53. package/dist/browser/utils/crypto.browser.d.ts +64 -0
  54. package/dist/browser/{modules/pdf/core/crypto.js → utils/crypto.browser.js} +91 -101
  55. package/dist/browser/utils/crypto.d.ts +97 -0
  56. package/dist/browser/utils/crypto.js +209 -0
  57. package/dist/cjs/modules/archive/compression/crc32.js +1 -1
  58. package/dist/cjs/modules/archive/crypto/aes.js +2 -23
  59. package/dist/cjs/modules/archive/crypto/index.js +3 -1
  60. package/dist/cjs/modules/csv/worker/worker-script.generated.js +1 -1
  61. package/dist/cjs/modules/excel/utils/cell-matrix.js +1 -0
  62. package/dist/cjs/modules/excel/utils/encryptor.browser.js +7 -12
  63. package/dist/cjs/modules/excel/utils/encryptor.js +4 -10
  64. package/dist/cjs/modules/pdf/builder/document-builder.js +1532 -0
  65. package/dist/cjs/modules/pdf/builder/form-appearance.js +145 -0
  66. package/dist/cjs/modules/pdf/builder/image-utils.js +135 -0
  67. package/dist/cjs/modules/pdf/builder/pdf-editor.js +1612 -0
  68. package/dist/cjs/modules/pdf/builder/resource-merger.js +263 -0
  69. package/dist/cjs/modules/pdf/core/digital-signature.js +667 -0
  70. package/dist/cjs/modules/pdf/core/encryption.js +8 -7
  71. package/dist/cjs/modules/pdf/core/pdf-object.js +38 -0
  72. package/dist/cjs/modules/pdf/core/pdf-stream.js +66 -0
  73. package/dist/cjs/modules/pdf/core/pdf-writer.js +272 -6
  74. package/dist/cjs/modules/pdf/core/pdfa.js +266 -0
  75. package/dist/cjs/modules/pdf/index.js +19 -1
  76. package/dist/cjs/modules/pdf/reader/bookmark-extractor.js +327 -0
  77. package/dist/cjs/modules/pdf/reader/pdf-decrypt.js +6 -5
  78. package/dist/cjs/modules/pdf/reader/pdf-reader.js +26 -2
  79. package/dist/cjs/modules/pdf/reader/table-extractor.js +368 -0
  80. package/dist/cjs/modules/pdf/render/layout-engine.js +113 -4
  81. package/dist/cjs/modules/pdf/render/page-renderer.js +63 -105
  82. package/dist/cjs/modules/pdf/render/pdf-exporter.js +3 -62
  83. package/dist/cjs/modules/pdf/render/style-converter.js +1 -0
  84. package/dist/cjs/modules/stream/browser/readable.js +8 -2
  85. package/dist/cjs/{modules/pdf/core/crypto.js → utils/crypto.browser.js} +95 -102
  86. package/dist/cjs/utils/crypto.js +228 -0
  87. package/dist/esm/modules/archive/compression/crc32.js +1 -1
  88. package/dist/esm/modules/archive/crypto/aes.js +1 -20
  89. package/dist/esm/modules/archive/crypto/index.js +3 -1
  90. package/dist/esm/modules/csv/worker/worker-script.generated.js +1 -1
  91. package/dist/esm/modules/excel/utils/cell-matrix.js +1 -0
  92. package/dist/esm/modules/excel/utils/encryptor.browser.js +7 -12
  93. package/dist/esm/modules/excel/utils/encryptor.js +4 -7
  94. package/dist/esm/modules/pdf/builder/document-builder.js +1493 -0
  95. package/dist/esm/modules/pdf/builder/form-appearance.js +140 -0
  96. package/dist/esm/modules/pdf/builder/image-utils.js +129 -0
  97. package/dist/esm/modules/pdf/builder/pdf-editor.js +1574 -0
  98. package/dist/esm/modules/pdf/builder/resource-merger.js +258 -0
  99. package/dist/esm/modules/pdf/core/digital-signature.js +659 -0
  100. package/dist/esm/modules/pdf/core/encryption.js +8 -7
  101. package/dist/esm/modules/pdf/core/pdf-object.js +38 -0
  102. package/dist/esm/modules/pdf/core/pdf-stream.js +66 -0
  103. package/dist/esm/modules/pdf/core/pdf-writer.js +271 -6
  104. package/dist/esm/modules/pdf/core/pdfa.js +261 -0
  105. package/dist/esm/modules/pdf/index.js +9 -0
  106. package/dist/esm/modules/pdf/reader/bookmark-extractor.js +324 -0
  107. package/dist/esm/modules/pdf/reader/pdf-decrypt.js +6 -5
  108. package/dist/esm/modules/pdf/reader/pdf-reader.js +26 -2
  109. package/dist/esm/modules/pdf/reader/table-extractor.js +365 -0
  110. package/dist/esm/modules/pdf/render/layout-engine.js +112 -5
  111. package/dist/esm/modules/pdf/render/page-renderer.js +62 -103
  112. package/dist/esm/modules/pdf/render/pdf-exporter.js +2 -61
  113. package/dist/esm/modules/pdf/render/style-converter.js +1 -1
  114. package/dist/esm/modules/stream/browser/readable.js +8 -2
  115. package/dist/esm/{modules/pdf/core/crypto.js → utils/crypto.browser.js} +91 -101
  116. package/dist/esm/utils/crypto.js +209 -0
  117. package/dist/iife/excelts.iife.js +1248 -1074
  118. package/dist/iife/excelts.iife.js.map +1 -1
  119. package/dist/iife/excelts.iife.min.js +53 -54
  120. package/dist/types/modules/archive/crypto/aes.d.ts +0 -8
  121. package/dist/types/modules/archive/crypto/index.d.ts +2 -1
  122. package/dist/types/modules/csv/parse/row-processor.d.ts +1 -1
  123. package/dist/types/modules/excel/utils/encryptor.browser.d.ts +4 -5
  124. package/dist/types/modules/excel/utils/encryptor.d.ts +1 -1
  125. package/dist/types/modules/pdf/builder/document-builder.d.ts +517 -0
  126. package/dist/types/modules/pdf/builder/form-appearance.d.ts +56 -0
  127. package/dist/types/modules/pdf/builder/image-utils.d.ts +39 -0
  128. package/dist/types/modules/pdf/builder/pdf-editor.d.ts +230 -0
  129. package/dist/types/modules/pdf/builder/resource-merger.d.ts +41 -0
  130. package/dist/types/modules/pdf/core/digital-signature.d.ts +109 -0
  131. package/dist/types/modules/pdf/core/pdf-object.d.ts +11 -0
  132. package/dist/types/modules/pdf/core/pdf-stream.d.ts +32 -0
  133. package/dist/types/modules/pdf/core/pdf-writer.d.ts +55 -1
  134. package/dist/types/modules/pdf/core/pdfa.d.ts +62 -0
  135. package/dist/types/modules/pdf/index.d.ts +11 -0
  136. package/dist/types/modules/pdf/reader/bookmark-extractor.d.ts +35 -0
  137. package/dist/types/modules/pdf/reader/pdf-reader.d.ts +17 -0
  138. package/dist/types/modules/pdf/reader/table-extractor.d.ts +69 -0
  139. package/dist/types/modules/pdf/render/layout-engine.d.ts +21 -1
  140. package/dist/types/modules/pdf/render/page-renderer.d.ts +2 -9
  141. package/dist/types/modules/pdf/render/style-converter.d.ts +4 -0
  142. package/dist/types/modules/pdf/types.d.ts +14 -1
  143. package/dist/types/utils/crypto.browser.d.ts +64 -0
  144. package/dist/types/utils/crypto.d.ts +97 -0
  145. package/package.json +110 -111
  146. package/dist/browser/modules/pdf/core/crypto.d.ts +0 -65
  147. package/dist/types/modules/pdf/core/crypto.d.ts +0 -65
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Form field appearance stream generation.
3
+ *
4
+ * Generates visual appearance streams for PDF form fields so that field
5
+ * values are visible in all PDF viewers — even those that do not honor
6
+ * the `/NeedAppearances` flag.
7
+ *
8
+ * @see PDF Reference 1.7, §12.5.5 — Appearance Streams
9
+ * @see PDF Reference 1.7, §12.7.4 — Field Appearance
10
+ */
11
+ /** Options for generating a text field appearance stream. */
12
+ export interface TextFieldAppearanceOptions {
13
+ /** The text value to display. */
14
+ value: string;
15
+ /** Widget annotation rectangle [x1, y1, x2, y2]. */
16
+ rect: number[];
17
+ /** Font size in points. 0 or omitted = auto-size to fit the field height. */
18
+ fontSize?: number;
19
+ /** Font resource name to reference (e.g. "Helv"). */
20
+ fontName?: string;
21
+ /** Text alignment within the field. */
22
+ alignment?: "left" | "center" | "right";
23
+ }
24
+ /**
25
+ * Generate an appearance stream for a text form field.
26
+ *
27
+ * Builds a minimal content stream that clips to the widget rect and
28
+ * draws the field value using the specified (or default) font.
29
+ *
30
+ * @returns The raw stream bytes and a resources dictionary string.
31
+ */
32
+ export declare function generateTextFieldAppearance(options: TextFieldAppearanceOptions): {
33
+ stream: Uint8Array;
34
+ resources: string;
35
+ };
36
+ /**
37
+ * Generate appearance streams for a checkbox form field.
38
+ *
39
+ * Returns two streams:
40
+ * - `streamOn`: draws a checkmark (✓-like shape) inside the rect
41
+ * - `streamOff`: empty appearance (blank field)
42
+ *
43
+ * @param checked - Whether to generate the checked or unchecked variant
44
+ * (both are always returned; `checked` is ignored — both
45
+ * on and off streams are produced).
46
+ * @param rect - Widget annotation rectangle [x1, y1, x2, y2].
47
+ */
48
+ export declare function generateCheckboxAppearance(_checked: boolean, rect: number[]): {
49
+ streamOn: Uint8Array;
50
+ streamOff: Uint8Array;
51
+ };
52
+ /**
53
+ * Build the BBox array string for a form XObject appearance stream.
54
+ * The bounding box is in the widget's coordinate space: [0 0 width height].
55
+ */
56
+ export declare function buildAppearanceBBox(rect: number[]): string;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Form field appearance stream generation.
3
+ *
4
+ * Generates visual appearance streams for PDF form fields so that field
5
+ * values are visible in all PDF viewers — even those that do not honor
6
+ * the `/NeedAppearances` flag.
7
+ *
8
+ * @see PDF Reference 1.7, §12.5.5 — Appearance Streams
9
+ * @see PDF Reference 1.7, §12.7.4 — Field Appearance
10
+ */
11
+ import { PdfContentStream } from "../core/pdf-stream.js";
12
+ import { pdfNumber } from "../core/pdf-object.js";
13
+ // =============================================================================
14
+ // Text Field Appearance
15
+ // =============================================================================
16
+ /** Default padding inside the field rectangle (in points). */
17
+ const FIELD_PADDING = 2;
18
+ /**
19
+ * Generate an appearance stream for a text form field.
20
+ *
21
+ * Builds a minimal content stream that clips to the widget rect and
22
+ * draws the field value using the specified (or default) font.
23
+ *
24
+ * @returns The raw stream bytes and a resources dictionary string.
25
+ */
26
+ export function generateTextFieldAppearance(options) {
27
+ const { value, rect, alignment = "left" } = options;
28
+ const fontName = options.fontName ?? "Helv";
29
+ // Derive bounding box dimensions from the widget rect
30
+ const x1 = rect[0] ?? 0;
31
+ const y1 = rect[1] ?? 0;
32
+ const x2 = rect[2] ?? x1;
33
+ const y2 = rect[3] ?? y1;
34
+ const width = Math.abs(x2 - x1);
35
+ const height = Math.abs(y2 - y1);
36
+ // Auto-size font: use ~70% of the field height, clamped to a reasonable range
37
+ let fontSize = options.fontSize ?? 0;
38
+ if (fontSize <= 0) {
39
+ fontSize = Math.max(4, Math.min(height * 0.7, 20));
40
+ }
41
+ // Approximate text width using 0.5 * fontSize per character (conservative estimate
42
+ // for Helvetica). This is intentionally simple — the appearance is a best-effort
43
+ // rendering; the authoritative value is in /V.
44
+ const approxCharWidth = fontSize * 0.5;
45
+ const textWidth = value.length * approxCharWidth;
46
+ // Compute horizontal offset based on alignment
47
+ let tx;
48
+ const usableWidth = width - 2 * FIELD_PADDING;
49
+ switch (alignment) {
50
+ case "center":
51
+ tx = FIELD_PADDING + Math.max(0, (usableWidth - textWidth) / 2);
52
+ break;
53
+ case "right":
54
+ tx = FIELD_PADDING + Math.max(0, usableWidth - textWidth);
55
+ break;
56
+ default:
57
+ tx = FIELD_PADDING;
58
+ break;
59
+ }
60
+ // Vertical centering: place baseline so the text is roughly centered
61
+ // Approximate ascent ≈ 0.75 * fontSize, descent ≈ -0.25 * fontSize
62
+ const ascent = fontSize * 0.75;
63
+ const descent = fontSize * 0.25;
64
+ const textHeight = ascent + descent;
65
+ const ty = (height - textHeight) / 2 + descent;
66
+ // Build the content stream
67
+ const cs = new PdfContentStream();
68
+ // Clip to the bounding box (form XObject coordinate space: 0,0 to width,height)
69
+ cs.rect(0, 0, width, height).clip().endPath();
70
+ // Draw the text
71
+ cs.beginText().setFont(fontName, fontSize).moveText(tx, ty).showText(value).endText();
72
+ const stream = cs.toUint8Array();
73
+ // Build a minimal resources dict referencing a standard Helvetica font.
74
+ // The font is declared inline as a Type1 font so the appearance stream
75
+ // is self-contained (no dependency on the page's resources).
76
+ const resources = `<< /Font << /${fontName} << /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >> >> >>`;
77
+ return { stream, resources };
78
+ }
79
+ // =============================================================================
80
+ // Checkbox Appearance
81
+ // =============================================================================
82
+ /**
83
+ * Generate appearance streams for a checkbox form field.
84
+ *
85
+ * Returns two streams:
86
+ * - `streamOn`: draws a checkmark (✓-like shape) inside the rect
87
+ * - `streamOff`: empty appearance (blank field)
88
+ *
89
+ * @param checked - Whether to generate the checked or unchecked variant
90
+ * (both are always returned; `checked` is ignored — both
91
+ * on and off streams are produced).
92
+ * @param rect - Widget annotation rectangle [x1, y1, x2, y2].
93
+ */
94
+ export function generateCheckboxAppearance(_checked, rect) {
95
+ const x1 = rect[0] ?? 0;
96
+ const y1 = rect[1] ?? 0;
97
+ const x2 = rect[2] ?? x1;
98
+ const y2 = rect[3] ?? y1;
99
+ const width = Math.abs(x2 - x1);
100
+ const height = Math.abs(y2 - y1);
101
+ // --- "On" appearance: draw an X ---
102
+ const onCs = new PdfContentStream();
103
+ const inset = Math.min(width, height) * 0.15;
104
+ onCs
105
+ .save()
106
+ .setStrokeColor({ r: 0, g: 0, b: 0 })
107
+ .setLineWidth(Math.max(1, Math.min(width, height) * 0.1))
108
+ // First stroke of the X: bottom-left to top-right
109
+ .moveTo(inset, inset)
110
+ .lineTo(width - inset, height - inset)
111
+ .stroke()
112
+ // Second stroke of the X: top-left to bottom-right
113
+ .moveTo(inset, height - inset)
114
+ .lineTo(width - inset, inset)
115
+ .stroke()
116
+ .restore();
117
+ // --- "Off" appearance: empty ---
118
+ const offCs = new PdfContentStream();
119
+ // Intentionally empty — nothing to draw for unchecked state
120
+ return {
121
+ streamOn: onCs.toUint8Array(),
122
+ streamOff: offCs.toUint8Array()
123
+ };
124
+ }
125
+ // =============================================================================
126
+ // Helpers
127
+ // =============================================================================
128
+ /**
129
+ * Build the BBox array string for a form XObject appearance stream.
130
+ * The bounding box is in the widget's coordinate space: [0 0 width height].
131
+ */
132
+ export function buildAppearanceBBox(rect) {
133
+ const x1 = rect[0] ?? 0;
134
+ const y1 = rect[1] ?? 0;
135
+ const x2 = rect[2] ?? x1;
136
+ const y2 = rect[3] ?? y1;
137
+ const width = Math.abs(x2 - x1);
138
+ const height = Math.abs(y2 - y1);
139
+ return `[0 0 ${pdfNumber(width)} ${pdfNumber(height)}]`;
140
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Shared image utilities for PDF generation.
3
+ *
4
+ * Centralises JPEG/PNG dimension parsing and PDF XObject writing so that
5
+ * both the builder (`document-builder.ts`) and the exporter (`pdf-exporter.ts`)
6
+ * share a single implementation.
7
+ */
8
+ import type { PdfWriter } from "../core/pdf-writer.js";
9
+ /**
10
+ * Parse image dimensions from raw bytes.
11
+ */
12
+ export declare function parseImageDimensions(data: Uint8Array, format: "jpeg" | "png"): {
13
+ width: number;
14
+ height: number;
15
+ };
16
+ /**
17
+ * Read width/height from a PNG IHDR chunk (bytes 16-23).
18
+ */
19
+ export declare function parsePngDimensions(data: Uint8Array): {
20
+ width: number;
21
+ height: number;
22
+ };
23
+ /**
24
+ * Read width/height from JPEG SOF marker.
25
+ *
26
+ * Correctly excludes non-SOF markers in the 0xC0-0xCF range:
27
+ * - 0xC4 = DHT (Define Huffman Table)
28
+ * - 0xC8 = JPG (reserved)
29
+ * - 0xCC = DAC (Define Arithmetic Coding)
30
+ */
31
+ export declare function parseJpegDimensions(data: Uint8Array): {
32
+ width: number;
33
+ height: number;
34
+ };
35
+ /**
36
+ * Write an image XObject (JPEG or PNG) to the writer.
37
+ * Returns the allocated object number.
38
+ */
39
+ export declare function writeImageXObject(writer: PdfWriter, data: Uint8Array, format: "jpeg" | "png"): number;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Shared image utilities for PDF generation.
3
+ *
4
+ * Centralises JPEG/PNG dimension parsing and PDF XObject writing so that
5
+ * both the builder (`document-builder.ts`) and the exporter (`pdf-exporter.ts`)
6
+ * share a single implementation.
7
+ */
8
+ import { PdfDict, pdfRef, pdfNumber } from "../core/pdf-object.js";
9
+ import { decodePng } from "../render/png-decoder.js";
10
+ // =============================================================================
11
+ // Image Dimension Parsing
12
+ // =============================================================================
13
+ /**
14
+ * Parse image dimensions from raw bytes.
15
+ */
16
+ export function parseImageDimensions(data, format) {
17
+ if (format === "png") {
18
+ return parsePngDimensions(data);
19
+ }
20
+ return parseJpegDimensions(data);
21
+ }
22
+ /**
23
+ * Read width/height from a PNG IHDR chunk (bytes 16-23).
24
+ */
25
+ export function parsePngDimensions(data) {
26
+ // PNG header: 8 byte signature, then IHDR chunk: 4 byte length, 4 byte type, 4 byte width, 4 byte height
27
+ if (data.length >= 24 &&
28
+ data[12] === 0x49 &&
29
+ data[13] === 0x48 &&
30
+ data[14] === 0x44 &&
31
+ data[15] === 0x52) {
32
+ const width = (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19];
33
+ const height = (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23];
34
+ return { width, height };
35
+ }
36
+ return { width: 1, height: 1 };
37
+ }
38
+ /**
39
+ * Read width/height from JPEG SOF marker.
40
+ *
41
+ * Correctly excludes non-SOF markers in the 0xC0-0xCF range:
42
+ * - 0xC4 = DHT (Define Huffman Table)
43
+ * - 0xC8 = JPG (reserved)
44
+ * - 0xCC = DAC (Define Arithmetic Coding)
45
+ */
46
+ export function parseJpegDimensions(data) {
47
+ let offset = 2; // skip SOI marker
48
+ while (offset < data.length - 1) {
49
+ // Skip padding 0xFF bytes
50
+ while (offset < data.length && data[offset] === 0xff && data[offset + 1] === 0xff) {
51
+ offset++;
52
+ }
53
+ if (offset >= data.length - 1 || data[offset] !== 0xff) {
54
+ break;
55
+ }
56
+ const marker = data[offset + 1];
57
+ const isSof = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
58
+ if (isSof && offset + 8 < data.length) {
59
+ return {
60
+ width: (data[offset + 7] << 8) | data[offset + 8],
61
+ height: (data[offset + 5] << 8) | data[offset + 6]
62
+ };
63
+ }
64
+ if (offset + 3 >= data.length) {
65
+ break;
66
+ }
67
+ const segLen = (data[offset + 2] << 8) | data[offset + 3];
68
+ offset += 2 + segLen;
69
+ }
70
+ return { width: 1, height: 1 };
71
+ }
72
+ // =============================================================================
73
+ // PDF Image XObject Writing
74
+ // =============================================================================
75
+ /**
76
+ * Write an image XObject (JPEG or PNG) to the writer.
77
+ * Returns the allocated object number.
78
+ */
79
+ export function writeImageXObject(writer, data, format) {
80
+ if (format === "png") {
81
+ return writePngImageXObject(writer, data);
82
+ }
83
+ return writeJpegImageXObject(writer, data);
84
+ }
85
+ /**
86
+ * Write a JPEG image using DCTDecode (raw JPEG data embedded directly).
87
+ */
88
+ function writeJpegImageXObject(writer, data) {
89
+ const objNum = writer.allocObject();
90
+ const dims = parseJpegDimensions(data);
91
+ const dict = new PdfDict()
92
+ .set("Type", "/XObject")
93
+ .set("Subtype", "/Image")
94
+ .set("Width", pdfNumber(dims.width))
95
+ .set("Height", pdfNumber(dims.height))
96
+ .set("ColorSpace", "/DeviceRGB")
97
+ .set("BitsPerComponent", "8")
98
+ .set("Filter", "/DCTDecode");
99
+ writer.addStreamObject(objNum, dict, data);
100
+ return objNum;
101
+ }
102
+ /**
103
+ * Write a PNG image: decode to raw RGB, create SMask for alpha if needed.
104
+ */
105
+ function writePngImageXObject(writer, data) {
106
+ const png = decodePng(data);
107
+ const objNum = writer.allocObject();
108
+ const dict = new PdfDict()
109
+ .set("Type", "/XObject")
110
+ .set("Subtype", "/Image")
111
+ .set("Width", pdfNumber(png.width))
112
+ .set("Height", pdfNumber(png.height))
113
+ .set("ColorSpace", "/DeviceRGB")
114
+ .set("BitsPerComponent", pdfNumber(png.bitsPerComponent));
115
+ if (png.alpha) {
116
+ const smaskObjNum = writer.allocObject();
117
+ const smaskDict = new PdfDict()
118
+ .set("Type", "/XObject")
119
+ .set("Subtype", "/Image")
120
+ .set("Width", pdfNumber(png.width))
121
+ .set("Height", pdfNumber(png.height))
122
+ .set("ColorSpace", "/DeviceGray")
123
+ .set("BitsPerComponent", "8");
124
+ writer.addStreamObject(smaskObjNum, smaskDict, png.alpha);
125
+ dict.set("SMask", pdfRef(smaskObjNum));
126
+ }
127
+ writer.addStreamObject(objNum, dict, png.pixels);
128
+ return objNum;
129
+ }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * PDF editor — modify existing PDF documents.
3
+ *
4
+ * Supports:
5
+ * - Adding new pages with free-form content
6
+ * - Adding text/shapes/images to existing pages (overlay)
7
+ * - Filling form fields (AcroForm)
8
+ * - Copying pages from other PDFs (merge)
9
+ * - Preserving page properties (Rotate, CropBox, etc.) and metadata
10
+ *
11
+ * Note: save() rebuilds the PDF from scratch rather than using incremental
12
+ * updates. This is simpler and more reliable but means object numbers change
13
+ * and existing digital signatures will be invalidated.
14
+ *
15
+ * @example Edit an existing PDF:
16
+ * ```typescript
17
+ * import { PdfEditor } from "@cj-tech-master/excelts/pdf";
18
+ *
19
+ * const editor = PdfEditor.load(existingPdfBytes);
20
+ * editor.getPage(0).drawText("APPROVED", { x: 200, y: 400, fontSize: 48, color: { r: 0, g: 0.5, b: 0 } });
21
+ * editor.setFormField("name", "John Doe");
22
+ * const result = await editor.save();
23
+ * ```
24
+ */
25
+ import type { PdfContentStream } from "../core/pdf-stream.js";
26
+ import type { PdfFormField } from "../reader/form-extractor.js";
27
+ import { PdfPageBuilder } from "./document-builder.js";
28
+ import type { DrawTextOptions, DrawRectOptions, DrawCircleOptions, DrawEllipseOptions, DrawLineOptions, DrawImageOptions, DrawPathOptions, PathOp, PageOptions, AnnotationOptions, FormFieldOptions, PdfSignatureOptions } from "./document-builder.js";
29
+ /** Options for loading a PDF for editing. */
30
+ export interface LoadOptions {
31
+ /** Password for encrypted PDFs. */
32
+ password?: string;
33
+ }
34
+ /**
35
+ * Proxy for an existing page that allows overlaying new content.
36
+ * New content is drawn on top of existing content via a separate content stream.
37
+ */
38
+ export declare class PdfEditorPage {
39
+ /** Page width in points. */
40
+ get width(): number;
41
+ /** Page height in points. */
42
+ get height(): number;
43
+ /**
44
+ * Measure the width of a text string in points.
45
+ */
46
+ measureText(text: string, options?: {
47
+ fontSize?: number;
48
+ fontFamily?: string;
49
+ bold?: boolean;
50
+ italic?: boolean;
51
+ }): number;
52
+ /**
53
+ * Draw text on this existing page (overlaid on top).
54
+ */
55
+ drawText(text: string, options: DrawTextOptions): this;
56
+ /**
57
+ * Draw a rectangle on this existing page.
58
+ */
59
+ drawRect(options: DrawRectOptions): this;
60
+ /**
61
+ * Draw a circle on this existing page.
62
+ */
63
+ drawCircle(options: DrawCircleOptions): this;
64
+ /**
65
+ * Draw an ellipse on this existing page.
66
+ */
67
+ drawEllipse(options: DrawEllipseOptions): this;
68
+ /**
69
+ * Draw a line on this existing page.
70
+ */
71
+ drawLine(options: DrawLineOptions): this;
72
+ /**
73
+ * Draw an image on this existing page.
74
+ */
75
+ drawImage(options: DrawImageOptions): this;
76
+ /**
77
+ * Get the raw overlay content stream.
78
+ */
79
+ getContentStream(): PdfContentStream;
80
+ /**
81
+ * Add an annotation to this existing page (Highlight, Text, FreeText, Stamp, etc.).
82
+ */
83
+ addAnnotation(options: AnnotationOptions): this;
84
+ /**
85
+ * Add a form field to this existing page.
86
+ */
87
+ addFormField(options: FormFieldOptions): this;
88
+ /**
89
+ * Draw an SVG path on this existing page.
90
+ */
91
+ drawSvgPath(d: string, options?: DrawPathOptions): this;
92
+ /**
93
+ * Draw a complex path from a list of path operations.
94
+ */
95
+ drawPath(ops: PathOp[], options?: DrawPathOptions): this;
96
+ }
97
+ /**
98
+ * Editor for modifying existing PDF documents.
99
+ *
100
+ * Load an existing PDF, overlay content on existing pages, fill form fields,
101
+ * add new pages, copy pages from other documents, and save.
102
+ */
103
+ export declare class PdfEditor {
104
+ private _doc;
105
+ private _password;
106
+ private _pages;
107
+ private _newPages;
108
+ private _fontManager;
109
+ private _formFieldUpdates;
110
+ private _copiedPages;
111
+ private _signaturePlaceholder;
112
+ private constructor();
113
+ /**
114
+ * Load a PDF for editing.
115
+ *
116
+ * @param data - Raw PDF file bytes
117
+ * @param options - Load options (e.g., password)
118
+ * @returns A PdfEditor instance
119
+ */
120
+ static load(data: Uint8Array, options?: LoadOptions): PdfEditor;
121
+ /** Number of existing pages. */
122
+ get pageCount(): number;
123
+ /**
124
+ * Get an existing page for editing (overlaying content).
125
+ *
126
+ * @param index - 0-based page index
127
+ */
128
+ getPage(index: number): PdfEditorPage;
129
+ /**
130
+ * Add a new blank page to the end of the document.
131
+ */
132
+ addPage(options?: PageOptions): PdfPageBuilder;
133
+ /**
134
+ * Remove a page from the document.
135
+ *
136
+ * @param index - 0-based page index (of original pages only)
137
+ */
138
+ removePage(index: number): this;
139
+ /**
140
+ * Set the rotation of an existing page.
141
+ *
142
+ * @param index - 0-based page index (of original pages only)
143
+ * @param degrees - Rotation in degrees (must be 0, 90, 180, or 270)
144
+ */
145
+ rotatePage(index: number, degrees: number): this;
146
+ /**
147
+ * Split the document: save each page (or a subset) as a separate PDF.
148
+ *
149
+ * @param pageIndices - 0-based page indices to extract. Omit to split all pages.
150
+ * @returns Array of Uint8Array, one per requested page.
151
+ */
152
+ splitPages(pageIndices?: number[]): Promise<Uint8Array[]>;
153
+ /**
154
+ * Embed a TrueType font for Unicode/CJK support.
155
+ */
156
+ embedFont(fontBytes: Uint8Array): this;
157
+ /**
158
+ * Set the value of a form field.
159
+ * The field is identified by its fully qualified name (e.g., "form.address.city").
160
+ *
161
+ * @param fieldName - Fully qualified field name
162
+ * @param value - New value to set
163
+ */
164
+ setFormField(fieldName: string, value: string): this;
165
+ /**
166
+ * Set multiple form field values at once.
167
+ *
168
+ * @param fields - Object mapping field names to values
169
+ */
170
+ setFormFields(fields: Record<string, string>): this;
171
+ /**
172
+ * Get current form fields (before any modifications).
173
+ */
174
+ getFormFields(): PdfFormField[];
175
+ /**
176
+ * Copy pages from another PDF document into this document.
177
+ *
178
+ * @param sourcePdf - Raw bytes of the source PDF
179
+ * @param pageIndices - 0-based page indices to copy. Omit to copy all pages.
180
+ * @param options - Load options for the source PDF (e.g., password)
181
+ */
182
+ copyPagesFrom(sourcePdf: Uint8Array, pageIndices?: number[], options?: LoadOptions): this;
183
+ /**
184
+ * Save the modified PDF.
185
+ *
186
+ * Rebuilds the PDF from scratch — content streams, resources, and page
187
+ * properties are deep-cloned into a new document. Original metadata and
188
+ * XMP streams are preserved. Digital signatures will be invalidated.
189
+ *
190
+ * @returns The modified PDF as Uint8Array
191
+ */
192
+ save(): Promise<Uint8Array>;
193
+ /**
194
+ * Save the modified PDF using incremental update.
195
+ *
196
+ * Appends new/modified objects after the original PDF bytes, preserving the
197
+ * original data byte-for-byte. This is ideal for overlays and form field
198
+ * updates on existing pages — it preserves digital signatures on unmodified
199
+ * content and produces smaller output.
200
+ *
201
+ * Falls back to {@link save} (full rebuild) if structural changes are
202
+ * present (new pages, copied pages, or removed pages).
203
+ *
204
+ * @returns The modified PDF as Uint8Array
205
+ */
206
+ saveIncremental(): Promise<Uint8Array>;
207
+ /** Page-level keys to preserve when rebuilding page dicts. */
208
+ private static readonly _PAGE_PRESERVE_KEYS;
209
+ /**
210
+ * Sign this PDF with a digital signature.
211
+ *
212
+ * Performs a full save with an embedded PKCS#7 signature placeholder,
213
+ * then fills in the real CMS SignedData.
214
+ *
215
+ * @param options - Certificate, private key, and optional signer metadata
216
+ * @returns The signed PDF as Uint8Array
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * const editor = PdfEditor.load(pdfBytes);
221
+ * const signed = await editor.sign({
222
+ * certificate: certDerBytes,
223
+ * privateKey: pkcs8DerBytes,
224
+ * name: "Jane Doe",
225
+ * reason: "Approval"
226
+ * });
227
+ * ```
228
+ */
229
+ sign(options: PdfSignatureOptions): Promise<Uint8Array>;
230
+ }