pdf-codec 1.3.0 → 1.4.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 (198) hide show
  1. package/README.md +38 -0
  2. package/dist/afm-widths-BoeTOK2r.cjs +826 -0
  3. package/dist/afm-widths-Dxucrg7D.js +779 -0
  4. package/dist/afm-widths-DyDq56Ph.d.cts +19 -0
  5. package/dist/afm-widths-DyDq56Ph.d.ts +19 -0
  6. package/dist/afm-widths.cjs +4 -0
  7. package/dist/afm-widths.d.cts +2 -0
  8. package/dist/afm-widths.d.ts +2 -0
  9. package/dist/afm-widths.js +2 -0
  10. package/dist/assets/stix-two-math-font.cjs +5 -0
  11. package/dist/assets/stix-two-math-font.d.cts +4 -0
  12. package/dist/assets/stix-two-math-font.d.ts +4 -0
  13. package/dist/assets/stix-two-math-font.js +4 -0
  14. package/dist/bytes/crc32.cjs +21 -0
  15. package/dist/bytes/crc32.d.cts +4 -0
  16. package/dist/bytes/crc32.d.ts +4 -0
  17. package/dist/bytes/crc32.js +20 -0
  18. package/dist/bytes/flate.cjs +53 -0
  19. package/dist/bytes/flate.d.cts +12 -0
  20. package/dist/bytes/flate.d.ts +12 -0
  21. package/dist/bytes/flate.js +49 -0
  22. package/dist/bytes/reader.cjs +60 -0
  23. package/dist/bytes/reader.d.cts +20 -0
  24. package/dist/bytes/reader.d.ts +20 -0
  25. package/dist/bytes/reader.js +58 -0
  26. package/dist/bytes/writer.cjs +37 -0
  27. package/dist/bytes/writer.d.cts +13 -0
  28. package/dist/bytes/writer.d.ts +13 -0
  29. package/dist/bytes/writer.js +35 -0
  30. package/dist/cmap.cjs +110 -0
  31. package/dist/cmap.d.cts +8 -0
  32. package/dist/cmap.d.ts +8 -0
  33. package/dist/cmap.js +109 -0
  34. package/dist/codec.cjs +34 -0
  35. package/dist/codec.d.cts +167 -0
  36. package/dist/codec.d.ts +167 -0
  37. package/dist/codec.js +32 -0
  38. package/dist/content-read.cjs +126 -0
  39. package/dist/content-read.d.cts +21 -0
  40. package/dist/content-read.d.ts +21 -0
  41. package/dist/content-read.js +125 -0
  42. package/dist/content-write.cjs +138 -0
  43. package/dist/content-write.d.cts +24 -0
  44. package/dist/content-write.d.ts +24 -0
  45. package/dist/content-write.js +137 -0
  46. package/dist/diagnostics.cjs +21 -0
  47. package/dist/diagnostics.d.cts +19 -0
  48. package/dist/diagnostics.d.ts +19 -0
  49. package/dist/diagnostics.js +18 -0
  50. package/dist/document.cjs +162 -0
  51. package/dist/document.d.cts +12 -0
  52. package/dist/document.d.ts +12 -0
  53. package/dist/document.js +161 -0
  54. package/dist/encoding.cjs +5 -0
  55. package/dist/encoding.d.cts +6 -0
  56. package/dist/encoding.d.ts +6 -0
  57. package/dist/encoding.js +2 -0
  58. package/dist/filters.cjs +223 -0
  59. package/dist/filters.d.cts +14 -0
  60. package/dist/filters.d.ts +14 -0
  61. package/dist/filters.js +218 -0
  62. package/dist/font-read.cjs +199 -0
  63. package/dist/font-read.d.cts +23 -0
  64. package/dist/font-read.d.ts +23 -0
  65. package/dist/font-read.js +198 -0
  66. package/dist/font-style.cjs +35 -0
  67. package/dist/font-style.d.cts +14 -0
  68. package/dist/font-style.d.ts +14 -0
  69. package/dist/font-style.js +34 -0
  70. package/dist/fonts.cjs +124 -0
  71. package/dist/fonts.d.cts +15 -0
  72. package/dist/fonts.d.ts +15 -0
  73. package/dist/fonts.js +122 -0
  74. package/dist/formula.cjs +0 -0
  75. package/dist/formula.d.cts +10 -0
  76. package/dist/formula.d.ts +10 -0
  77. package/dist/formula.js +0 -0
  78. package/dist/image/jpeg-info.cjs +71 -0
  79. package/dist/image/jpeg-info.d.cts +12 -0
  80. package/dist/image/jpeg-info.d.ts +12 -0
  81. package/dist/image/jpeg-info.js +70 -0
  82. package/dist/image/png-decode.cjs +181 -0
  83. package/dist/image/png-decode.d.cts +14 -0
  84. package/dist/image/png-decode.d.ts +14 -0
  85. package/dist/image/png-decode.js +180 -0
  86. package/dist/image/png-encode.cjs +64 -0
  87. package/dist/image/png-encode.d.cts +8 -0
  88. package/dist/image/png-encode.d.ts +8 -0
  89. package/dist/image/png-encode.js +63 -0
  90. package/dist/image/png-filter.cjs +95 -0
  91. package/dist/image/png-filter.d.cts +6 -0
  92. package/dist/image/png-filter.d.ts +6 -0
  93. package/dist/image/png-filter.js +93 -0
  94. package/dist/images-read.cjs +250 -0
  95. package/dist/images-read.d.cts +13 -0
  96. package/dist/images-read.d.ts +13 -0
  97. package/dist/images-read.js +249 -0
  98. package/dist/index.cjs +45 -5912
  99. package/dist/index.d.cts +21 -483
  100. package/dist/index.d.ts +21 -483
  101. package/dist/index.js +18 -5885
  102. package/dist/interpret.cjs +587 -0
  103. package/dist/interpret.d.cts +84 -0
  104. package/dist/interpret.d.ts +84 -0
  105. package/dist/interpret.js +586 -0
  106. package/dist/lexer.cjs +180 -0
  107. package/dist/lexer.d.cts +29 -0
  108. package/dist/lexer.d.ts +29 -0
  109. package/dist/lexer.js +179 -0
  110. package/dist/math-cmap.cjs +81 -0
  111. package/dist/math-cmap.d.cts +6 -0
  112. package/dist/math-cmap.d.ts +6 -0
  113. package/dist/math-cmap.js +80 -0
  114. package/dist/math-content-write.cjs +83 -0
  115. package/dist/math-content-write.d.cts +11 -0
  116. package/dist/math-content-write.d.ts +11 -0
  117. package/dist/math-content-write.js +81 -0
  118. package/dist/math-font-write.cjs +100 -0
  119. package/dist/math-font-write.d.cts +19 -0
  120. package/dist/math-font-write.d.ts +19 -0
  121. package/dist/math-font-write.js +99 -0
  122. package/dist/math-font.cjs +103 -0
  123. package/dist/math-font.d.cts +25 -0
  124. package/dist/math-font.d.ts +25 -0
  125. package/dist/math-font.js +102 -0
  126. package/dist/math-hmtx.cjs +17 -0
  127. package/dist/math-hmtx.d.cts +8 -0
  128. package/dist/math-hmtx.d.ts +8 -0
  129. package/dist/math-hmtx.js +16 -0
  130. package/dist/math-table.cjs +117 -0
  131. package/dist/math-table.d.cts +45 -0
  132. package/dist/math-table.d.ts +45 -0
  133. package/dist/math-table.js +116 -0
  134. package/dist/math-types-Ba8qAf1p.d.cts +82 -0
  135. package/dist/math-types-Ba8qAf1p.d.ts +82 -0
  136. package/dist/math-types.cjs +0 -0
  137. package/dist/math-types.d.cts +2 -0
  138. package/dist/math-types.d.ts +2 -0
  139. package/dist/math-types.js +0 -0
  140. package/dist/matrix-B3c2_a2f.d.cts +18 -0
  141. package/dist/matrix-B3c2_a2f.d.ts +18 -0
  142. package/dist/matrix.cjs +93 -0
  143. package/dist/matrix.d.cts +2 -0
  144. package/dist/matrix.d.ts +2 -0
  145. package/dist/matrix.js +83 -0
  146. package/dist/measure.cjs +54 -0
  147. package/dist/measure.d.cts +20 -0
  148. package/dist/measure.d.ts +20 -0
  149. package/dist/measure.js +53 -0
  150. package/dist/objects.cjs +100 -0
  151. package/dist/objects.d.cts +58 -0
  152. package/dist/objects.d.ts +58 -0
  153. package/dist/objects.js +84 -0
  154. package/dist/parse.cjs +230 -0
  155. package/dist/parse.d.cts +13 -0
  156. package/dist/parse.d.ts +13 -0
  157. package/dist/parse.js +228 -0
  158. package/dist/predictors.cjs +64 -0
  159. package/dist/predictors.d.cts +13 -0
  160. package/dist/predictors.d.ts +13 -0
  161. package/dist/predictors.js +62 -0
  162. package/dist/read.cjs +418 -0
  163. package/dist/read.d.cts +21 -0
  164. package/dist/read.d.ts +21 -0
  165. package/dist/read.js +413 -0
  166. package/dist/serialize.cjs +69 -0
  167. package/dist/serialize.d.cts +8 -0
  168. package/dist/serialize.d.ts +8 -0
  169. package/dist/serialize.js +66 -0
  170. package/dist/sfnt.cjs +52 -0
  171. package/dist/sfnt.d.cts +17 -0
  172. package/dist/sfnt.d.ts +17 -0
  173. package/dist/sfnt.js +46 -0
  174. package/dist/text-layout.cjs +224 -0
  175. package/dist/text-layout.d.cts +37 -0
  176. package/dist/text-layout.d.ts +37 -0
  177. package/dist/text-layout.js +222 -0
  178. package/dist/util/abort.cjs +7 -0
  179. package/dist/util/abort.d.cts +4 -0
  180. package/dist/util/abort.d.ts +4 -0
  181. package/dist/util/abort.js +6 -0
  182. package/dist/util/base64.cjs +48 -0
  183. package/dist/util/base64.d.cts +5 -0
  184. package/dist/util/base64.d.ts +5 -0
  185. package/dist/util/base64.js +46 -0
  186. package/dist/winansi.cjs +5 -0
  187. package/dist/winansi.d.cts +20 -0
  188. package/dist/winansi.d.ts +20 -0
  189. package/dist/winansi.js +2 -0
  190. package/dist/write.cjs +421 -0
  191. package/dist/write.d.cts +16 -0
  192. package/dist/write.d.ts +16 -0
  193. package/dist/write.js +419 -0
  194. package/dist/xref.cjs +325 -0
  195. package/dist/xref.d.cts +21 -0
  196. package/dist/xref.d.ts +21 -0
  197. package/dist/xref.js +324 -0
  198. package/package.json +9 -1
@@ -0,0 +1,250 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_objects = require("./objects.cjs");
3
+ const require_filters = require("./filters.cjs");
4
+ const require_image_png_encode = require("./image/png-encode.cjs");
5
+ const require_image_jpeg_info = require("./image/jpeg-info.cjs");
6
+ //#region src/images-read.ts
7
+ function componentsOf(cs) {
8
+ if (cs.kind === "gray") return 1;
9
+ if (cs.kind === "cmyk") return 4;
10
+ return 3;
11
+ }
12
+ function resolveColorSpace(csObj, resolver, sink) {
13
+ const resolved = resolver.resolve(csObj);
14
+ const directName = resolved?.kind === "name" ? resolved.name : void 0;
15
+ if (directName !== void 0) {
16
+ if (directName === "DeviceGray" || directName === "CalGray" || directName === "G") return { kind: "gray" };
17
+ if (directName === "DeviceRGB" || directName === "CalRGB" || directName === "RGB") return { kind: "rgb" };
18
+ if (directName === "DeviceCMYK" || directName === "CMYK") return { kind: "cmyk" };
19
+ return {
20
+ kind: "unsupported",
21
+ name: directName
22
+ };
23
+ }
24
+ const arr = resolved?.kind === "array" ? resolved.items : void 0;
25
+ if (arr === void 0) return {
26
+ kind: "unsupported",
27
+ name: "(missing or invalid /ColorSpace)"
28
+ };
29
+ const family = require_objects.asName(arr[0]);
30
+ if (family === "ICCBased") {
31
+ const streamObj = resolver.resolve(arr[1]);
32
+ const n = streamObj?.kind === "stream" ? require_objects.asNumber(require_objects.dictGet(streamObj.dict, "N")) : void 0;
33
+ if (n === 1) return { kind: "gray" };
34
+ if (n === 4) return { kind: "cmyk" };
35
+ return { kind: "rgb" };
36
+ }
37
+ if (family === "Indexed") {
38
+ const base = resolveColorSpace(arr[1], resolver, sink);
39
+ const lookupObj = resolver.resolve(arr[3]);
40
+ let lookup;
41
+ if (lookupObj?.kind === "stream") lookup = require_filters.decodeStream(lookupObj.raw, lookupObj.dict, sink).bytes;
42
+ else if (lookupObj?.kind === "string") lookup = lookupObj.bytes;
43
+ else lookup = /* @__PURE__ */ new Uint8Array(0);
44
+ return {
45
+ kind: "indexed",
46
+ base,
47
+ lookup
48
+ };
49
+ }
50
+ if (family === "CalGray") return { kind: "gray" };
51
+ if (family === "CalRGB") return { kind: "rgb" };
52
+ return {
53
+ kind: "unsupported",
54
+ name: family ?? "(unrecognised array colour space)"
55
+ };
56
+ }
57
+ function unpackSamples(data, width, height, componentsPerPixel, bitsPerComponent) {
58
+ if (bitsPerComponent === 8) return Array.from(data.subarray(0, width * height * componentsPerPixel));
59
+ const samplesPerRow = width * componentsPerPixel;
60
+ const bytesPerRow = Math.ceil(samplesPerRow * bitsPerComponent / 8);
61
+ const out = [];
62
+ for (let row = 0; row < height; row++) {
63
+ const rowStart = row * bytesPerRow;
64
+ let bitPos = 0;
65
+ for (let s = 0; s < samplesPerRow; s++) {
66
+ let value = 0;
67
+ for (let b = 0; b < bitsPerComponent; b++) {
68
+ const byteIndex = rowStart + Math.floor(bitPos / 8);
69
+ const bitIndex = 7 - bitPos % 8;
70
+ const bit = (data[byteIndex] ?? 0) >> bitIndex & 1;
71
+ value = value << 1 | bit;
72
+ bitPos++;
73
+ }
74
+ out.push(value);
75
+ }
76
+ }
77
+ return out;
78
+ }
79
+ function scaleToByte(value, maxValue, inverted) {
80
+ const v = inverted ? maxValue - value : value;
81
+ return maxValue === 255 ? v : Math.round(v * 255 / maxValue);
82
+ }
83
+ function cmykToRgbByte(c, m, y, k) {
84
+ return {
85
+ r: Math.round(255 * (1 - c) * (1 - k)),
86
+ g: Math.round(255 * (1 - m) * (1 - k)),
87
+ b: Math.round(255 * (1 - y) * (1 - k))
88
+ };
89
+ }
90
+ function sampleFromPalette(base, lookup, offset) {
91
+ if (base.kind === "gray") {
92
+ const g = lookup[offset] ?? 0;
93
+ return {
94
+ r: g,
95
+ g,
96
+ b: g
97
+ };
98
+ }
99
+ if (base.kind === "cmyk") return cmykToRgbByte((lookup[offset] ?? 0) / 255, (lookup[offset + 1] ?? 0) / 255, (lookup[offset + 2] ?? 0) / 255, (lookup[offset + 3] ?? 0) / 255);
100
+ return {
101
+ r: lookup[offset] ?? 0,
102
+ g: lookup[offset + 1] ?? 0,
103
+ b: lookup[offset + 2] ?? 0
104
+ };
105
+ }
106
+ function buildRawImage(data, width, height, bitsPerComponent, colorSpace, inverted) {
107
+ const maxValue = (1 << bitsPerComponent) - 1;
108
+ if (colorSpace.kind === "indexed") {
109
+ const indices = unpackSamples(data, width, height, 1, bitsPerComponent);
110
+ const baseComponents = componentsOf(colorSpace.base);
111
+ const out = new Uint8Array(width * height * (colorSpace.base.kind === "gray" ? 1 : 3));
112
+ let outIdx = 0;
113
+ for (const index of indices) {
114
+ const rgb = sampleFromPalette(colorSpace.base, colorSpace.lookup, index * baseComponents);
115
+ if (colorSpace.base.kind === "gray") out[outIdx++] = rgb.r;
116
+ else {
117
+ out[outIdx++] = rgb.r;
118
+ out[outIdx++] = rgb.g;
119
+ out[outIdx++] = rgb.b;
120
+ }
121
+ }
122
+ return {
123
+ width,
124
+ height,
125
+ channels: colorSpace.base.kind === "gray" ? 1 : 3,
126
+ data: out
127
+ };
128
+ }
129
+ if (colorSpace.kind === "gray") {
130
+ const samples = unpackSamples(data, width, height, 1, bitsPerComponent);
131
+ return {
132
+ width,
133
+ height,
134
+ channels: 1,
135
+ data: Uint8Array.from(samples, (v) => scaleToByte(v, maxValue, inverted))
136
+ };
137
+ }
138
+ if (colorSpace.kind === "rgb") {
139
+ const samples = unpackSamples(data, width, height, 3, bitsPerComponent);
140
+ return {
141
+ width,
142
+ height,
143
+ channels: 3,
144
+ data: Uint8Array.from(samples, (v) => scaleToByte(v, maxValue, inverted))
145
+ };
146
+ }
147
+ const samples = unpackSamples(data, width, height, 4, bitsPerComponent);
148
+ const out = new Uint8Array(width * height * 3);
149
+ for (let px = 0; px < width * height; px++) {
150
+ const rgb = cmykToRgbByte(scaleToByte(samples[px * 4] ?? 0, maxValue, inverted) / 255, scaleToByte(samples[px * 4 + 1] ?? 0, maxValue, inverted) / 255, scaleToByte(samples[px * 4 + 2] ?? 0, maxValue, inverted) / 255, scaleToByte(samples[px * 4 + 3] ?? 0, maxValue, inverted) / 255);
151
+ out[px * 3] = rgb.r;
152
+ out[px * 3 + 1] = rgb.g;
153
+ out[px * 3 + 2] = rgb.b;
154
+ }
155
+ return {
156
+ width,
157
+ height,
158
+ channels: 3,
159
+ data: out
160
+ };
161
+ }
162
+ function readSoftMaskAlpha(dict, width, height, resolver, sink) {
163
+ const smaskObj = resolver.resolve(require_objects.dictGet(dict, "SMask"));
164
+ if (smaskObj?.kind !== "stream") return;
165
+ const decoded = require_filters.decodeStream(smaskObj.raw, smaskObj.dict, sink);
166
+ if (decoded.remainingFilter !== void 0) return;
167
+ const smaskWidth = require_objects.asNumber(require_objects.dictGet(smaskObj.dict, "Width")) ?? width;
168
+ const smaskHeight = require_objects.asNumber(require_objects.dictGet(smaskObj.dict, "Height")) ?? height;
169
+ const smaskBpc = require_objects.asNumber(require_objects.dictGet(smaskObj.dict, "BitsPerComponent")) ?? 8;
170
+ if (smaskWidth !== width || smaskHeight !== height || smaskBpc !== 8) return;
171
+ return decoded.bytes.subarray(0, width * height);
172
+ }
173
+ function isTrue(obj) {
174
+ return obj?.kind === "bool" && obj.value;
175
+ }
176
+ function readImageXObject(dict, raw, resolver, sink) {
177
+ if (isTrue(require_objects.dictGet(dict, "ImageMask") ?? require_objects.dictGet(dict, "IM"))) {
178
+ sink({
179
+ code: "image/mask-unsupported",
180
+ severity: "info",
181
+ message: "an /ImageMask stencil paints with the current fill colour rather than standing alone as an image; skipping"
182
+ });
183
+ return;
184
+ }
185
+ const decoded = require_filters.decodeStream(raw, dict, sink);
186
+ if (decoded.remainingFilter === "DCTDecode") {
187
+ let info;
188
+ try {
189
+ info = require_image_jpeg_info.readJpegInfo(decoded.bytes);
190
+ } catch {
191
+ sink({
192
+ code: "image/undecodable",
193
+ severity: "warning",
194
+ message: "DCTDecode image bytes did not look like a valid JPEG (no SOF marker found); skipping this image"
195
+ });
196
+ return;
197
+ }
198
+ return {
199
+ format: "jpeg",
200
+ bytes: decoded.bytes,
201
+ widthPx: info.width,
202
+ heightPx: info.height
203
+ };
204
+ }
205
+ if (decoded.remainingFilter !== void 0) return;
206
+ const width = require_objects.asNumber(require_objects.dictGet(dict, "Width") ?? require_objects.dictGet(dict, "W"));
207
+ const height = require_objects.asNumber(require_objects.dictGet(dict, "Height") ?? require_objects.dictGet(dict, "H"));
208
+ if (width === void 0 || height === void 0 || width <= 0 || height <= 0) {
209
+ sink({
210
+ code: "image/undecodable",
211
+ severity: "warning",
212
+ message: "image XObject is missing a valid /Width or /Height; skipping"
213
+ });
214
+ return;
215
+ }
216
+ const bitsPerComponent = require_objects.asNumber(require_objects.dictGet(dict, "BitsPerComponent") ?? require_objects.dictGet(dict, "BPC")) ?? 8;
217
+ if (bitsPerComponent !== 1 && bitsPerComponent !== 2 && bitsPerComponent !== 4 && bitsPerComponent !== 8) {
218
+ sink({
219
+ code: "image/unsupported-bit-depth",
220
+ severity: "warning",
221
+ message: `image has an unsupported /BitsPerComponent (${String(bitsPerComponent)}); skipping`
222
+ });
223
+ return;
224
+ }
225
+ const colorSpace = resolveColorSpace(require_objects.dictGet(dict, "ColorSpace") ?? require_objects.dictGet(dict, "CS"), resolver, sink);
226
+ if (colorSpace.kind === "unsupported") {
227
+ sink({
228
+ code: "image/unsupported-colorspace",
229
+ severity: "warning",
230
+ message: `image has an unsupported colour space (${colorSpace.name}); skipping`
231
+ });
232
+ return;
233
+ }
234
+ const decodeArr = require_objects.asArray(require_objects.dictGet(dict, "Decode"));
235
+ const inverted = decodeArr !== void 0 && require_objects.asNumber(decodeArr[0]) === 1 && require_objects.asNumber(decodeArr[1]) === 0;
236
+ const rawImage = buildRawImage(decoded.bytes, width, height, bitsPerComponent, colorSpace, inverted);
237
+ const alpha = readSoftMaskAlpha(dict, width, height, resolver, sink);
238
+ const withAlpha = alpha !== void 0 ? {
239
+ ...rawImage,
240
+ alpha
241
+ } : rawImage;
242
+ return {
243
+ format: "png",
244
+ bytes: require_image_png_encode.encodePng(withAlpha),
245
+ widthPx: width,
246
+ heightPx: height
247
+ };
248
+ }
249
+ //#endregion
250
+ exports.readImageXObject = readImageXObject;
@@ -0,0 +1,13 @@
1
+ import { PdfDiagnosticSink } from "./diagnostics.cjs";
2
+ import { PdfDict } from "./objects.cjs";
3
+ import { PdfObjectResolver } from "./interpret.cjs";
4
+ //#region src/images-read.d.ts
5
+ interface ExtractedPdfImage {
6
+ readonly format: 'png' | 'jpeg';
7
+ readonly bytes: Uint8Array<ArrayBuffer>;
8
+ readonly widthPx: number;
9
+ readonly heightPx: number;
10
+ }
11
+ declare function readImageXObject(dict: PdfDict, raw: Uint8Array<ArrayBuffer>, resolver: PdfObjectResolver, sink: PdfDiagnosticSink): ExtractedPdfImage | undefined;
12
+ //#endregion
13
+ export { ExtractedPdfImage, readImageXObject };
@@ -0,0 +1,13 @@
1
+ import { PdfDiagnosticSink } from "./diagnostics.js";
2
+ import { PdfDict } from "./objects.js";
3
+ import { PdfObjectResolver } from "./interpret.js";
4
+ //#region src/images-read.d.ts
5
+ interface ExtractedPdfImage {
6
+ readonly format: 'png' | 'jpeg';
7
+ readonly bytes: Uint8Array<ArrayBuffer>;
8
+ readonly widthPx: number;
9
+ readonly heightPx: number;
10
+ }
11
+ declare function readImageXObject(dict: PdfDict, raw: Uint8Array<ArrayBuffer>, resolver: PdfObjectResolver, sink: PdfDiagnosticSink): ExtractedPdfImage | undefined;
12
+ //#endregion
13
+ export { ExtractedPdfImage, readImageXObject };
@@ -0,0 +1,249 @@
1
+ import { asArray, asName, asNumber, dictGet } from "./objects.js";
2
+ import { decodeStream } from "./filters.js";
3
+ import { encodePng } from "./image/png-encode.js";
4
+ import { readJpegInfo } from "./image/jpeg-info.js";
5
+ //#region src/images-read.ts
6
+ function componentsOf(cs) {
7
+ if (cs.kind === "gray") return 1;
8
+ if (cs.kind === "cmyk") return 4;
9
+ return 3;
10
+ }
11
+ function resolveColorSpace(csObj, resolver, sink) {
12
+ const resolved = resolver.resolve(csObj);
13
+ const directName = resolved?.kind === "name" ? resolved.name : void 0;
14
+ if (directName !== void 0) {
15
+ if (directName === "DeviceGray" || directName === "CalGray" || directName === "G") return { kind: "gray" };
16
+ if (directName === "DeviceRGB" || directName === "CalRGB" || directName === "RGB") return { kind: "rgb" };
17
+ if (directName === "DeviceCMYK" || directName === "CMYK") return { kind: "cmyk" };
18
+ return {
19
+ kind: "unsupported",
20
+ name: directName
21
+ };
22
+ }
23
+ const arr = resolved?.kind === "array" ? resolved.items : void 0;
24
+ if (arr === void 0) return {
25
+ kind: "unsupported",
26
+ name: "(missing or invalid /ColorSpace)"
27
+ };
28
+ const family = asName(arr[0]);
29
+ if (family === "ICCBased") {
30
+ const streamObj = resolver.resolve(arr[1]);
31
+ const n = streamObj?.kind === "stream" ? asNumber(dictGet(streamObj.dict, "N")) : void 0;
32
+ if (n === 1) return { kind: "gray" };
33
+ if (n === 4) return { kind: "cmyk" };
34
+ return { kind: "rgb" };
35
+ }
36
+ if (family === "Indexed") {
37
+ const base = resolveColorSpace(arr[1], resolver, sink);
38
+ const lookupObj = resolver.resolve(arr[3]);
39
+ let lookup;
40
+ if (lookupObj?.kind === "stream") lookup = decodeStream(lookupObj.raw, lookupObj.dict, sink).bytes;
41
+ else if (lookupObj?.kind === "string") lookup = lookupObj.bytes;
42
+ else lookup = /* @__PURE__ */ new Uint8Array(0);
43
+ return {
44
+ kind: "indexed",
45
+ base,
46
+ lookup
47
+ };
48
+ }
49
+ if (family === "CalGray") return { kind: "gray" };
50
+ if (family === "CalRGB") return { kind: "rgb" };
51
+ return {
52
+ kind: "unsupported",
53
+ name: family ?? "(unrecognised array colour space)"
54
+ };
55
+ }
56
+ function unpackSamples(data, width, height, componentsPerPixel, bitsPerComponent) {
57
+ if (bitsPerComponent === 8) return Array.from(data.subarray(0, width * height * componentsPerPixel));
58
+ const samplesPerRow = width * componentsPerPixel;
59
+ const bytesPerRow = Math.ceil(samplesPerRow * bitsPerComponent / 8);
60
+ const out = [];
61
+ for (let row = 0; row < height; row++) {
62
+ const rowStart = row * bytesPerRow;
63
+ let bitPos = 0;
64
+ for (let s = 0; s < samplesPerRow; s++) {
65
+ let value = 0;
66
+ for (let b = 0; b < bitsPerComponent; b++) {
67
+ const byteIndex = rowStart + Math.floor(bitPos / 8);
68
+ const bitIndex = 7 - bitPos % 8;
69
+ const bit = (data[byteIndex] ?? 0) >> bitIndex & 1;
70
+ value = value << 1 | bit;
71
+ bitPos++;
72
+ }
73
+ out.push(value);
74
+ }
75
+ }
76
+ return out;
77
+ }
78
+ function scaleToByte(value, maxValue, inverted) {
79
+ const v = inverted ? maxValue - value : value;
80
+ return maxValue === 255 ? v : Math.round(v * 255 / maxValue);
81
+ }
82
+ function cmykToRgbByte(c, m, y, k) {
83
+ return {
84
+ r: Math.round(255 * (1 - c) * (1 - k)),
85
+ g: Math.round(255 * (1 - m) * (1 - k)),
86
+ b: Math.round(255 * (1 - y) * (1 - k))
87
+ };
88
+ }
89
+ function sampleFromPalette(base, lookup, offset) {
90
+ if (base.kind === "gray") {
91
+ const g = lookup[offset] ?? 0;
92
+ return {
93
+ r: g,
94
+ g,
95
+ b: g
96
+ };
97
+ }
98
+ if (base.kind === "cmyk") return cmykToRgbByte((lookup[offset] ?? 0) / 255, (lookup[offset + 1] ?? 0) / 255, (lookup[offset + 2] ?? 0) / 255, (lookup[offset + 3] ?? 0) / 255);
99
+ return {
100
+ r: lookup[offset] ?? 0,
101
+ g: lookup[offset + 1] ?? 0,
102
+ b: lookup[offset + 2] ?? 0
103
+ };
104
+ }
105
+ function buildRawImage(data, width, height, bitsPerComponent, colorSpace, inverted) {
106
+ const maxValue = (1 << bitsPerComponent) - 1;
107
+ if (colorSpace.kind === "indexed") {
108
+ const indices = unpackSamples(data, width, height, 1, bitsPerComponent);
109
+ const baseComponents = componentsOf(colorSpace.base);
110
+ const out = new Uint8Array(width * height * (colorSpace.base.kind === "gray" ? 1 : 3));
111
+ let outIdx = 0;
112
+ for (const index of indices) {
113
+ const rgb = sampleFromPalette(colorSpace.base, colorSpace.lookup, index * baseComponents);
114
+ if (colorSpace.base.kind === "gray") out[outIdx++] = rgb.r;
115
+ else {
116
+ out[outIdx++] = rgb.r;
117
+ out[outIdx++] = rgb.g;
118
+ out[outIdx++] = rgb.b;
119
+ }
120
+ }
121
+ return {
122
+ width,
123
+ height,
124
+ channels: colorSpace.base.kind === "gray" ? 1 : 3,
125
+ data: out
126
+ };
127
+ }
128
+ if (colorSpace.kind === "gray") {
129
+ const samples = unpackSamples(data, width, height, 1, bitsPerComponent);
130
+ return {
131
+ width,
132
+ height,
133
+ channels: 1,
134
+ data: Uint8Array.from(samples, (v) => scaleToByte(v, maxValue, inverted))
135
+ };
136
+ }
137
+ if (colorSpace.kind === "rgb") {
138
+ const samples = unpackSamples(data, width, height, 3, bitsPerComponent);
139
+ return {
140
+ width,
141
+ height,
142
+ channels: 3,
143
+ data: Uint8Array.from(samples, (v) => scaleToByte(v, maxValue, inverted))
144
+ };
145
+ }
146
+ const samples = unpackSamples(data, width, height, 4, bitsPerComponent);
147
+ const out = new Uint8Array(width * height * 3);
148
+ for (let px = 0; px < width * height; px++) {
149
+ const rgb = cmykToRgbByte(scaleToByte(samples[px * 4] ?? 0, maxValue, inverted) / 255, scaleToByte(samples[px * 4 + 1] ?? 0, maxValue, inverted) / 255, scaleToByte(samples[px * 4 + 2] ?? 0, maxValue, inverted) / 255, scaleToByte(samples[px * 4 + 3] ?? 0, maxValue, inverted) / 255);
150
+ out[px * 3] = rgb.r;
151
+ out[px * 3 + 1] = rgb.g;
152
+ out[px * 3 + 2] = rgb.b;
153
+ }
154
+ return {
155
+ width,
156
+ height,
157
+ channels: 3,
158
+ data: out
159
+ };
160
+ }
161
+ function readSoftMaskAlpha(dict, width, height, resolver, sink) {
162
+ const smaskObj = resolver.resolve(dictGet(dict, "SMask"));
163
+ if (smaskObj?.kind !== "stream") return;
164
+ const decoded = decodeStream(smaskObj.raw, smaskObj.dict, sink);
165
+ if (decoded.remainingFilter !== void 0) return;
166
+ const smaskWidth = asNumber(dictGet(smaskObj.dict, "Width")) ?? width;
167
+ const smaskHeight = asNumber(dictGet(smaskObj.dict, "Height")) ?? height;
168
+ const smaskBpc = asNumber(dictGet(smaskObj.dict, "BitsPerComponent")) ?? 8;
169
+ if (smaskWidth !== width || smaskHeight !== height || smaskBpc !== 8) return;
170
+ return decoded.bytes.subarray(0, width * height);
171
+ }
172
+ function isTrue(obj) {
173
+ return obj?.kind === "bool" && obj.value;
174
+ }
175
+ function readImageXObject(dict, raw, resolver, sink) {
176
+ if (isTrue(dictGet(dict, "ImageMask") ?? dictGet(dict, "IM"))) {
177
+ sink({
178
+ code: "image/mask-unsupported",
179
+ severity: "info",
180
+ message: "an /ImageMask stencil paints with the current fill colour rather than standing alone as an image; skipping"
181
+ });
182
+ return;
183
+ }
184
+ const decoded = decodeStream(raw, dict, sink);
185
+ if (decoded.remainingFilter === "DCTDecode") {
186
+ let info;
187
+ try {
188
+ info = readJpegInfo(decoded.bytes);
189
+ } catch {
190
+ sink({
191
+ code: "image/undecodable",
192
+ severity: "warning",
193
+ message: "DCTDecode image bytes did not look like a valid JPEG (no SOF marker found); skipping this image"
194
+ });
195
+ return;
196
+ }
197
+ return {
198
+ format: "jpeg",
199
+ bytes: decoded.bytes,
200
+ widthPx: info.width,
201
+ heightPx: info.height
202
+ };
203
+ }
204
+ if (decoded.remainingFilter !== void 0) return;
205
+ const width = asNumber(dictGet(dict, "Width") ?? dictGet(dict, "W"));
206
+ const height = asNumber(dictGet(dict, "Height") ?? dictGet(dict, "H"));
207
+ if (width === void 0 || height === void 0 || width <= 0 || height <= 0) {
208
+ sink({
209
+ code: "image/undecodable",
210
+ severity: "warning",
211
+ message: "image XObject is missing a valid /Width or /Height; skipping"
212
+ });
213
+ return;
214
+ }
215
+ const bitsPerComponent = asNumber(dictGet(dict, "BitsPerComponent") ?? dictGet(dict, "BPC")) ?? 8;
216
+ if (bitsPerComponent !== 1 && bitsPerComponent !== 2 && bitsPerComponent !== 4 && bitsPerComponent !== 8) {
217
+ sink({
218
+ code: "image/unsupported-bit-depth",
219
+ severity: "warning",
220
+ message: `image has an unsupported /BitsPerComponent (${String(bitsPerComponent)}); skipping`
221
+ });
222
+ return;
223
+ }
224
+ const colorSpace = resolveColorSpace(dictGet(dict, "ColorSpace") ?? dictGet(dict, "CS"), resolver, sink);
225
+ if (colorSpace.kind === "unsupported") {
226
+ sink({
227
+ code: "image/unsupported-colorspace",
228
+ severity: "warning",
229
+ message: `image has an unsupported colour space (${colorSpace.name}); skipping`
230
+ });
231
+ return;
232
+ }
233
+ const decodeArr = asArray(dictGet(dict, "Decode"));
234
+ const inverted = decodeArr !== void 0 && asNumber(decodeArr[0]) === 1 && asNumber(decodeArr[1]) === 0;
235
+ const rawImage = buildRawImage(decoded.bytes, width, height, bitsPerComponent, colorSpace, inverted);
236
+ const alpha = readSoftMaskAlpha(dict, width, height, resolver, sink);
237
+ const withAlpha = alpha !== void 0 ? {
238
+ ...rawImage,
239
+ alpha
240
+ } : rawImage;
241
+ return {
242
+ format: "png",
243
+ bytes: encodePng(withAlpha),
244
+ widthPx: width,
245
+ heightPx: height
246
+ };
247
+ }
248
+ //#endregion
249
+ export { readImageXObject };