gutterpress 0.11.17-alpha.2 → 0.11.17-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.js +11 -2
- package/dist/black-text-gray-7dz61456.js +15 -0
- package/dist/{build-1p999cmg.js → build-fam0tqg6.js} +5 -4
- package/dist/checks/pdf/black-text.d.ts +3 -0
- package/dist/checks/pdf/index.d.ts +2 -0
- package/dist/checks/pdf/signature.d.ts +3 -0
- package/dist/{cli-6z5zthzt.js → cli-2kc34kme.js} +470 -489
- package/dist/{cli-70xgehq2.js → cli-8apk8571.js} +1 -1
- package/dist/cli-ht1bcqdd.js +493 -0
- package/dist/{cli-gymfg04q.js → cli-jyy9pqf3.js} +15 -10
- package/dist/{cli-ppc0vz4b.js → cli-pqcb2nnb.js} +1 -1
- package/dist/cli.js +12 -12
- package/dist/{doctor-reqpnn3h.js → doctor-zre5k5fg.js} +2 -2
- package/dist/engine/compiler/build.d.ts +3 -2
- package/dist/engine/compiler/postprocess.d.ts +6 -4
- package/dist/engine/shared/synthesis.d.ts +29 -5
- package/dist/{engine-5jzhjjvv.js → engine-3xqgm82d.js} +2 -2
- package/dist/{engine-078ggczp.js → engine-zdb41ay1.js} +1 -1
- package/dist/{ext-dsbfejt3.js → ext-hfqcg6ax.js} +5 -4
- package/dist/{gutterpress-viewer-zkv2stfy.js → gutterpress-viewer-1n793gwd.js} +5 -3
- package/dist/{index-c275p061.js → index-gp503ze9.js} +1622 -1332
- package/dist/{index-5dcj1vts.js → index-hrx0gfkj.js} +23 -684
- package/dist/index-ht1bcqdd.js +493 -0
- package/dist/index-q1xqxrh9.js +949 -0
- package/dist/index.js +12 -3
- package/dist/lib/black-text-gray.d.ts +55 -8
- package/dist/lib/build-fingerprint.d.ts +2 -0
- package/dist/lib/engine.d.ts +2 -1
- package/dist/lib/lint-rules.d.ts +9 -0
- package/dist/lib/markdown/assemble.d.ts +3 -5
- package/dist/lib/markdown/index.d.ts +3 -8
- package/dist/lib/markdown/renderer.d.ts +9 -1
- package/dist/lib/page-size.d.ts +66 -0
- package/dist/lib/pdf-inspect.d.ts +6 -0
- package/dist/lib/printsafe.d.ts +8 -1
- package/dist/lib/project-scaffold.d.ts +3 -9
- package/dist/lib/remote-auth/github-repos.d.ts +7 -0
- package/dist/lib/remote-auth/online-backup.d.ts +51 -0
- package/dist/lib/snippets.d.ts +3 -0
- package/dist/{manifest.schema-40sfq49z.json → manifest.schema-gh0jjve9.json} +14 -2
- package/dist/{new-m5y2wepr.js → new-bjdf7bhg.js} +5 -4
- package/dist/{plugin.js-m4a7m3t4.tpl → plugin.js-cswq7wvd.tpl} +4 -1
- package/dist/{plugin.test.js-6n8ct5e3.tpl → plugin.test.js-xgxrd8hf.tpl} +4 -1
- package/dist/{preflight-bwpqwwny.js → preflight-gvw01661.js} +5 -4
- package/dist/preview/file-watcher.d.ts +2 -4
- package/dist/{preview-62y7qhkp.js → preview-1cschq8s.js} +5 -4
- package/dist/{publish-k2zyt3x6.js → publish-ddqrb8s9.js} +5 -4
- package/dist/render.js +30 -5
- package/dist/schema/manifest.types.d.ts +17 -0
- package/dist/{validate-fgwwxtbn.js → validate-3hjkgxrz.js} +5 -4
- package/package.json +1 -1
- package/dist/index-eyaj6nkc.js +0 -267
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./index-37x76zdn.js";
|
|
4
|
+
|
|
5
|
+
// src/lib/black-text-gray.ts
|
|
6
|
+
var MAX_CHANNEL = 0.12;
|
|
7
|
+
var MAX_SPREAD = 0.04;
|
|
8
|
+
function isNearBlack([r, g, b]) {
|
|
9
|
+
const max = Math.max(r, g, b);
|
|
10
|
+
return max <= MAX_CHANNEL && max - Math.min(r, g, b) <= MAX_SPREAD;
|
|
11
|
+
}
|
|
12
|
+
var WS = new Set([0, 9, 10, 12, 13, 32]);
|
|
13
|
+
var DELIM = new Set("()<>[]{}/%".split("").map((c) => c.charCodeAt(0)));
|
|
14
|
+
function* tokenize(s) {
|
|
15
|
+
let i = 0;
|
|
16
|
+
const n = s.length;
|
|
17
|
+
while (i < n) {
|
|
18
|
+
const c = s.charCodeAt(i);
|
|
19
|
+
if (WS.has(c)) {
|
|
20
|
+
i++;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const start = i;
|
|
24
|
+
if (c === 37) {
|
|
25
|
+
while (i < n && s[i] !== `
|
|
26
|
+
` && s[i] !== "\r")
|
|
27
|
+
i++;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (c === 40) {
|
|
31
|
+
let depth = 0;
|
|
32
|
+
while (i < n) {
|
|
33
|
+
const d = s[i++];
|
|
34
|
+
if (d === "\\")
|
|
35
|
+
i++;
|
|
36
|
+
else if (d === "(")
|
|
37
|
+
depth++;
|
|
38
|
+
else if (d === ")" && --depth === 0)
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
yield { text: s.slice(start, i), start, end: i, op: false };
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (c === 60 && s[i + 1] === "<") {
|
|
45
|
+
i += 2;
|
|
46
|
+
yield { text: "<<", start, end: i, op: false };
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (c === 62 && s[i + 1] === ">") {
|
|
50
|
+
i += 2;
|
|
51
|
+
yield { text: ">>", start, end: i, op: false };
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (c === 60) {
|
|
55
|
+
while (i < n && s[i] !== ">")
|
|
56
|
+
i++;
|
|
57
|
+
i++;
|
|
58
|
+
yield { text: s.slice(start, i), start, end: i, op: false };
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (c === 91 || c === 93 || c === 62 || c === 123 || c === 125 || c === 41) {
|
|
62
|
+
i++;
|
|
63
|
+
yield { text: s[start], start, end: i, op: false };
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
i++;
|
|
67
|
+
while (i < n && !WS.has(s.charCodeAt(i)) && !DELIM.has(s.charCodeAt(i)))
|
|
68
|
+
i++;
|
|
69
|
+
const text = s.slice(start, i);
|
|
70
|
+
const isOperand = c === 47 || /^[+-]?(\d+\.?\d*|\.\d+)$/.test(text);
|
|
71
|
+
yield { text, start, end: i, op: !isOperand };
|
|
72
|
+
if (text === "ID") {
|
|
73
|
+
const m = /\sEI(?=\s|$)/g;
|
|
74
|
+
m.lastIndex = i;
|
|
75
|
+
const hit = m.exec(s);
|
|
76
|
+
i = hit ? hit.index + hit[0].length : n;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
var LARGE_TEXT_PT = 24;
|
|
81
|
+
var SIZE_EPS = 0.05;
|
|
82
|
+
var area = (m) => Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
|
|
83
|
+
|
|
84
|
+
class TextScale {
|
|
85
|
+
ctm;
|
|
86
|
+
font = 0;
|
|
87
|
+
tm = 1;
|
|
88
|
+
stack = [];
|
|
89
|
+
constructor(ctm) {
|
|
90
|
+
this.ctm = ctm;
|
|
91
|
+
}
|
|
92
|
+
get unit() {
|
|
93
|
+
return this.ctm;
|
|
94
|
+
}
|
|
95
|
+
get size() {
|
|
96
|
+
return this.font * this.tm * this.ctm;
|
|
97
|
+
}
|
|
98
|
+
op(op, nums) {
|
|
99
|
+
switch (op) {
|
|
100
|
+
case "q":
|
|
101
|
+
this.stack.push([this.ctm, this.font]);
|
|
102
|
+
return false;
|
|
103
|
+
case "Q": {
|
|
104
|
+
const p = this.stack.pop();
|
|
105
|
+
if (p)
|
|
106
|
+
[this.ctm, this.font] = p;
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
case "cm":
|
|
110
|
+
if (nums.length === 6)
|
|
111
|
+
this.ctm *= area(nums);
|
|
112
|
+
return true;
|
|
113
|
+
case "BT":
|
|
114
|
+
this.tm = 1;
|
|
115
|
+
return true;
|
|
116
|
+
case "Tm":
|
|
117
|
+
if (nums.length === 6)
|
|
118
|
+
this.tm = area(nums);
|
|
119
|
+
return true;
|
|
120
|
+
case "Tf":
|
|
121
|
+
this.font = Math.abs(nums[nums.length - 1] ?? 0) || 0;
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function rewriteBlackText(src, ctx = {}) {
|
|
128
|
+
let fill = null;
|
|
129
|
+
let stroke = null;
|
|
130
|
+
const stack = [];
|
|
131
|
+
const scale = new TextScale(ctx.baseScale ?? 1);
|
|
132
|
+
let inText = false;
|
|
133
|
+
let fillDirty = false;
|
|
134
|
+
let strokeDirty = false;
|
|
135
|
+
const spans = [];
|
|
136
|
+
let lastOp = "";
|
|
137
|
+
let rect = null;
|
|
138
|
+
const edits = [];
|
|
139
|
+
let operands = [];
|
|
140
|
+
const rgbOf = () => {
|
|
141
|
+
if (operands.length !== 3)
|
|
142
|
+
return null;
|
|
143
|
+
const v = operands.map((t) => Number(t.text));
|
|
144
|
+
return v.some((x) => Number.isNaN(x)) ? null : { rgb: v, raw: operands.map((t) => t.text).join(" ") };
|
|
145
|
+
};
|
|
146
|
+
const isSmall = () => scale.size <= LARGE_TEXT_PT + SIZE_EPS;
|
|
147
|
+
let fillIns = null;
|
|
148
|
+
let strokeIns = null;
|
|
149
|
+
const syncOne = (colour, dirty, ins, at, gray, op) => {
|
|
150
|
+
if (!colour || !isNearBlack(colour.rgb))
|
|
151
|
+
return [dirty, ins];
|
|
152
|
+
const small = isSmall();
|
|
153
|
+
if (small && !dirty) {
|
|
154
|
+
const e = { start: at, end: at, text: ` ${gray}` };
|
|
155
|
+
edits.push(e);
|
|
156
|
+
return [true, e];
|
|
157
|
+
}
|
|
158
|
+
if (!small && dirty) {
|
|
159
|
+
if (ins)
|
|
160
|
+
ins.text = "";
|
|
161
|
+
else
|
|
162
|
+
edits.push({ start: at, end: at, text: ` ${colour.raw} ${op}` });
|
|
163
|
+
return [false, null];
|
|
164
|
+
}
|
|
165
|
+
return [dirty, ins];
|
|
166
|
+
};
|
|
167
|
+
const sync = (at) => {
|
|
168
|
+
[fillDirty, fillIns] = syncOne(fill, fillDirty, fillIns, at, "0 g", "rg");
|
|
169
|
+
[strokeDirty, strokeIns] = syncOne(stroke, strokeDirty, strokeIns, at, "0 G", "RG");
|
|
170
|
+
};
|
|
171
|
+
const flush = (s) => {
|
|
172
|
+
for (const r of s.rects) {
|
|
173
|
+
if (r.thickness > 0.2 * s.maxSmall)
|
|
174
|
+
continue;
|
|
175
|
+
edits.push({ start: r.start, end: r.start, text: "q 0 g " }, { start: r.end, end: r.end, text: " Q" });
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
for (const t of tokenize(src)) {
|
|
179
|
+
if (!t.op) {
|
|
180
|
+
operands.push(t);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const op = t.text;
|
|
184
|
+
const nums = operands.map((o) => Number(o.text));
|
|
185
|
+
const sizeMayHaveChanged = scale.op(op, nums);
|
|
186
|
+
switch (op) {
|
|
187
|
+
case "q":
|
|
188
|
+
stack.push([fill, stroke]);
|
|
189
|
+
break;
|
|
190
|
+
case "Q": {
|
|
191
|
+
const p = stack.pop();
|
|
192
|
+
if (p)
|
|
193
|
+
[fill, stroke] = p;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
case "BT":
|
|
197
|
+
inText = true;
|
|
198
|
+
fillDirty = strokeDirty = false;
|
|
199
|
+
fillIns = strokeIns = null;
|
|
200
|
+
sync(t.end);
|
|
201
|
+
break;
|
|
202
|
+
case "Tf":
|
|
203
|
+
case "Tm":
|
|
204
|
+
if (inText && sizeMayHaveChanged)
|
|
205
|
+
sync(t.end);
|
|
206
|
+
break;
|
|
207
|
+
case "ET":
|
|
208
|
+
if (inText) {
|
|
209
|
+
let ins = "";
|
|
210
|
+
if (fillDirty && fill)
|
|
211
|
+
ins += `${fill.raw} rg `;
|
|
212
|
+
if (strokeDirty && stroke)
|
|
213
|
+
ins += `${stroke.raw} RG `;
|
|
214
|
+
if (ins)
|
|
215
|
+
edits.push({ start: t.start, end: t.start, text: ins });
|
|
216
|
+
}
|
|
217
|
+
inText = false;
|
|
218
|
+
fillIns = strokeIns = null;
|
|
219
|
+
break;
|
|
220
|
+
case "rg":
|
|
221
|
+
case "RG": {
|
|
222
|
+
const c = rgbOf();
|
|
223
|
+
const isFill = op === "rg";
|
|
224
|
+
if (isFill)
|
|
225
|
+
fill = c;
|
|
226
|
+
else
|
|
227
|
+
stroke = c;
|
|
228
|
+
if (inText && c && isNearBlack(c.rgb) && isSmall()) {
|
|
229
|
+
edits.push({ start: operands[0].start, end: t.end, text: isFill ? "0 g" : "0 G" });
|
|
230
|
+
if (isFill)
|
|
231
|
+
fillDirty = true;
|
|
232
|
+
else
|
|
233
|
+
strokeDirty = true;
|
|
234
|
+
} else if (inText) {
|
|
235
|
+
if (isFill)
|
|
236
|
+
fillDirty = false;
|
|
237
|
+
else
|
|
238
|
+
strokeDirty = false;
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
case "g":
|
|
243
|
+
case "k":
|
|
244
|
+
case "sc":
|
|
245
|
+
case "scn":
|
|
246
|
+
case "cs":
|
|
247
|
+
fill = null;
|
|
248
|
+
if (inText)
|
|
249
|
+
fillDirty = false;
|
|
250
|
+
break;
|
|
251
|
+
case "G":
|
|
252
|
+
case "K":
|
|
253
|
+
case "SC":
|
|
254
|
+
case "SCN":
|
|
255
|
+
case "CS":
|
|
256
|
+
stroke = null;
|
|
257
|
+
if (inText)
|
|
258
|
+
strokeDirty = false;
|
|
259
|
+
break;
|
|
260
|
+
case "Do": {
|
|
261
|
+
const name = operands[operands.length - 1]?.text;
|
|
262
|
+
if (name?.startsWith("/"))
|
|
263
|
+
ctx.onDo?.(name.slice(1), scale.unit);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case "BDC":
|
|
267
|
+
case "BMC":
|
|
268
|
+
spans.push({ hasSmall: false, maxSmall: 0, rects: [] });
|
|
269
|
+
break;
|
|
270
|
+
case "EMC": {
|
|
271
|
+
const s = spans.pop();
|
|
272
|
+
if (!s)
|
|
273
|
+
break;
|
|
274
|
+
const parent = spans[spans.length - 1];
|
|
275
|
+
if (s.hasSmall)
|
|
276
|
+
flush(s);
|
|
277
|
+
else
|
|
278
|
+
parent?.rects.push(...s.rects);
|
|
279
|
+
if (parent && s.hasSmall) {
|
|
280
|
+
parent.hasSmall = true;
|
|
281
|
+
parent.maxSmall = Math.max(parent.maxSmall, s.maxSmall);
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case "Tj":
|
|
286
|
+
case "TJ":
|
|
287
|
+
case "'":
|
|
288
|
+
case '"': {
|
|
289
|
+
fillIns = strokeIns = null;
|
|
290
|
+
const span = spans[spans.length - 1];
|
|
291
|
+
if (span && inText && isSmall()) {
|
|
292
|
+
span.hasSmall = true;
|
|
293
|
+
span.maxSmall = Math.max(span.maxSmall, scale.size);
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case "re":
|
|
298
|
+
rect = !inText && fill && isNearBlack(fill.rgb) && operands.length === 4 && nums.every((x) => !Number.isNaN(x)) ? { start: operands[0].start, thickness: Math.min(Math.abs(nums[2]), Math.abs(nums[3])) * scale.unit } : null;
|
|
299
|
+
break;
|
|
300
|
+
case "f":
|
|
301
|
+
case "F":
|
|
302
|
+
case "f*":
|
|
303
|
+
if (rect && lastOp === "re")
|
|
304
|
+
spans[spans.length - 1]?.rects.push({ ...rect, end: t.end });
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
lastOp = op;
|
|
308
|
+
operands = [];
|
|
309
|
+
}
|
|
310
|
+
return applyEdits(src, edits);
|
|
311
|
+
}
|
|
312
|
+
function applyEdits(s, edits) {
|
|
313
|
+
if (!edits.length)
|
|
314
|
+
return s;
|
|
315
|
+
edits.sort((a, b) => a.start - b.start);
|
|
316
|
+
let out = "";
|
|
317
|
+
let pos = 0;
|
|
318
|
+
for (const e of edits) {
|
|
319
|
+
out += s.slice(pos, e.start) + e.text;
|
|
320
|
+
pos = e.end;
|
|
321
|
+
}
|
|
322
|
+
return out + s.slice(pos);
|
|
323
|
+
}
|
|
324
|
+
function isRichBlack(c) {
|
|
325
|
+
const [cy, m, y, k] = c;
|
|
326
|
+
const lo = Math.min(cy, m, y);
|
|
327
|
+
return k >= 0.7 && lo >= 0.3 && Math.max(cy, m, y) - lo <= 0.25;
|
|
328
|
+
}
|
|
329
|
+
function countRichBlackText(src, ctx = {}) {
|
|
330
|
+
const scale = new TextScale(ctx.baseScale ?? 1);
|
|
331
|
+
let fill = null;
|
|
332
|
+
const stack = [];
|
|
333
|
+
let count = 0;
|
|
334
|
+
let operands = [];
|
|
335
|
+
for (const t of tokenize(src)) {
|
|
336
|
+
if (!t.op) {
|
|
337
|
+
operands.push(t);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const op = t.text;
|
|
341
|
+
const nums = operands.map((o) => Number(o.text));
|
|
342
|
+
scale.op(op, nums);
|
|
343
|
+
switch (op) {
|
|
344
|
+
case "q":
|
|
345
|
+
stack.push(fill);
|
|
346
|
+
break;
|
|
347
|
+
case "Q":
|
|
348
|
+
if (stack.length)
|
|
349
|
+
fill = stack.pop() ?? null;
|
|
350
|
+
break;
|
|
351
|
+
case "k":
|
|
352
|
+
fill = nums.length === 4 && !nums.some(Number.isNaN) ? nums : null;
|
|
353
|
+
break;
|
|
354
|
+
case "sc":
|
|
355
|
+
case "scn":
|
|
356
|
+
fill = nums.length === 4 && !nums.some(Number.isNaN) ? nums : null;
|
|
357
|
+
break;
|
|
358
|
+
case "g":
|
|
359
|
+
case "rg":
|
|
360
|
+
case "cs":
|
|
361
|
+
fill = null;
|
|
362
|
+
break;
|
|
363
|
+
case "Do": {
|
|
364
|
+
const name = operands[operands.length - 1]?.text;
|
|
365
|
+
if (name?.startsWith("/"))
|
|
366
|
+
ctx.onDo?.(name.slice(1), scale.unit);
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
case "Tj":
|
|
370
|
+
case "TJ":
|
|
371
|
+
case "'":
|
|
372
|
+
case '"':
|
|
373
|
+
if (fill && isRichBlack(fill) && scale.size <= LARGE_TEXT_PT + SIZE_EPS)
|
|
374
|
+
count++;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
operands = [];
|
|
378
|
+
}
|
|
379
|
+
return count;
|
|
380
|
+
}
|
|
381
|
+
async function visitContent(doc, visit) {
|
|
382
|
+
const { PDFName, PDFRawStream, PDFArray, PDFRef, PDFDict, decodePDFRawStream } = await import("pdf-lib");
|
|
383
|
+
const ctx = doc.context;
|
|
384
|
+
const SKIP = ["Filter", "DecodeParms", "Length"].map((k) => PDFName.of(k));
|
|
385
|
+
let changed = false;
|
|
386
|
+
const decode = (s) => {
|
|
387
|
+
try {
|
|
388
|
+
return Buffer.from(decodePDFRawStream(s).decode()).toString("latin1");
|
|
389
|
+
} catch {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
const replace = (ref, old, text) => {
|
|
394
|
+
const entries = {};
|
|
395
|
+
for (const [k, v] of old.dict.entries())
|
|
396
|
+
if (!SKIP.includes(k))
|
|
397
|
+
entries[k.decodeText()] = v;
|
|
398
|
+
ctx.assign(ref, ctx.flateStream(Buffer.from(text, "latin1"), entries));
|
|
399
|
+
changed = true;
|
|
400
|
+
};
|
|
401
|
+
const formScale = new Map;
|
|
402
|
+
const formPages = new Map;
|
|
403
|
+
const formMatrixScale = (form) => {
|
|
404
|
+
const m = form.dict.lookupMaybe(PDFName.of("Matrix"), PDFArray);
|
|
405
|
+
const nums = m?.asArray().map((n) => Number(n.toString()));
|
|
406
|
+
return nums?.length === 6 && !nums.some(Number.isNaN) ? area(nums) : 1;
|
|
407
|
+
};
|
|
408
|
+
const pages = doc.getPages();
|
|
409
|
+
for (let p = 0;p < pages.length; p++) {
|
|
410
|
+
const page = pages[p];
|
|
411
|
+
const contents = page.node.Contents();
|
|
412
|
+
const refs = [];
|
|
413
|
+
if (contents instanceof PDFArray) {
|
|
414
|
+
for (let i = 0;i < contents.size(); i++) {
|
|
415
|
+
const r = contents.get(i);
|
|
416
|
+
if (r instanceof PDFRef)
|
|
417
|
+
refs.push(r);
|
|
418
|
+
}
|
|
419
|
+
} else if (contents instanceof PDFRawStream) {
|
|
420
|
+
const r = page.node.get(PDFName.of("Contents"));
|
|
421
|
+
if (r instanceof PDFRef)
|
|
422
|
+
refs.push(r);
|
|
423
|
+
}
|
|
424
|
+
const streams = refs.map((r) => ctx.lookup(r));
|
|
425
|
+
if (!refs.length || !streams.every((s) => s instanceof PDFRawStream))
|
|
426
|
+
continue;
|
|
427
|
+
const parts = streams.map((s) => decode(s));
|
|
428
|
+
if (parts.some((q) => q === null))
|
|
429
|
+
continue;
|
|
430
|
+
const joined = parts.join(`
|
|
431
|
+
`);
|
|
432
|
+
const xobjects = page.node.Resources()?.lookupMaybe(PDFName.of("XObject"), PDFDict);
|
|
433
|
+
const next = visit(joined, {
|
|
434
|
+
baseScale: 1,
|
|
435
|
+
pages: [p + 1],
|
|
436
|
+
onDo: (name, scale) => {
|
|
437
|
+
const ref = xobjects?.get(PDFName.of(name));
|
|
438
|
+
const form = ref instanceof PDFRef ? ctx.lookup(ref) : undefined;
|
|
439
|
+
if (!(ref instanceof PDFRef) || !(form instanceof PDFRawStream))
|
|
440
|
+
return;
|
|
441
|
+
if (form.dict.get(PDFName.of("Subtype")) !== PDFName.of("Form"))
|
|
442
|
+
return;
|
|
443
|
+
const at = scale * formMatrixScale(form);
|
|
444
|
+
formScale.set(ref.tag, Math.min(formScale.get(ref.tag) ?? Infinity, at));
|
|
445
|
+
(formPages.get(ref.tag) ?? formPages.set(ref.tag, new Set).get(ref.tag)).add(p + 1);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
if (next === joined)
|
|
449
|
+
continue;
|
|
450
|
+
replace(refs[0], streams[0], next);
|
|
451
|
+
page.node.set(PDFName.of("Contents"), refs[0]);
|
|
452
|
+
}
|
|
453
|
+
for (const [ref, obj] of ctx.enumerateIndirectObjects()) {
|
|
454
|
+
if (!(obj instanceof PDFRawStream))
|
|
455
|
+
continue;
|
|
456
|
+
if (obj.dict.get(PDFName.of("Subtype")) !== PDFName.of("Form"))
|
|
457
|
+
continue;
|
|
458
|
+
const text = decode(obj);
|
|
459
|
+
if (text === null)
|
|
460
|
+
continue;
|
|
461
|
+
const next = visit(text, {
|
|
462
|
+
baseScale: formScale.get(ref.tag) ?? 0,
|
|
463
|
+
pages: [...formPages.get(ref.tag) ?? []]
|
|
464
|
+
});
|
|
465
|
+
if (next !== text)
|
|
466
|
+
replace(ref, obj, next);
|
|
467
|
+
}
|
|
468
|
+
return changed;
|
|
469
|
+
}
|
|
470
|
+
async function blackTextToGray(pdf) {
|
|
471
|
+
const { PDFDocument } = await import("pdf-lib");
|
|
472
|
+
const doc = await PDFDocument.load(pdf, { updateMetadata: false });
|
|
473
|
+
const changed = await visitContent(doc, (src, info) => rewriteBlackText(src, info));
|
|
474
|
+
return changed ? doc.save({ updateFieldAppearances: false }) : pdf;
|
|
475
|
+
}
|
|
476
|
+
async function findRichBlackText(pdf) {
|
|
477
|
+
const { PDFDocument } = await import("pdf-lib");
|
|
478
|
+
const doc = await PDFDocument.load(pdf, { updateMetadata: false });
|
|
479
|
+
let runs = 0;
|
|
480
|
+
const pages = new Set;
|
|
481
|
+
await visitContent(doc, (src, info) => {
|
|
482
|
+
const n = countRichBlackText(src, info);
|
|
483
|
+
if (n) {
|
|
484
|
+
runs += n;
|
|
485
|
+
for (const p of info.pages)
|
|
486
|
+
pages.add(p);
|
|
487
|
+
}
|
|
488
|
+
return src;
|
|
489
|
+
});
|
|
490
|
+
return { runs, pages: [...pages].sort((a, b) => a - b) };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export { LARGE_TEXT_PT, rewriteBlackText, countRichBlackText, blackTextToGray, findRichBlackText };
|