reamkit 1.26.0 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/dist/esm/core/converter/ream.d.ts +0 -9
- package/dist/esm/core/converter/ream.js +0 -1
- package/dist/esm/core/document-model/types.d.ts +5 -3
- package/dist/esm/core/font/index.d.ts +1 -0
- package/dist/esm/core/font/ligatures.d.ts +17 -0
- package/dist/esm/core/font/ligatures.js +49 -0
- package/dist/esm/core/font/ttf-parser.d.ts +2 -1
- package/dist/esm/core/font/ttf-parser.js +11 -2
- package/dist/esm/core/fonts/remote-fonts.d.ts +1 -1
- package/dist/esm/core/fonts/remote-fonts.js +47 -2
- package/dist/esm/core/fonts/scripts.js +10 -5
- package/dist/esm/excel/header-footer.js +53 -8
- package/dist/esm/layout/styled-layout.js +53 -3
- package/dist/esm/pdf/cid-font.js +35 -8
- package/dist/esm/pdf-reader/annot-draw.d.ts +65 -0
- package/dist/esm/pdf-reader/annot-draw.js +487 -0
- package/dist/esm/pdf-reader/annots.d.ts +0 -12
- package/dist/esm/pdf-reader/annots.js +30 -4
- package/dist/esm/pdf-reader/ccitt.d.ts +20 -3
- package/dist/esm/pdf-reader/ccitt.js +102 -6
- package/dist/esm/pdf-reader/cff-outline.d.ts +36 -0
- package/dist/esm/pdf-reader/cff-outline.js +1122 -0
- package/dist/esm/pdf-reader/cie-color.d.ts +33 -0
- package/dist/esm/pdf-reader/cie-color.js +112 -0
- package/dist/esm/pdf-reader/cmap.js +5 -2
- package/dist/esm/pdf-reader/content.d.ts +123 -3
- package/dist/esm/pdf-reader/content.js +232 -54
- package/dist/esm/pdf-reader/dingbats.d.ts +11 -0
- package/dist/esm/pdf-reader/dingbats.js +1033 -0
- package/dist/esm/pdf-reader/display.d.ts +1 -1
- package/dist/esm/pdf-reader/display.js +61 -6
- package/dist/esm/pdf-reader/document.d.ts +6 -0
- package/dist/esm/pdf-reader/document.js +55 -10
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +23 -3
- package/dist/esm/pdf-reader/embedded-fonts.js +36 -4
- package/dist/esm/pdf-reader/encodings.d.ts +25 -0
- package/dist/esm/pdf-reader/encodings.js +110 -0
- package/dist/esm/pdf-reader/flow-build.d.ts +19 -5
- package/dist/esm/pdf-reader/flow-build.js +111 -11
- package/dist/esm/pdf-reader/font.js +403 -21
- package/dist/esm/pdf-reader/function.d.ts +16 -0
- package/dist/esm/pdf-reader/function.js +414 -0
- package/dist/esm/pdf-reader/glyf-outline.d.ts +43 -0
- package/dist/esm/pdf-reader/glyf-outline.js +351 -0
- package/dist/esm/pdf-reader/glyph-names.js +20 -0
- package/dist/esm/pdf-reader/icc.d.ts +10 -0
- package/dist/esm/pdf-reader/icc.js +210 -0
- package/dist/esm/pdf-reader/image-decode.d.ts +9 -4
- package/dist/esm/pdf-reader/image-decode.js +274 -72
- package/dist/esm/pdf-reader/images.d.ts +18 -0
- package/dist/esm/pdf-reader/images.js +154 -11
- package/dist/esm/pdf-reader/jbig2.d.ts +23 -0
- package/dist/esm/pdf-reader/jbig2.js +126 -32
- package/dist/esm/pdf-reader/layout.d.ts +32 -0
- package/dist/esm/pdf-reader/layout.js +1316 -64
- package/dist/esm/pdf-reader/lexer.d.ts +2 -0
- package/dist/esm/pdf-reader/lexer.js +4 -0
- package/dist/esm/pdf-reader/math-rows.d.ts +23 -0
- package/dist/esm/pdf-reader/math-rows.js +198 -0
- package/dist/esm/pdf-reader/optional-content.d.ts +36 -0
- package/dist/esm/pdf-reader/optional-content.js +93 -0
- package/dist/esm/pdf-reader/predefined-cmap.d.ts +21 -0
- package/dist/esm/pdf-reader/predefined-cmap.js +102 -0
- package/dist/esm/pdf-reader/reader.d.ts +6 -6
- package/dist/esm/pdf-reader/reader.js +102 -16
- package/dist/esm/pdf-reader/shading.d.ts +139 -8
- package/dist/esm/pdf-reader/shading.js +309 -39
- package/dist/esm/pdf-reader/standard-metrics.d.ts +8 -0
- package/dist/esm/pdf-reader/standard-metrics.js +18 -0
- package/dist/esm/pdf-reader/standard-widths.d.ts +20 -0
- package/dist/esm/pdf-reader/standard-widths.js +62 -0
- package/dist/esm/pdf-reader/stream-filters.d.ts +6 -0
- package/dist/esm/pdf-reader/stream-filters.js +67 -0
- package/dist/esm/pdf-reader/tagged.js +204 -32
- package/dist/esm/pdf-reader/text-rules.d.ts +16 -0
- package/dist/esm/pdf-reader/text-rules.js +185 -0
- package/dist/esm/pdf-reader/text.js +157 -4
- package/dist/esm/pdf-reader/type1-outline.d.ts +21 -0
- package/dist/esm/pdf-reader/type1-outline.js +576 -0
- package/dist/esm/pdf-reader/vector.d.ts +5 -0
- package/dist/esm/pdf-reader/vector.js +79 -27
- package/dist/esm/word/document-parser.js +4 -1
- package/dist/esm/word/docx-writer.js +74 -11
- package/package.json +1 -1
|
@@ -2,6 +2,9 @@ import { PDF_NULL, PdfHexString, PdfName, PdfStream } from "../pdf/objects.js";
|
|
|
2
2
|
import { encodePng } from "../core/png-encode.js";
|
|
3
3
|
import { lzwDecodeMsb } from "../core/lzw.js";
|
|
4
4
|
import { reversePredictor } from "./predictor.js";
|
|
5
|
+
import { ascii85Decode, asciiHexDecode, runLengthDecode } from "./stream-filters.js";
|
|
6
|
+
import { labToSrgb } from "./cie-color.js";
|
|
7
|
+
import { readFunction } from "./function.js";
|
|
5
8
|
import { decodeCcitt } from "./ccitt.js";
|
|
6
9
|
import { decodeJbig2 } from "./jbig2.js";
|
|
7
10
|
import { decodeJpeg } from "./jpeg.js";
|
|
@@ -16,19 +19,24 @@ var MAX_PIXELS = 4e7;
|
|
|
16
19
|
* Supports DeviceGray/RGB/CMYK, CalGray/CalRGB, ICCBased (by `/N`) and Indexed
|
|
17
20
|
* colour spaces; Flate, LZW (EP12), RunLength, ASCII85, ASCIIHex and CCITT
|
|
18
21
|
* Group 4 / Group 3 1-D fax (EP15) filters; PNG/TIFF predictors; bit depths
|
|
19
|
-
* 1/2/4/8/16;
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
+
* 1/2/4/8/16; an `/SMask` folded in as the PNG alpha channel; and a stencil
|
|
23
|
+
* `/ImageMask` (§8.9.6.2) given back as RGBA in `fillHex`; `Lab` (§8.6.5.8) and
|
|
24
|
+
* a `Separation`/`DeviceN` through its own tint transform (§8.6.6.4).
|
|
25
|
+
* Unsupported inputs (CCITT Group 3 2-D) return a typed failure so the caller
|
|
26
|
+
* records a loss.
|
|
22
27
|
*
|
|
28
|
+
* @param file The owning file.
|
|
29
|
+
* @param stream The image XObject, or an inline image wrapped as one.
|
|
30
|
+
* @param fillHex §8.9.6.2 — the non-stroking colour a stencil mask paints in.
|
|
23
31
|
* @returns The decoded image, or `{ ok: false }` with the loss severity and reason.
|
|
24
32
|
*/
|
|
25
|
-
function decodePdfImage(file, stream) {
|
|
33
|
+
function decodePdfImage(file, stream, fillHex) {
|
|
26
34
|
const d = stream.dict;
|
|
27
35
|
const width = intOf(file.get(d, "Width")) || intOf(file.get(d, "W"));
|
|
28
36
|
const height = intOf(file.get(d, "Height")) || intOf(file.get(d, "H"));
|
|
29
37
|
if (width <= 0 || height <= 0) return fail("dropped", "image with no dimensions");
|
|
30
38
|
if (width * height > MAX_PIXELS) return fail("dropped", "image too large to decode");
|
|
31
|
-
if (boolOf(d.get("ImageMask")) || boolOf(d.get("IM"))) return
|
|
39
|
+
if (boolOf(d.get("ImageMask")) || boolOf(d.get("IM"))) return stencil(file, stream, width, height, fillHex);
|
|
32
40
|
const filters = filterNames(file, d);
|
|
33
41
|
const last = filters[filters.length - 1];
|
|
34
42
|
if (last === "DCTDecode" || last === "DCT") {
|
|
@@ -116,13 +124,14 @@ function decodeCcittImage(file, stream, filters, width, height) {
|
|
|
116
124
|
byteAlign: parms ? boolOf(file.get(parms, "EncodedByteAlign")) : false
|
|
117
125
|
});
|
|
118
126
|
if (!packed) return "CCITT fax image not decoded (Group 3 2-D or malformed)";
|
|
127
|
+
const blackIs1 = parms ? boolOf(file.get(parms, "BlackIs1")) : false;
|
|
119
128
|
const rowBytes = columns + 7 >> 3;
|
|
120
129
|
const samples = new Uint8Array(width * height);
|
|
121
130
|
for (let y = 0; y < height; y++) {
|
|
122
131
|
const rowOff = y * rowBytes;
|
|
123
132
|
for (let x = 0; x < width; x++) {
|
|
124
|
-
const
|
|
125
|
-
samples[y * width + x] =
|
|
133
|
+
const bit = x < columns ? packed[rowOff + (x >> 3)] >> 7 - (x & 7) & 1 : 0;
|
|
134
|
+
samples[y * width + x] = bit === (blackIs1 ? 0 : 1) ? 0 : 255;
|
|
126
135
|
}
|
|
127
136
|
}
|
|
128
137
|
return {
|
|
@@ -130,6 +139,89 @@ function decodeCcittImage(file, stream, filters, width, height) {
|
|
|
130
139
|
samples
|
|
131
140
|
};
|
|
132
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* §8.9.6.2 — a stencil mask: one bit per pixel saying WHERE to paint, and the
|
|
144
|
+
* colour is whatever the page's non-stroking colour was at the `Do`.
|
|
145
|
+
*
|
|
146
|
+
* A sample of 0 paints and 1 leaves the page alone, which `/Decode [1 0]`
|
|
147
|
+
* swaps. Given back as an RGBA picture — the fill colour throughout, opaque
|
|
148
|
+
* where the stencil paints and clear where it does not — which is what the mask
|
|
149
|
+
* MEANS and what every format downstream can show.
|
|
150
|
+
* images_1bit_grayscale.pdf draws two of them and they were dropped entirely.
|
|
151
|
+
*/
|
|
152
|
+
function stencil(file, stream, width, height, fillHex) {
|
|
153
|
+
const d = stream.dict;
|
|
154
|
+
const filters = filterNames(file, d);
|
|
155
|
+
const last = filters[filters.length - 1];
|
|
156
|
+
const packed = last === "CCITTFaxDecode" || last === "CCF" ? ccittBits(file, stream, filters, height) : last === "JBIG2Decode" ? jbig2Bits(file, stream, filters, width, height) : decodeChain(file, stream, filters);
|
|
157
|
+
if (!packed) return fail("dropped", "stencil image mask not decoded");
|
|
158
|
+
const decode = file.resolve(d.get("Decode") ?? d.get("D") ?? PDF_NULL);
|
|
159
|
+
const paintBit = Array.isArray(decode) && file.resolve(decode[0] ?? PDF_NULL) === 1 ? 1 : 0;
|
|
160
|
+
const rgb = fillHex !== void 0 ? parseHex(fillHex) : [
|
|
161
|
+
0,
|
|
162
|
+
0,
|
|
163
|
+
0
|
|
164
|
+
];
|
|
165
|
+
const rowBytes = width + 7 >> 3;
|
|
166
|
+
if (packed.length < rowBytes * height) return fail("dropped", "stencil image mask truncated");
|
|
167
|
+
const samples = new Uint8Array(width * height * 4);
|
|
168
|
+
for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {
|
|
169
|
+
const bit = packed[y * rowBytes + (x >> 3)] >> 7 - (x & 7) & 1;
|
|
170
|
+
const at = (y * width + x) * 4;
|
|
171
|
+
samples[at] = rgb[0];
|
|
172
|
+
samples[at + 1] = rgb[1];
|
|
173
|
+
samples[at + 2] = rgb[2];
|
|
174
|
+
samples[at + 3] = bit === paintBit ? 255 : 0;
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
ok: true,
|
|
178
|
+
bytes: encodePng(width, height, "rgba", samples),
|
|
179
|
+
format: "png",
|
|
180
|
+
widthPx: width,
|
|
181
|
+
heightPx: height
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/** A 6-hex colour as its three bytes. */
|
|
185
|
+
function parseHex(hex) {
|
|
186
|
+
const v = Number.parseInt(hex, 16);
|
|
187
|
+
if (!Number.isFinite(v)) return [
|
|
188
|
+
0,
|
|
189
|
+
0,
|
|
190
|
+
0
|
|
191
|
+
];
|
|
192
|
+
return [
|
|
193
|
+
v >> 16 & 255,
|
|
194
|
+
v >> 8 & 255,
|
|
195
|
+
v & 255
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
/** A stencil's bits where the file coded them as fax (§7.4.6). */
|
|
199
|
+
function ccittBits(file, stream, filters, height) {
|
|
200
|
+
const parms = decodeParmsOf(file, stream.dict);
|
|
201
|
+
const columns = (parms ? intOf(file.get(parms, "Columns")) : 0) || 1728;
|
|
202
|
+
const packed = decodeCcitt(applyChainExceptLast(filters, stream.data), {
|
|
203
|
+
k: parms ? intOf(file.get(parms, "K")) : 0,
|
|
204
|
+
columns,
|
|
205
|
+
rows: height,
|
|
206
|
+
byteAlign: parms ? boolOf(file.get(parms, "EncodedByteAlign")) : false
|
|
207
|
+
});
|
|
208
|
+
if (!packed) return void 0;
|
|
209
|
+
if (parms ? boolOf(file.get(parms, "BlackIs1")) : false) return packed;
|
|
210
|
+
const flipped = new Uint8Array(packed.length);
|
|
211
|
+
for (let i = 0; i < packed.length; i++) flipped[i] = ~packed[i] & 255;
|
|
212
|
+
return flipped;
|
|
213
|
+
}
|
|
214
|
+
/** …or as JBIG2, which codes 1 as black and so paints on 1. */
|
|
215
|
+
function jbig2Bits(file, stream, filters, width, height) {
|
|
216
|
+
const parms = decodeParmsOf(file, stream.dict);
|
|
217
|
+
const globalsVal = parms ? file.get(parms, "JBIG2Globals") : void 0;
|
|
218
|
+
const globals = globalsVal instanceof PdfStream ? file.streamData(globalsVal) : void 0;
|
|
219
|
+
const packed = decodeJbig2(applyChainExceptLast(filters, stream.data), globals, width, height);
|
|
220
|
+
if (!packed) return void 0;
|
|
221
|
+
const flipped = new Uint8Array(packed.length);
|
|
222
|
+
for (let i = 0; i < packed.length; i++) flipped[i] = ~packed[i] & 255;
|
|
223
|
+
return flipped;
|
|
224
|
+
}
|
|
133
225
|
function resolveColorSpace(file, csVal) {
|
|
134
226
|
const cs = file.resolve(csVal ?? PDF_NULL);
|
|
135
227
|
if (cs instanceof PdfName) return namedColorSpace(cs.value);
|
|
@@ -178,6 +270,54 @@ function resolveColorSpace(file, csVal) {
|
|
|
178
270
|
lookup
|
|
179
271
|
};
|
|
180
272
|
}
|
|
273
|
+
if (tag === "Lab") {
|
|
274
|
+
const params = file.resolve(cs[1] ?? PDF_NULL);
|
|
275
|
+
const dict = params instanceof Map ? params : void 0;
|
|
276
|
+
const nums = (v) => {
|
|
277
|
+
const r = v !== void 0 ? file.resolve(v) : void 0;
|
|
278
|
+
return Array.isArray(r) ? r.map((x) => typeof file.resolve(x) === "number" ? file.resolve(x) : 0) : [];
|
|
279
|
+
};
|
|
280
|
+
const white = dict ? nums(file.get(dict, "WhitePoint")) : [];
|
|
281
|
+
if (white.length < 3 || !(white[1] > 0)) return void 0;
|
|
282
|
+
const range = dict ? nums(file.get(dict, "Range")) : [];
|
|
283
|
+
return {
|
|
284
|
+
kind: "lab",
|
|
285
|
+
components: 3,
|
|
286
|
+
lab: {
|
|
287
|
+
white: [
|
|
288
|
+
white[0],
|
|
289
|
+
white[1],
|
|
290
|
+
white[2]
|
|
291
|
+
],
|
|
292
|
+
range: range.length === 4 ? [
|
|
293
|
+
range[0],
|
|
294
|
+
range[1],
|
|
295
|
+
range[2],
|
|
296
|
+
range[3]
|
|
297
|
+
] : [
|
|
298
|
+
-100,
|
|
299
|
+
100,
|
|
300
|
+
-100,
|
|
301
|
+
100
|
|
302
|
+
]
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (tag === "Separation" || tag === "DeviceN" || tag === "I") {
|
|
307
|
+
const names = file.resolve(cs[1] ?? PDF_NULL);
|
|
308
|
+
const components = tag === "Separation" ? 1 : Array.isArray(names) ? names.length : 0;
|
|
309
|
+
const alternate = resolveColorSpace(file, cs[2]);
|
|
310
|
+
const transform = readFunction(file, cs[3]);
|
|
311
|
+
if (components < 1 || !alternate || alternate.kind === "tint" || !transform) return void 0;
|
|
312
|
+
return {
|
|
313
|
+
kind: "tint",
|
|
314
|
+
components,
|
|
315
|
+
tint: {
|
|
316
|
+
transform,
|
|
317
|
+
alternate
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
181
321
|
}
|
|
182
322
|
function namedColorSpace(name) {
|
|
183
323
|
if (name === "DeviceGray" || name === "G" || name === "CalGray") return {
|
|
@@ -241,13 +381,71 @@ function toColor(cs, s, px, bpc, decode) {
|
|
|
241
381
|
samples: out
|
|
242
382
|
};
|
|
243
383
|
}
|
|
384
|
+
if (cs.kind === "tint" && cs.tint) {
|
|
385
|
+
const { transform, alternate } = cs.tint;
|
|
386
|
+
const n = cs.components;
|
|
387
|
+
const cache = /* @__PURE__ */ new Map();
|
|
388
|
+
const out = new Uint8Array(px * 3);
|
|
389
|
+
for (let i = 0; i < px; i++) {
|
|
390
|
+
const comps = [];
|
|
391
|
+
for (let k = 0; k < n; k++) comps.push(c01(s[i * n + k], k));
|
|
392
|
+
const key = comps.join(",");
|
|
393
|
+
let rgb = cache.get(key);
|
|
394
|
+
if (!rgb) {
|
|
395
|
+
rgb = alternateRgb(alternate, transform(comps));
|
|
396
|
+
if (cache.size < MAX_TINT_CACHE) cache.set(key, rgb);
|
|
397
|
+
}
|
|
398
|
+
out[i * 3] = rgb[0];
|
|
399
|
+
out[i * 3 + 1] = rgb[1];
|
|
400
|
+
out[i * 3 + 2] = rgb[2];
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
color: "rgb",
|
|
404
|
+
samples: out
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (cs.kind === "lab") {
|
|
408
|
+
const lab = cs.lab;
|
|
409
|
+
const out = new Uint8Array(px * 3);
|
|
410
|
+
const span = (i) => decode && decode.length >= 6 ? [0, 1] : i === 0 ? [0, 100] : [lab.range[(i - 1) * 2], lab.range[(i - 1) * 2 + 1]];
|
|
411
|
+
for (let i = 0; i < px; i++) {
|
|
412
|
+
const comp = [
|
|
413
|
+
0,
|
|
414
|
+
1,
|
|
415
|
+
2
|
|
416
|
+
].map((k) => {
|
|
417
|
+
const t = c01(s[i * 3 + k], k);
|
|
418
|
+
const [lo, hi] = span(k);
|
|
419
|
+
return lo + t * (hi - lo);
|
|
420
|
+
});
|
|
421
|
+
const [r, g, b] = labToSrgb(lab.white, [
|
|
422
|
+
comp[0],
|
|
423
|
+
comp[1],
|
|
424
|
+
comp[2]
|
|
425
|
+
]);
|
|
426
|
+
out[i * 3] = Math.round(r * 255);
|
|
427
|
+
out[i * 3 + 1] = Math.round(g * 255);
|
|
428
|
+
out[i * 3 + 2] = Math.round(b * 255);
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
color: "rgb",
|
|
432
|
+
samples: out
|
|
433
|
+
};
|
|
434
|
+
}
|
|
244
435
|
const base = cs.base;
|
|
245
436
|
const lookup = cs.lookup;
|
|
246
437
|
const hival = cs.hival ?? 255;
|
|
247
438
|
const bn = base.components;
|
|
439
|
+
const index = (v) => {
|
|
440
|
+
if (!decode || decode.length < 2) return Math.min(v, hival);
|
|
441
|
+
const lo = decode[0];
|
|
442
|
+
const hi = decode[1];
|
|
443
|
+
const at = maxv > 0 ? lo + v / maxv * (hi - lo) : lo;
|
|
444
|
+
return Math.min(Math.max(0, Math.round(at)), hival);
|
|
445
|
+
};
|
|
248
446
|
if (base.kind === "gray") {
|
|
249
447
|
const out = new Uint8Array(px);
|
|
250
|
-
for (let i = 0; i < px; i++) out[i] = lookup[
|
|
448
|
+
for (let i = 0; i < px; i++) out[i] = lookup[index(s[i]) * bn] ?? 0;
|
|
251
449
|
return {
|
|
252
450
|
color: "gray",
|
|
253
451
|
samples: out
|
|
@@ -255,7 +453,7 @@ function toColor(cs, s, px, bpc, decode) {
|
|
|
255
453
|
}
|
|
256
454
|
const out = new Uint8Array(px * 3);
|
|
257
455
|
for (let i = 0; i < px; i++) {
|
|
258
|
-
const [r, g, b] = paletteRgb(base, lookup,
|
|
456
|
+
const [r, g, b] = paletteRgb(base, lookup, index(s[i]) * bn);
|
|
259
457
|
out[i * 3] = r;
|
|
260
458
|
out[i * 3 + 1] = g;
|
|
261
459
|
out[i * 3 + 2] = b;
|
|
@@ -265,12 +463,67 @@ function toColor(cs, s, px, bpc, decode) {
|
|
|
265
463
|
samples: out
|
|
266
464
|
};
|
|
267
465
|
}
|
|
466
|
+
/** How many distinct tint tuples are remembered before the cache stops growing. */
|
|
467
|
+
var MAX_TINT_CACHE = 65536;
|
|
468
|
+
/** §8.6.6.4 — what the alternate space makes of the transform's output. */
|
|
469
|
+
function alternateRgb(alternate, out) {
|
|
470
|
+
const at = (i) => Math.min(1, Math.max(0, out[i] ?? 0));
|
|
471
|
+
const to255 = (v) => Math.round(v * 255);
|
|
472
|
+
if (alternate.kind === "cmyk") {
|
|
473
|
+
const k = at(3);
|
|
474
|
+
return [
|
|
475
|
+
to255((1 - at(0)) * (1 - k)),
|
|
476
|
+
to255((1 - at(1)) * (1 - k)),
|
|
477
|
+
to255((1 - at(2)) * (1 - k))
|
|
478
|
+
];
|
|
479
|
+
}
|
|
480
|
+
if (alternate.kind === "lab" && alternate.lab) {
|
|
481
|
+
const r = alternate.lab.range;
|
|
482
|
+
const [sr, sg, sb] = labToSrgb(alternate.lab.white, [
|
|
483
|
+
at(0) * 100,
|
|
484
|
+
r[0] + at(1) * (r[1] - r[0]),
|
|
485
|
+
r[2] + at(2) * (r[3] - r[2])
|
|
486
|
+
]);
|
|
487
|
+
return [
|
|
488
|
+
to255(sr),
|
|
489
|
+
to255(sg),
|
|
490
|
+
to255(sb)
|
|
491
|
+
];
|
|
492
|
+
}
|
|
493
|
+
if (alternate.kind === "gray") {
|
|
494
|
+
const g = to255(at(0));
|
|
495
|
+
return [
|
|
496
|
+
g,
|
|
497
|
+
g,
|
|
498
|
+
g
|
|
499
|
+
];
|
|
500
|
+
}
|
|
501
|
+
return [
|
|
502
|
+
to255(at(0)),
|
|
503
|
+
to255(at(1)),
|
|
504
|
+
to255(at(2))
|
|
505
|
+
];
|
|
506
|
+
}
|
|
268
507
|
function paletteRgb(base, lookup, off) {
|
|
269
508
|
if (base.kind === "rgb") return [
|
|
270
509
|
lookup[off] ?? 0,
|
|
271
510
|
lookup[off + 1] ?? 0,
|
|
272
511
|
lookup[off + 2] ?? 0
|
|
273
512
|
];
|
|
513
|
+
if (base.kind === "lab" && base.lab) {
|
|
514
|
+
const r = base.lab.range;
|
|
515
|
+
const at = (i, lo, hi) => lo + (lookup[off + i] ?? 0) / 255 * (hi - lo);
|
|
516
|
+
const [sr, sg, sb] = labToSrgb(base.lab.white, [
|
|
517
|
+
at(0, 0, 100),
|
|
518
|
+
at(1, r[0], r[1]),
|
|
519
|
+
at(2, r[2], r[3])
|
|
520
|
+
]);
|
|
521
|
+
return [
|
|
522
|
+
Math.round(sr * 255),
|
|
523
|
+
Math.round(sg * 255),
|
|
524
|
+
Math.round(sb * 255)
|
|
525
|
+
];
|
|
526
|
+
}
|
|
274
527
|
if (base.kind === "cmyk") {
|
|
275
528
|
const c = (lookup[off] ?? 0) / 255;
|
|
276
529
|
const m = (lookup[off + 1] ?? 0) / 255;
|
|
@@ -406,15 +659,25 @@ function combineAlpha(color, alpha) {
|
|
|
406
659
|
};
|
|
407
660
|
}
|
|
408
661
|
function filterNames(file, d) {
|
|
409
|
-
const f = file.resolve(d.get("Filter") ?? PDF_NULL);
|
|
662
|
+
const f = file.resolve(d.get("Filter") ?? d.get("F") ?? PDF_NULL);
|
|
410
663
|
const arr = Array.isArray(f) ? f : [f];
|
|
411
664
|
const out = [];
|
|
412
665
|
for (const x of arr) {
|
|
413
666
|
const r = file.resolve(x);
|
|
414
|
-
if (r instanceof PdfName) out.push(r.value);
|
|
667
|
+
if (r instanceof PdfName) out.push(FILTER_ABBREVIATIONS[r.value] ?? r.value);
|
|
415
668
|
}
|
|
416
669
|
return out;
|
|
417
670
|
}
|
|
671
|
+
/** §8.9.7 Table 93 — the short name an inline image writes a filter under. */
|
|
672
|
+
var FILTER_ABBREVIATIONS = {
|
|
673
|
+
AHx: "ASCIIHexDecode",
|
|
674
|
+
A85: "ASCII85Decode",
|
|
675
|
+
LZW: "LZWDecode",
|
|
676
|
+
Fl: "FlateDecode",
|
|
677
|
+
RL: "RunLengthDecode",
|
|
678
|
+
CCF: "CCITTFaxDecode",
|
|
679
|
+
DCT: "DCTDecode"
|
|
680
|
+
};
|
|
418
681
|
function decodeChain(file, stream, filters) {
|
|
419
682
|
let data = stream.data;
|
|
420
683
|
let mayPredict = false;
|
|
@@ -470,61 +733,6 @@ function lzwEarlyChange(file, d) {
|
|
|
470
733
|
const ec = decodeParmsOf(file, d)?.get("EarlyChange");
|
|
471
734
|
return ec !== void 0 && file.resolve(ec) === 0 ? 0 : 1;
|
|
472
735
|
}
|
|
473
|
-
function runLengthDecode(data) {
|
|
474
|
-
const out = [];
|
|
475
|
-
let i = 0;
|
|
476
|
-
while (i < data.length) {
|
|
477
|
-
const len = data[i++];
|
|
478
|
-
if (len === 128) break;
|
|
479
|
-
if (len < 128) for (let j = 0; j <= len && i < data.length; j++) out.push(data[i++]);
|
|
480
|
-
else {
|
|
481
|
-
const b = data[i++] ?? 0;
|
|
482
|
-
for (let j = 0; j < 257 - len; j++) out.push(b);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
return Uint8Array.from(out);
|
|
486
|
-
}
|
|
487
|
-
function ascii85Decode(data) {
|
|
488
|
-
const out = [];
|
|
489
|
-
let tuple = 0;
|
|
490
|
-
let count = 0;
|
|
491
|
-
for (const c of data) {
|
|
492
|
-
if (c === 126) break;
|
|
493
|
-
if (c <= 32) continue;
|
|
494
|
-
if (c === 122 && count === 0) {
|
|
495
|
-
out.push(0, 0, 0, 0);
|
|
496
|
-
continue;
|
|
497
|
-
}
|
|
498
|
-
if (c < 33 || c > 117) continue;
|
|
499
|
-
tuple = tuple * 85 + (c - 33);
|
|
500
|
-
if (++count === 5) {
|
|
501
|
-
out.push(tuple >>> 24 & 255, tuple >>> 16 & 255, tuple >>> 8 & 255, tuple & 255);
|
|
502
|
-
tuple = 0;
|
|
503
|
-
count = 0;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
if (count > 0) {
|
|
507
|
-
for (let i = count; i < 5; i++) tuple = tuple * 85 + 84;
|
|
508
|
-
for (let i = 0; i < count - 1; i++) out.push(tuple >>> 24 - i * 8 & 255);
|
|
509
|
-
}
|
|
510
|
-
return Uint8Array.from(out);
|
|
511
|
-
}
|
|
512
|
-
function asciiHexDecode(data) {
|
|
513
|
-
const out = [];
|
|
514
|
-
let hi = -1;
|
|
515
|
-
for (const c of data) {
|
|
516
|
-
if (c === 62) break;
|
|
517
|
-
const v = hexVal(c);
|
|
518
|
-
if (v < 0) continue;
|
|
519
|
-
if (hi < 0) hi = v;
|
|
520
|
-
else {
|
|
521
|
-
out.push(hi << 4 | v);
|
|
522
|
-
hi = -1;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
if (hi >= 0) out.push(hi << 4);
|
|
526
|
-
return Uint8Array.from(out);
|
|
527
|
-
}
|
|
528
736
|
function decodeParmsOf(file, d) {
|
|
529
737
|
const p = file.resolve(d.get("DecodeParms") ?? d.get("DP") ?? PDF_NULL);
|
|
530
738
|
if (p instanceof Map) return p;
|
|
@@ -561,12 +769,6 @@ function boolOf(v) {
|
|
|
561
769
|
function clamp01(x) {
|
|
562
770
|
return x < 0 ? 0 : x > 1 ? 1 : x;
|
|
563
771
|
}
|
|
564
|
-
function hexVal(c) {
|
|
565
|
-
if (c >= 48 && c <= 57) return c - 48;
|
|
566
|
-
if (c >= 65 && c <= 70) return c - 65 + 10;
|
|
567
|
-
if (c >= 97 && c <= 102) return c - 97 + 10;
|
|
568
|
-
return -1;
|
|
569
|
-
}
|
|
570
772
|
function fail(severity, detail) {
|
|
571
773
|
return {
|
|
572
774
|
ok: false,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ImageCrop } from '../core/document-model/types.js';
|
|
1
2
|
import { Loss } from '../core/ir/index.js';
|
|
2
3
|
import { PdfFile, PdfPage } from './document.js';
|
|
3
4
|
/**
|
|
@@ -15,8 +16,25 @@ export interface PdfImage {
|
|
|
15
16
|
/** Page-space lower-left corner (points, y-up). */
|
|
16
17
|
readonly x: number;
|
|
17
18
|
readonly y: number;
|
|
19
|
+
/**
|
|
20
|
+
* §8.9.5 — how far the CTM turns the picture, in degrees counter-clockwise.
|
|
21
|
+
* The box above is the picture's own, unturned; a turn spins it about its
|
|
22
|
+
* centre, which is what every downstream format does with one.
|
|
23
|
+
*/
|
|
24
|
+
readonly rotationDeg?: number;
|
|
25
|
+
/**
|
|
26
|
+
* §8.5.4 — the fraction of each of the picture's OWN edges a clip cut away,
|
|
27
|
+
* where one bounded it: `a:srcRect` in DrawingML terms. Absent is whole.
|
|
28
|
+
*/
|
|
29
|
+
readonly crop?: ImageCrop;
|
|
18
30
|
/** Enclosing marked-content id, if the placement was inside a `/Figure`. */
|
|
19
31
|
readonly mcid?: number;
|
|
32
|
+
/**
|
|
33
|
+
* §11.6.4.4 `/ca` — how opaque the picture is drawn, `0..1`. Absent is
|
|
34
|
+
* opaque. alphatrans.pdf lays a photograph in at half strength over three
|
|
35
|
+
* coloured squares, and drawn full-strength it hid all three.
|
|
36
|
+
*/
|
|
37
|
+
readonly alpha?: number;
|
|
20
38
|
/**
|
|
21
39
|
* §8.5.3 — where this was painted, as the chain of positions leading to it.
|
|
22
40
|
* The same key a lifted path carries, so the two can be ordered against each
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
2
2
|
import { FEATURES } from "../core/ir/features.js";
|
|
3
|
+
import { encodePng } from "../core/png-encode.js";
|
|
4
|
+
import { buildAlphaMap, sampledShading } from "./shading.js";
|
|
3
5
|
import { interpretContent, multiply } from "./content.js";
|
|
4
|
-
import { decodePdfImage } from "./image-decode.js";
|
|
5
6
|
import { collectPageAppearances } from "./annots.js";
|
|
7
|
+
import { hiddenProperties, hiddenXObject } from "./optional-content.js";
|
|
8
|
+
import { buildFonts } from "./text.js";
|
|
9
|
+
import { decodePdfImage } from "./image-decode.js";
|
|
6
10
|
//#region src/pdf-reader/images.ts
|
|
7
|
-
var NO_FONTS = /* @__PURE__ */ new Map();
|
|
8
11
|
var MAX_FORM_DEPTH = 12;
|
|
9
12
|
var MAX_IMAGES = 4096;
|
|
10
13
|
/**
|
|
@@ -20,6 +23,15 @@ function collectPageImages(file, page) {
|
|
|
20
23
|
const images = [];
|
|
21
24
|
const lossByDetail = /* @__PURE__ */ new Map();
|
|
22
25
|
const visiting = /* @__PURE__ */ new Set();
|
|
26
|
+
const alphaCache = /* @__PURE__ */ new Map();
|
|
27
|
+
const fontCache = /* @__PURE__ */ new Map();
|
|
28
|
+
const fontsOf = (resources) => {
|
|
29
|
+
const had = fontCache.get(resources);
|
|
30
|
+
if (had) return had;
|
|
31
|
+
const made = buildFonts(file, resources);
|
|
32
|
+
fontCache.set(resources, made);
|
|
33
|
+
return made;
|
|
34
|
+
};
|
|
23
35
|
const addLoss = (severity, detail) => {
|
|
24
36
|
if (!lossByDetail.has(detail)) lossByDetail.set(detail, {
|
|
25
37
|
severity,
|
|
@@ -30,7 +42,12 @@ function collectPageImages(file, page) {
|
|
|
30
42
|
const walk = (resources, content, baseCtm, depth, inheritedMcid, prefix) => {
|
|
31
43
|
const xobjects = resources ? file.get(resources, "XObject") : PDF_NULL;
|
|
32
44
|
const xobjDict = xobjects instanceof Map ? xobjects : void 0;
|
|
33
|
-
|
|
45
|
+
let paints = alphaCache.get(resources);
|
|
46
|
+
if (!paints) {
|
|
47
|
+
paints = buildAlphaMap(file, resources);
|
|
48
|
+
alphaCache.set(resources, paints);
|
|
49
|
+
}
|
|
50
|
+
const result = interpretContent(content, fontsOf(resources), baseCtm, void 0, paints, void 0, hiddenProperties(file, resources));
|
|
34
51
|
const patterns = resources ? file.get(resources, "Pattern") : PDF_NULL;
|
|
35
52
|
const patternDict = patterns instanceof Map ? patterns : void 0;
|
|
36
53
|
for (const vector of result.vectors) {
|
|
@@ -43,20 +60,67 @@ function collectPageImages(file, page) {
|
|
|
43
60
|
walk(patternRes instanceof Map ? patternRes : resources, file.streamData(stream), multiply(matrixOf(file, stream.dict), baseCtm), depth + 1, inheritedMcid, [...prefix, vector.order]);
|
|
44
61
|
visiting.delete(stream);
|
|
45
62
|
}
|
|
63
|
+
for (const call of result.glyphs) {
|
|
64
|
+
if (depth >= MAX_FORM_DEPTH || visiting.has(call.stream)) continue;
|
|
65
|
+
visiting.add(call.stream);
|
|
66
|
+
walk(call.resources ?? resources, file.streamData(call.stream), call.ctm, depth + 1, inheritedMcid, [...prefix, call.order]);
|
|
67
|
+
visiting.delete(call.stream);
|
|
68
|
+
}
|
|
69
|
+
for (const paint of result.shadings) {
|
|
70
|
+
if (images.length >= MAX_IMAGES) return;
|
|
71
|
+
const dict = resources ? file.get(resources, "Shading") : PDF_NULL;
|
|
72
|
+
const shading = dict instanceof Map ? file.resolve(dict.get(paint.name) ?? PDF_NULL) : PDF_NULL;
|
|
73
|
+
const sh = shading instanceof PdfStream ? shading.dict : shading instanceof Map ? shading : void 0;
|
|
74
|
+
if (!sh) continue;
|
|
75
|
+
if (paint.masked) continue;
|
|
76
|
+
const sampled = sampledShading(file, sh);
|
|
77
|
+
if (!sampled) continue;
|
|
78
|
+
const placed = multiply(matrixOf(file, sh), paint.ctm);
|
|
79
|
+
const [dx0, dx1, dy0, dy1] = sampled.domain;
|
|
80
|
+
const unit = [
|
|
81
|
+
dx1 - dx0,
|
|
82
|
+
0,
|
|
83
|
+
0,
|
|
84
|
+
dy1 - dy0,
|
|
85
|
+
dx0,
|
|
86
|
+
dy0
|
|
87
|
+
];
|
|
88
|
+
images.push({
|
|
89
|
+
...geometry(multiply(unit, placed), {
|
|
90
|
+
bytes: encodePng(sampled.size, sampled.size, "rgb", sampled.rgb),
|
|
91
|
+
format: "png"
|
|
92
|
+
}, inheritedMcid, paint.clip),
|
|
93
|
+
orderKey: [...prefix, paint.order]
|
|
94
|
+
});
|
|
95
|
+
}
|
|
46
96
|
for (const placement of result.images) {
|
|
47
97
|
if (images.length >= MAX_IMAGES) return;
|
|
98
|
+
if (placement.inline) {
|
|
99
|
+
const decoded = decodePdfImage(file, new PdfStream(placement.inline.dict, placement.inline.data), placement.fillHex);
|
|
100
|
+
if (decoded.ok) {
|
|
101
|
+
images.push({
|
|
102
|
+
...geometry(placement.ctm, decoded, placement.mcid ?? inheritedMcid, placement.clip, placement.alpha),
|
|
103
|
+
orderKey: [...prefix, placement.order]
|
|
104
|
+
});
|
|
105
|
+
if (decoded.degraded) addLoss("degraded", decoded.degraded);
|
|
106
|
+
} else addLoss(decoded.severity, decoded.detail);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
48
109
|
const stream = xobjDict ? file.resolve(xobjDict.get(placement.name) ?? PDF_NULL) : PDF_NULL;
|
|
49
110
|
if (!(stream instanceof PdfStream)) continue;
|
|
111
|
+
if (hiddenXObject(file, stream)) continue;
|
|
50
112
|
const subtype = nameOf(file.get(stream.dict, "Subtype"));
|
|
51
113
|
const mcid = placement.mcid ?? inheritedMcid;
|
|
52
114
|
if (subtype === "Image") {
|
|
53
|
-
const decoded = decodePdfImage(file, stream);
|
|
115
|
+
const decoded = decodePdfImage(file, stream, placement.fillHex);
|
|
54
116
|
if (decoded.ok) {
|
|
55
117
|
images.push({
|
|
56
|
-
...geometry(placement.ctm, decoded, mcid),
|
|
118
|
+
...geometry(placement.ctm, decoded, mcid, placement.clip, placement.alpha),
|
|
57
119
|
orderKey: [...prefix, placement.order]
|
|
58
120
|
});
|
|
59
121
|
if (decoded.degraded) addLoss("degraded", decoded.degraded);
|
|
122
|
+
if (placement.blend !== void 0) addLoss("degraded", `PDF blend mode /${placement.blend} is not performed; the picture is drawn over what it was to blend with`);
|
|
123
|
+
if (placement.masked === true) addLoss("degraded", "PDF soft mask (/SMask in the graphics state) is not applied; the picture is drawn at full opacity throughout");
|
|
60
124
|
} else addLoss(decoded.severity, decoded.detail);
|
|
61
125
|
} else if (subtype === "Form" && depth < MAX_FORM_DEPTH && !visiting.has(stream)) {
|
|
62
126
|
visiting.add(stream);
|
|
@@ -82,16 +146,95 @@ function collectPageImages(file, page) {
|
|
|
82
146
|
losses: [...lossByDetail.values()]
|
|
83
147
|
};
|
|
84
148
|
}
|
|
85
|
-
function geometry(ctm, decoded, mcid) {
|
|
149
|
+
function geometry(ctm, decoded, mcid, clip, alpha) {
|
|
150
|
+
const widthPt = Math.hypot(ctm[0], ctm[1]) || 1;
|
|
151
|
+
const heightPt = Math.hypot(ctm[2], ctm[3]) || 1;
|
|
152
|
+
const angle = Math.atan2(ctm[1], ctm[0]) * 180 / Math.PI;
|
|
153
|
+
const box = clippedUnitBox(ctm, clip);
|
|
154
|
+
const u = (box.u0 + box.u1) / 2;
|
|
155
|
+
const v = (box.v0 + box.v1) / 2;
|
|
156
|
+
const cx = ctm[0] * u + ctm[2] * v + ctm[4];
|
|
157
|
+
const cy = ctm[1] * u + ctm[3] * v + ctm[5];
|
|
158
|
+
const shownW = widthPt * (box.u1 - box.u0);
|
|
159
|
+
const shownH = heightPt * (box.v1 - box.v0);
|
|
160
|
+
const crop = box.u0 > 0 || box.v0 > 0 || box.u1 < 1 || box.v1 < 1 ? {
|
|
161
|
+
left: box.u0,
|
|
162
|
+
right: 1 - box.u1,
|
|
163
|
+
top: 1 - box.v1,
|
|
164
|
+
bottom: box.v0
|
|
165
|
+
} : void 0;
|
|
86
166
|
return {
|
|
87
167
|
bytes: decoded.bytes,
|
|
88
168
|
format: decoded.format,
|
|
89
|
-
widthPt:
|
|
90
|
-
heightPt:
|
|
91
|
-
x:
|
|
92
|
-
y:
|
|
93
|
-
...
|
|
169
|
+
widthPt: shownW,
|
|
170
|
+
heightPt: shownH,
|
|
171
|
+
x: cx - shownW / 2,
|
|
172
|
+
y: cy - shownH / 2,
|
|
173
|
+
...crop ? { crop } : {},
|
|
174
|
+
...Math.abs(angle) > .5 ? { rotationDeg: angle } : {},
|
|
175
|
+
...mcid !== void 0 ? { mcid } : {},
|
|
176
|
+
...alpha !== void 0 ? { alpha } : {}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** Below this the clip took a sliver off an edge and is not worth a crop. */
|
|
180
|
+
var CROPS = .01;
|
|
181
|
+
/**
|
|
182
|
+
* §8.5.4 — the part of the unit square a clip leaves showing, in the PICTURE's
|
|
183
|
+
* own axes.
|
|
184
|
+
*
|
|
185
|
+
* A clip is stated in the page's coordinates and a picture may be turned within
|
|
186
|
+
* them, so intersecting the two boxes as they stand crops along the wrong axes.
|
|
187
|
+
* Carried back through the placement matrix the clip lands in the unit square
|
|
188
|
+
* the image is drawn into, where a crop is what `a:srcRect` means: the fraction
|
|
189
|
+
* off each of the picture's own edges. image-rotated-black-white-ratio.pdf
|
|
190
|
+
* turns picture and clip together by thirty-one degrees, and in that space the
|
|
191
|
+
* clip is square on and takes the middle 53% of both sides.
|
|
192
|
+
*
|
|
193
|
+
* Where the clip is turned differently from the picture this bounds it rather
|
|
194
|
+
* than cutting it exactly — a rectangle is all `a:srcRect` can say.
|
|
195
|
+
*/
|
|
196
|
+
function clippedUnitBox(ctm, clip) {
|
|
197
|
+
const whole = {
|
|
198
|
+
u0: 0,
|
|
199
|
+
v0: 0,
|
|
200
|
+
u1: 1,
|
|
201
|
+
v1: 1
|
|
202
|
+
};
|
|
203
|
+
if (!clip) return whole;
|
|
204
|
+
const det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
|
|
205
|
+
if (!Number.isFinite(det) || Math.abs(det) < 1e-9) return whole;
|
|
206
|
+
let u0 = Infinity;
|
|
207
|
+
let v0 = Infinity;
|
|
208
|
+
let u1 = -Infinity;
|
|
209
|
+
let v1 = -Infinity;
|
|
210
|
+
const add = (x, y) => {
|
|
211
|
+
const dx = x - ctm[4];
|
|
212
|
+
const dy = y - ctm[5];
|
|
213
|
+
const u = (ctm[3] * dx - ctm[2] * dy) / det;
|
|
214
|
+
const v = (ctm[0] * dy - ctm[1] * dx) / det;
|
|
215
|
+
u0 = Math.min(u0, u);
|
|
216
|
+
v0 = Math.min(v0, v);
|
|
217
|
+
u1 = Math.max(u1, u);
|
|
218
|
+
v1 = Math.max(v1, v);
|
|
219
|
+
};
|
|
220
|
+
for (const seg of clip.segs) {
|
|
221
|
+
if (seg.op === "close") continue;
|
|
222
|
+
if (seg.op === "cubic") {
|
|
223
|
+
add(seg.x1, seg.y1);
|
|
224
|
+
add(seg.x2, seg.y2);
|
|
225
|
+
}
|
|
226
|
+
add(seg.x, seg.y);
|
|
227
|
+
}
|
|
228
|
+
if (!Number.isFinite(u0) || !Number.isFinite(v0)) return whole;
|
|
229
|
+
const cut = {
|
|
230
|
+
u0: Math.min(1, Math.max(0, u0)),
|
|
231
|
+
v0: Math.min(1, Math.max(0, v0)),
|
|
232
|
+
u1: Math.min(1, Math.max(0, u1)),
|
|
233
|
+
v1: Math.min(1, Math.max(0, v1))
|
|
94
234
|
};
|
|
235
|
+
if (!(cut.u1 - cut.u0 > CROPS) || !(cut.v1 - cut.v0 > CROPS)) return whole;
|
|
236
|
+
if (cut.u1 - cut.u0 > 1 - CROPS && cut.v1 - cut.v0 > 1 - CROPS) return whole;
|
|
237
|
+
return cut;
|
|
95
238
|
}
|
|
96
239
|
function matrixOf(file, dict) {
|
|
97
240
|
const m = file.resolve(dict.get("Matrix") ?? PDF_NULL);
|