gutterpress 0.11.20-alpha.2 → 0.11.20-alpha.3

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 (31) hide show
  1. package/dist/api/index.js +3 -2
  2. package/dist/{build-qmjg9r2d.js → build-a8ycsg80.js} +5 -4
  3. package/dist/{cli-zacevwcy.js → cli-9k2tb493.js} +181 -431
  4. package/dist/{cli-7z29z4wz.js → cli-hn9pk0hp.js} +1 -1
  5. package/dist/cli-sth219v1.js +418 -0
  6. package/dist/{cli-j802s31j.js → cli-x4p99w8d.js} +2 -2
  7. package/dist/{cli-gh0ym80r.js → cli-y9bcya7r.js} +121 -20
  8. package/dist/cli.js +12 -12
  9. package/dist/{doctor-fe7tcs4v.js → doctor-wbjkrcpc.js} +2 -2
  10. package/dist/{engine-yax79k9m.js → engine-fdtv01a2.js} +2 -2
  11. package/dist/{engine-9xb1ky8d.js → engine-yxnnp3hx.js} +1 -1
  12. package/dist/{ext-ahwxt6gv.js → ext-pesyrs68.js} +5 -4
  13. package/dist/{gutterpress-viewer-retj6ym2.js → gutterpress-viewer-1yse8cx3.js} +12 -4
  14. package/dist/{index-6xwtb7cw.js → index-r87y7wzw.js} +1 -1
  15. package/dist/index-sth219v1.js +418 -0
  16. package/dist/{index-razpty47.js → index-tjvrchxj.js} +297 -448
  17. package/dist/{index-3nr5rzzp.js → index-zagagnwq.js} +2 -2
  18. package/dist/index.js +4 -3
  19. package/dist/lib/build-fingerprint.d.ts +8 -0
  20. package/dist/lib/ghostscript.d.ts +70 -16
  21. package/dist/lib/pdf-inspect.d.ts +45 -0
  22. package/dist/{manifest.schema-74s1ykyv.json → manifest.schema-c9x8912a.json} +13 -1
  23. package/dist/{new-ftd4fec4.js → new-8yah8z91.js} +5 -4
  24. package/dist/pdf-inspect-zdmy7qca.js +35 -0
  25. package/dist/{preflight-8sqarcsr.js → preflight-8ccx4kdc.js} +5 -4
  26. package/dist/{preview-q76xn95h.js → preview-w7v9w36q.js} +5 -4
  27. package/dist/{publish-7gdy6gbf.js → publish-g5nh5myh.js} +5 -4
  28. package/dist/schema/manifest.types.d.ts +20 -2
  29. package/dist/test-helpers/testkit.d.ts +2 -2
  30. package/dist/{validate-e3ryegce.js → validate-1yh59x0x.js} +5 -4
  31. package/package.json +1 -1
@@ -4,7 +4,7 @@ import {
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "gutterpress",
7
- version: "0.11.20-alpha.2",
7
+ version: "0.11.20-alpha.3",
8
8
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
9
9
  author: "itlackey",
10
10
  license: "MPL-2.0",
@@ -0,0 +1,418 @@
1
+ // src/lib/pdf-inspect.ts
2
+ import { stat, readFile } from "node:fs/promises";
3
+ import { getDocumentProxy } from "unpdf";
4
+ import * as pdfjs from "unpdf/pdfjs";
5
+ function getOps(mod) {
6
+ if ("OPS" in mod && mod.OPS && typeof mod.OPS === "object") {
7
+ return mod.OPS;
8
+ }
9
+ throw new Error("unpdf/pdfjs missing OPS export");
10
+ }
11
+ var OPS = getOps(pdfjs);
12
+ var docCache = new Map;
13
+ var DOC_CACHE_MAX = 8;
14
+ function destroyEntry(entry) {
15
+ entry.doc.then((d) => d.destroy()).catch(() => {});
16
+ }
17
+ var EVICT_DESTROY_GRACE_MS = 60000;
18
+ var gracePending = new Map;
19
+ function destroyEntryAfterGrace(entry) {
20
+ const t = setTimeout(() => {
21
+ gracePending.delete(t);
22
+ destroyEntry(entry);
23
+ }, EVICT_DESTROY_GRACE_MS);
24
+ t.unref?.();
25
+ gracePending.set(t, entry);
26
+ }
27
+ function evictLru() {
28
+ while (docCache.size > DOC_CACHE_MAX) {
29
+ const oldestKey = docCache.keys().next().value;
30
+ if (oldestKey === undefined)
31
+ break;
32
+ const evicted = docCache.get(oldestKey);
33
+ docCache.delete(oldestKey);
34
+ if (evicted)
35
+ destroyEntryAfterGrace(evicted);
36
+ }
37
+ }
38
+ async function loadPdf(path) {
39
+ let st;
40
+ try {
41
+ st = await stat(path);
42
+ } catch {
43
+ return null;
44
+ }
45
+ const hit = docCache.get(path);
46
+ if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
47
+ docCache.delete(path);
48
+ docCache.set(path, hit);
49
+ try {
50
+ return await hit.doc;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ if (hit)
56
+ destroyEntry(hit);
57
+ const docPromise = readFile(path).then((buf) => getDocumentProxy(new Uint8Array(buf)));
58
+ docCache.delete(path);
59
+ docCache.set(path, { mtimeMs: st.mtimeMs, size: st.size, doc: docPromise });
60
+ evictLru();
61
+ try {
62
+ return await docPromise;
63
+ } catch {
64
+ if (docCache.get(path)?.doc === docPromise)
65
+ docCache.delete(path);
66
+ return null;
67
+ }
68
+ }
69
+ var activeRetains = 0;
70
+ function retainPdfCache() {
71
+ activeRetains++;
72
+ let released = false;
73
+ return () => {
74
+ if (released)
75
+ return;
76
+ released = true;
77
+ activeRetains--;
78
+ if (activeRetains === 0)
79
+ clearPdfCache();
80
+ };
81
+ }
82
+ function clearPdfCache() {
83
+ for (const [t, entry] of gracePending) {
84
+ clearTimeout(t);
85
+ destroyEntry(entry);
86
+ }
87
+ gracePending.clear();
88
+ for (const entry of docCache.values())
89
+ destroyEntry(entry);
90
+ docCache.clear();
91
+ }
92
+ function pendingGraceDestroyCount() {
93
+ return gracePending.size;
94
+ }
95
+ function getPageSize(page) {
96
+ const vp = page.getViewport({ scale: 1 });
97
+ return { w: vp.width, h: vp.height };
98
+ }
99
+ async function getOutlineCount(doc) {
100
+ try {
101
+ const outline = await doc.getOutline();
102
+ return outline ? outline.length : 0;
103
+ } catch {
104
+ return 0;
105
+ }
106
+ }
107
+ async function getPageLabels(doc) {
108
+ try {
109
+ return await doc.getPageLabels();
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+ async function countLinkAnnotations(doc, stopAtFirst = false) {
115
+ let count = 0;
116
+ for (let i = 1;i <= doc.numPages; i++) {
117
+ try {
118
+ const page = await doc.getPage(i);
119
+ const annots = await page.getAnnotations();
120
+ for (const a of annots) {
121
+ if (a.subtype === "Link") {
122
+ count++;
123
+ if (stopAtFirst)
124
+ return count;
125
+ }
126
+ }
127
+ } catch {}
128
+ }
129
+ return count;
130
+ }
131
+ var textCache = new WeakMap;
132
+ function getTextPass(doc) {
133
+ let p = textCache.get(doc);
134
+ if (!p) {
135
+ p = (async () => {
136
+ const textByPage = [];
137
+ const positions = [];
138
+ const runsByPage = [];
139
+ for (let i = 1;i <= doc.numPages; i++) {
140
+ try {
141
+ const page = await doc.getPage(i);
142
+ const tc = await page.getTextContent();
143
+ let pageText = "";
144
+ const runs = [];
145
+ for (const item of tc.items) {
146
+ const it = item;
147
+ if (typeof it.str !== "string")
148
+ continue;
149
+ pageText += it.str;
150
+ if (it.transform && it.str.trim().length > 0) {
151
+ const x = it.transform[4];
152
+ const y = it.transform[5];
153
+ positions.push({ x, y });
154
+ runs.push({ s: it.str, x, y, w: it.width ?? 0, h: it.height ?? 0 });
155
+ }
156
+ }
157
+ textByPage.push(pageText);
158
+ runsByPage.push(runs);
159
+ } catch {
160
+ textByPage.push("");
161
+ runsByPage.push([]);
162
+ }
163
+ }
164
+ return { textByPage, positions, runsByPage };
165
+ })();
166
+ textCache.set(doc, p);
167
+ }
168
+ return p;
169
+ }
170
+ var opCache = new WeakMap;
171
+ function multiply(m1, m2) {
172
+ return [
173
+ m1[0] * m2[0] + m1[2] * m2[1],
174
+ m1[1] * m2[0] + m1[3] * m2[1],
175
+ m1[0] * m2[2] + m1[2] * m2[3],
176
+ m1[1] * m2[2] + m1[3] * m2[3],
177
+ m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
178
+ m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
179
+ ];
180
+ }
181
+ function fontIsEmbedded(f) {
182
+ if (f.isType3Font)
183
+ return true;
184
+ if (f.missingFile === true)
185
+ return false;
186
+ if (f.isStandardFont === true)
187
+ return false;
188
+ return true;
189
+ }
190
+ function getOpPass(doc) {
191
+ let p = opCache.get(doc);
192
+ if (!p) {
193
+ p = (async () => {
194
+ const imagesByPage = new Map;
195
+ const fonts = [];
196
+ const seenFonts = new Set;
197
+ for (let i = 1;i <= doc.numPages; i++) {
198
+ let page;
199
+ try {
200
+ page = await doc.getPage(i);
201
+ } catch {
202
+ continue;
203
+ }
204
+ let ops;
205
+ try {
206
+ ops = await page.getOperatorList();
207
+ } catch {
208
+ continue;
209
+ }
210
+ let ctm = [1, 0, 0, 1, 0, 0];
211
+ const stack = [];
212
+ const pageImages = [];
213
+ for (let j = 0;j < ops.fnArray.length; j++) {
214
+ const fn = ops.fnArray[j];
215
+ const args = ops.argsArray[j];
216
+ if (fn === OPS.save) {
217
+ stack.push(ctm.slice());
218
+ } else if (fn === OPS.restore) {
219
+ ctm = stack.pop() ?? ctm;
220
+ } else if (fn === OPS.transform) {
221
+ ctm = multiply(ctm, args);
222
+ } else if (fn === OPS.paintImageXObject || fn === OPS.paintImageXObjectRepeat || fn === OPS.paintInlineImageXObject) {
223
+ const name = typeof args[0] === "string" ? args[0] : "(inline)";
224
+ pageImages.push({
225
+ name,
226
+ placedW: Math.hypot(ctm[0], ctm[1]),
227
+ placedH: Math.hypot(ctm[2], ctm[3]),
228
+ x: ctm[4],
229
+ y: ctm[5],
230
+ page: i
231
+ });
232
+ } else if (fn === OPS.setFont) {
233
+ const ref = args[0];
234
+ if (typeof ref === "string" && !seenFonts.has(ref)) {
235
+ seenFonts.add(ref);
236
+ try {
237
+ if (page.commonObjs.has(ref)) {
238
+ const f = page.commonObjs.get(ref);
239
+ fonts.push({
240
+ name: f?.name ?? ref,
241
+ embedded: fontIsEmbedded(f ?? {})
242
+ });
243
+ }
244
+ } catch {}
245
+ }
246
+ }
247
+ }
248
+ if (pageImages.length > 0)
249
+ imagesByPage.set(i, pageImages);
250
+ }
251
+ return { imagesByPage, fonts };
252
+ })();
253
+ opCache.set(doc, p);
254
+ }
255
+ return p;
256
+ }
257
+ var opSet = (names) => new Set(names.map((name) => OPS[name]).filter((op) => op !== undefined));
258
+ var FILL_COLOR_OPS = opSet(["setFillGray", "setFillRGBColor", "setFillCMYKColor", "setFillColor"]);
259
+ var STROKE_COLOR_OPS = opSet(["setStrokeGray", "setStrokeRGBColor", "setStrokeCMYKColor", "setStrokeColor"]);
260
+ var SHOW_TEXT_OPS = opSet(["showText", "showSpacedText", "nextLineShowText", "nextLineSetSpacingShowText"]);
261
+ async function unsplittableTextReasons(pdfBytes, pages) {
262
+ const GSTATE_BIT = { ca: 1, CA: 2, BM: 4, SMask: 8 };
263
+ const COMPOSITING = 15;
264
+ const FILL_PATTERN = 16;
265
+ const STROKE_PATTERN = 32;
266
+ const CLIP_TEXT = 64;
267
+ const doc = await getDocumentProxy(new Uint8Array(pdfBytes));
268
+ const reasons = new Map;
269
+ try {
270
+ const scan = pages ?? Array.from({ length: doc.numPages }, (_, i) => i + 1);
271
+ for (const i of scan) {
272
+ const page = await doc.getPage(i);
273
+ const ops = await page.getOperatorList();
274
+ let state = 0;
275
+ const groups = [];
276
+ const stack = [];
277
+ let showsText = false;
278
+ for (let j = 0;j < ops.fnArray.length; j++) {
279
+ const fn = ops.fnArray[j];
280
+ if (fn === OPS.save || fn === OPS.paintFormXObjectBegin)
281
+ stack.push(state);
282
+ else if (fn === OPS.restore || fn === OPS.paintFormXObjectEnd)
283
+ state = stack.pop() ?? 0;
284
+ else if (fn === OPS.beginGroup) {
285
+ const group = ops.argsArray[j][0];
286
+ groups.push((state & COMPOSITING) !== 0 || !!(group.smask || group.knockout));
287
+ } else if (fn === OPS.endGroup)
288
+ groups.pop();
289
+ else if (fn === OPS.setFont) {
290
+ const ref = ops.argsArray[j][0];
291
+ const font = typeof ref === "string" && page.commonObjs.has(ref) ? page.commonObjs.get(ref) : undefined;
292
+ if (font?.isType3Font) {
293
+ reasons.set(i, "type3-font");
294
+ break;
295
+ }
296
+ } else if (fn === OPS.setFillColorN || fn === OPS.setStrokeColorN) {
297
+ const bit = fn === OPS.setFillColorN ? FILL_PATTERN : STROKE_PATTERN;
298
+ const kind = ops.argsArray[j][0];
299
+ state = kind === "TilingPattern" || kind === "Shading" ? state | bit : state & ~bit;
300
+ } else if (FILL_COLOR_OPS.has(fn))
301
+ state &= ~FILL_PATTERN;
302
+ else if (STROKE_COLOR_OPS.has(fn))
303
+ state &= ~STROKE_PATTERN;
304
+ else if (fn === OPS.setTextRenderingMode) {
305
+ state = ops.argsArray[j][0] >= 4 ? state | CLIP_TEXT : state & ~CLIP_TEXT;
306
+ } else if (fn === OPS.setGState) {
307
+ for (const [key, value] of ops.argsArray[j][0]) {
308
+ const bit = GSTATE_BIT[key];
309
+ if (!bit)
310
+ continue;
311
+ const on = key === "BM" ? value !== "source-over" : key === "SMask" ? !!value : value < 1;
312
+ state = on ? state | bit : state & ~bit;
313
+ }
314
+ } else if (SHOW_TEXT_OPS.has(fn)) {
315
+ showsText = true;
316
+ if (state || groups.includes(true)) {
317
+ const paint = state & (FILL_PATTERN | STROKE_PATTERN | CLIP_TEXT);
318
+ reasons.set(i, paint ? "text-pattern-or-clip" : "text-transparency");
319
+ break;
320
+ }
321
+ }
322
+ }
323
+ if (!showsText && !reasons.has(i))
324
+ reasons.set(i, "no-text");
325
+ }
326
+ } finally {
327
+ await doc.destroy();
328
+ }
329
+ return reasons;
330
+ }
331
+ function getImagePixelSize(page, name, timeoutMs = 2000) {
332
+ return new Promise((resolve) => {
333
+ let done = false;
334
+ const finish = (v) => {
335
+ if (!done) {
336
+ done = true;
337
+ resolve(v);
338
+ }
339
+ };
340
+ const t = setTimeout(() => finish(null), timeoutMs);
341
+ try {
342
+ page.objs.get(name, (obj) => {
343
+ clearTimeout(t);
344
+ finish(obj && obj.width && obj.height ? { width: obj.width, height: obj.height } : null);
345
+ });
346
+ } catch {
347
+ clearTimeout(t);
348
+ finish(null);
349
+ }
350
+ });
351
+ }
352
+ var PAINT_OPS = new Set([
353
+ "stroke",
354
+ "closeStroke",
355
+ "fill",
356
+ "eoFill",
357
+ "fillStroke",
358
+ "eoFillStroke",
359
+ "closeFillStroke",
360
+ "closeEOFillStroke",
361
+ "shadingFill",
362
+ "paintImageXObject",
363
+ "paintInlineImageXObject",
364
+ "paintImageXObjectRepeat",
365
+ "paintImageMaskXObject",
366
+ "paintImageMaskXObjectGroup",
367
+ "paintImageMaskXObjectRepeat",
368
+ "paintSolidColorImageMask",
369
+ "showText",
370
+ "showSpacedText",
371
+ "nextLineShowText",
372
+ "nextLineSetSpacingShowText"
373
+ ].map((name) => OPS[name]).filter((op) => op !== undefined));
374
+ async function pageHasMarks(doc, pageNum) {
375
+ const ops = await (await doc.getPage(pageNum)).getOperatorList();
376
+ return ops.fnArray.some((fn, i) => fn === OPS.constructPath ? PAINT_OPS.has(ops.argsArray[i][0]) : PAINT_OPS.has(fn));
377
+ }
378
+ async function getImageResolutions(doc) {
379
+ const { imagesByPage } = await getOpPass(doc);
380
+ const out = [];
381
+ for (const [pageNum, imgs] of imagesByPage) {
382
+ let page;
383
+ try {
384
+ page = await doc.getPage(pageNum);
385
+ } catch {
386
+ continue;
387
+ }
388
+ for (const img of imgs) {
389
+ if (img.name === "(inline)")
390
+ continue;
391
+ const px = await getImagePixelSize(page, img.name);
392
+ if (!px)
393
+ continue;
394
+ const placedWin = img.placedW / 72;
395
+ const placedHin = img.placedH / 72;
396
+ if (placedWin <= 0 || placedHin <= 0)
397
+ continue;
398
+ out.push({
399
+ page: pageNum,
400
+ xDpi: px.width / placedWin,
401
+ yDpi: px.height / placedHin
402
+ });
403
+ }
404
+ }
405
+ return out;
406
+ }
407
+ async function isLoadable(doc) {
408
+ try {
409
+ for (let i = 1;i <= doc.numPages; i++) {
410
+ await doc.getPage(i);
411
+ }
412
+ return true;
413
+ } catch {
414
+ return false;
415
+ }
416
+ }
417
+
418
+ export { DOC_CACHE_MAX, loadPdf, retainPdfCache, clearPdfCache, pendingGraceDestroyCount, getPageSize, getOutlineCount, getPageLabels, countLinkAnnotations, getTextPass, getOpPass, unsplittableTextReasons, pageHasMarks, getImageResolutions, isLoadable };
@@ -1116,7 +1116,7 @@ import { fileURLToPath } from "node:url";
1116
1116
  var favicon_default = "./favicon-272ga6y0.ico";
1117
1117
 
1118
1118
  // src/assets/manifest.schema.json
1119
- var manifest_schema_default = "./manifest.schema-74s1ykyv.json";
1119
+ var manifest_schema_default = "./manifest.schema-c9x8912a.json";
1120
1120
 
1121
1121
  // src/assets/preview/scripts/preview-interface.js
1122
1122
  var preview_interface_default = "./preview-interface-nnmrxyqm.js";
@@ -1131,7 +1131,7 @@ var preview_shell_default = "./preview-shell-wk9k3gcd.js";
1131
1131
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
1132
1132
 
1133
1133
  // src/assets/engine/gutterpress-viewer.js
1134
- var gutterpress_viewer_default = "./gutterpress-viewer-retj6ym2.js";
1134
+ var gutterpress_viewer_default = "./gutterpress-viewer-1yse8cx3.js";
1135
1135
 
1136
1136
  // src/assets/engine/gutterpress-agent.js
1137
1137
  var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "./cli-7z29z4wz.js";
3
+ } from "./cli-hn9pk0hp.js";
4
4
  import {
5
5
  enhancedPath,
6
6
  execCapture,
@@ -132,8 +132,8 @@ async function resolveGhostscript(targetPlatform = process.platform, env = proce
132
132
  }
133
133
  return targetPlatform === "win32" ? findWindowsGhostscriptInstall(env) : undefined;
134
134
  }
135
- async function hasLiveTransparency(pdfBytes) {
136
- const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } = await import("pdf-lib");
135
+ async function transparentPages(pdfBytes) {
136
+ const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFArray, PDFRef } = await import("pdf-lib");
137
137
  let doc;
138
138
  try {
139
139
  doc = await PDFDocument.load(pdfBytes, {
@@ -141,36 +141,64 @@ async function hasLiveTransparency(pdfBytes) {
141
141
  throwOnInvalidObject: false
142
142
  });
143
143
  } catch {
144
- return false;
144
+ return [];
145
145
  }
146
146
  const ctx = doc.context;
147
- const NONE = "/None";
148
- for (const [, obj] of ctx.enumerateIndirectObjects()) {
149
- const dict = obj instanceof PDFRawStream ? obj.dict : obj instanceof PDFDict ? obj : undefined;
150
- if (!dict)
151
- continue;
147
+ const SKIP = new Set(["Parent", "Font", "Annots"]);
148
+ const isSet = (v) => v !== undefined && String(v) !== "/None";
149
+ const dictHasTransparency = (dict) => {
152
150
  const smask = dict.get(PDFName.of("SMask"));
153
151
  const subtype = String(dict.get(PDFName.of("Subtype")) ?? "");
154
152
  const type = String(dict.get(PDFName.of("Type")) ?? "");
155
- if (subtype === "/Image" && smask && String(smask) !== NONE)
153
+ if (subtype === "/Image" && isSet(smask))
156
154
  return true;
157
- if (type === "/ExtGState") {
158
- if (smask && String(smask) !== NONE)
155
+ if (type === "/ExtGState" || dict.has(PDFName.of("ca")) || dict.has(PDFName.of("BM"))) {
156
+ if (isSet(smask))
159
157
  return true;
160
158
  for (const key of ["ca", "CA"]) {
161
- const alpha = dict.get(PDFName.of(key));
159
+ const alpha = ctx.lookup(dict.get(PDFName.of(key)));
162
160
  if (alpha instanceof PDFNumber && alpha.asNumber() < 1)
163
161
  return true;
164
162
  }
163
+ const bm = ctx.lookup(dict.get(PDFName.of("BM")));
164
+ const mode = bm instanceof PDFArray ? bm.get(0) : bm;
165
+ if (mode && !["/Normal", "/Compatible"].includes(String(mode)))
166
+ return true;
165
167
  }
166
- const group = dict.get(PDFName.of("Group"));
167
- if (group) {
168
- const resolved = group instanceof PDFRef ? ctx.lookup(group) : group;
169
- if (resolved instanceof PDFDict && String(resolved.get(PDFName.of("S"))) === "/Transparency")
168
+ const group = ctx.lookup(dict.get(PDFName.of("Group")));
169
+ return group instanceof PDFDict && String(group.get(PDFName.of("S"))) === "/Transparency";
170
+ };
171
+ const reaches = (root) => {
172
+ const seen = new Set;
173
+ const stack = [root];
174
+ while (stack.length) {
175
+ const node = stack.pop();
176
+ const obj = node instanceof PDFRef ? ctx.lookup(node) : node;
177
+ if (!obj || seen.has(obj))
178
+ continue;
179
+ seen.add(obj);
180
+ if (obj instanceof PDFArray) {
181
+ stack.push(...obj.asArray());
182
+ continue;
183
+ }
184
+ const dict = obj instanceof PDFRawStream ? obj.dict : obj instanceof PDFDict ? obj : undefined;
185
+ if (!dict)
186
+ continue;
187
+ if (dictHasTransparency(dict))
170
188
  return true;
189
+ for (const [key, value] of dict.entries()) {
190
+ if (!SKIP.has(key.asString().slice(1)))
191
+ stack.push(value);
192
+ }
171
193
  }
172
- }
173
- return false;
194
+ return false;
195
+ };
196
+ const pages = [];
197
+ doc.getPages().forEach((page, i) => {
198
+ if (reaches(page.node))
199
+ pages.push(i + 1);
200
+ });
201
+ return pages;
174
202
  }
175
203
  async function findPostScriptShadingPages(pdfBytes) {
176
204
  const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } = await import("pdf-lib");
@@ -241,6 +269,75 @@ async function stripAnnotations(pdfPath, stagingDir) {
241
269
  const out = await doc.save();
242
270
  await writeFile(pdfPath, out);
243
271
  }
272
+ function planTransparentPages(transparent, reasons, flattenPages, gradientPages) {
273
+ const split = [];
274
+ const artOnly = [];
275
+ const kept = new Map;
276
+ for (const n of transparent) {
277
+ const reason = reasons.get(n);
278
+ if (reason === "no-text")
279
+ artOnly.push(n);
280
+ else if (flattenPages.includes(n))
281
+ kept.set(n, "listed");
282
+ else if (reason)
283
+ kept.set(n, reason);
284
+ else if (gradientPages.includes(n))
285
+ kept.set(n, "gradient");
286
+ else
287
+ split.push(n);
288
+ }
289
+ return { split, artOnly, kept };
290
+ }
291
+ async function flattenTransparentPages(pdfPath, pageNumbers, stagingDir, dpi = 300) {
292
+ const pages = [...new Set(pageNumbers)].sort((a, b) => a - b);
293
+ if (pages.length === 0)
294
+ return;
295
+ const ghostscript = await resolveGhostscript();
296
+ if (!ghostscript) {
297
+ throw new Error("Ghostscript executable not found. Install Ghostscript or set GHOSTSCRIPT_PATH to its command-line executable.");
298
+ }
299
+ const art = join(stagingDir, "flatten-art.pdf");
300
+ const text = join(stagingDir, "flatten-text.pdf");
301
+ const pageList = `-sPageList=${pages.join(",")}`;
302
+ const gs = (...args) => run(ghostscript, [
303
+ "-q",
304
+ "-dBATCH",
305
+ "-dNOPAUSE",
306
+ "-dSAFER",
307
+ "-dPreserveAnnots=false",
308
+ "-dNO_PDFMARK_OUTLINES",
309
+ ...args
310
+ ]);
311
+ try {
312
+ await gs(pageList, "-sDEVICE=pdfimage24", "-dFILTERTEXT", `-r${dpi}`, `-sOutputFile=${art}`, pdfPath);
313
+ await gs(pageList, "-sDEVICE=pdfwrite", "-dNOTRANSPARENCY", "-dFILTERIMAGE", "-dFILTERVECTOR", "-dCompatibilityLevel=1.3", `-sOutputFile=${text}`, pdfPath);
314
+ const { PDFDocument, PDFName } = await import("pdf-lib");
315
+ const doc = await PDFDocument.load(await readFile(pdfPath), { updateMetadata: false });
316
+ const artDoc = await PDFDocument.load(await readFile(art));
317
+ const textDoc = await PDFDocument.load(await readFile(text));
318
+ if (artDoc.getPageCount() !== pages.length || textDoc.getPageCount() !== pages.length) {
319
+ throw new Error(`Ghostscript returned ${artDoc.getPageCount()} artwork and ${textDoc.getPageCount()} text pages for ${pages.length} transparent pages.`);
320
+ }
321
+ const artPages = await doc.embedPages(artDoc.getPages());
322
+ const withText = textDoc.getPages().filter((p) => p.node.Contents());
323
+ const embeddedText = await doc.embedPages(withText);
324
+ const textPages = textDoc.getPages().map((p) => embeddedText[withText.indexOf(p)]);
325
+ pages.forEach((pageNumber, i) => {
326
+ const page = doc.getPage(pageNumber - 1);
327
+ const { x, y, width, height } = page.getMediaBox();
328
+ page.node.set(PDFName.of("Contents"), doc.context.obj([]));
329
+ page.node.set(PDFName.of("Resources"), doc.context.obj({}));
330
+ page.node.delete(PDFName.of("Group"));
331
+ page.drawPage(artPages[i], { x, y, width, height });
332
+ const textPage = textPages[i];
333
+ if (textPage)
334
+ page.drawPage(textPage, { x, y, width, height });
335
+ });
336
+ await writeFile(pdfPath, await doc.save());
337
+ } finally {
338
+ await Promise.all([art, text].map((f) => unlink(f).catch(() => {})));
339
+ }
340
+ }
244
341
  function makePdfxDefPs(params) {
245
342
  const pdfxVersion = params.pdfx === "x3" ? "PDF/X-3:2002" : "PDF/X-1:2001";
246
343
  const pdfxConformance = params.pdfx === "x1a" ? "PDF/X-1a:2001" : null;
@@ -332,6 +429,10 @@ async function convertToPdfxCmyk(inputPdf, outPdf, config) {
332
429
  "-dDownsampleColorImages=false",
333
430
  "-dDownsampleGrayImages=false",
334
431
  "-dDownsampleMonoImages=false",
432
+ "-dAutoFilterColorImages=false",
433
+ "-dColorImageFilter=/FlateEncode",
434
+ "-dAutoFilterGrayImages=false",
435
+ "-dGrayImageFilter=/FlateEncode",
335
436
  ...config.rasterDpi ? [`-r${config.rasterDpi}`] : [],
336
437
  "-sProcessColorModel=DeviceCMYK",
337
438
  "-sColorConversionStrategy=CMYK",
@@ -584,4 +685,4 @@ async function getSystemDiagnostics() {
584
685
  };
585
686
  }
586
687
 
587
- export { setLogLevel, debug, info, warn, error, log, resolveGhostscript, hasLiveTransparency, findPostScriptShadingPages, stripAnnotations, stampCreator, convertToPdfxCmyk, PACKAGE_META, PACKAGE_VERSION, defaultConfigDir, FileTokenStore, getSystemDiagnostics };
688
+ export { setLogLevel, debug, info, warn, error, log, resolveGhostscript, transparentPages, findPostScriptShadingPages, stripAnnotations, planTransparentPages, flattenTransparentPages, stampCreator, convertToPdfxCmyk, PACKAGE_META, PACKAGE_VERSION, defaultConfigDir, FileTokenStore, getSystemDiagnostics };
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  UsageError,
7
7
  package_default,
8
8
  rejectUnknownFlags
9
- } from "./cli-7z29z4wz.js";
9
+ } from "./cli-hn9pk0hp.js";
10
10
  import {
11
11
  EXIT_CODES
12
12
  } from "./cli-46ycxe6r.js";
@@ -18,14 +18,14 @@ import {
18
18
  import { defineCommand, parseArgs, runMain } from "citty";
19
19
  import { statSync } from "node:fs";
20
20
  var SUBCOMMANDS = {
21
- new: () => import("./new-ftd4fec4.js").then((m) => m.default),
22
- preview: () => import("./preview-q76xn95h.js").then((m) => m.default),
23
- build: () => import("./build-qmjg9r2d.js").then((m) => m.default),
24
- publish: () => import("./publish-7gdy6gbf.js").then((m) => m.default),
25
- validate: () => import("./validate-e3ryegce.js").then((m) => m.default),
26
- preflight: () => import("./preflight-8sqarcsr.js").then((m) => m.default),
27
- doctor: () => import("./doctor-fe7tcs4v.js").then((m) => m.default),
28
- ext: () => import("./ext-ahwxt6gv.js").then((m) => m.default)
21
+ new: () => import("./new-8yah8z91.js").then((m) => m.default),
22
+ preview: () => import("./preview-w7v9w36q.js").then((m) => m.default),
23
+ build: () => import("./build-a8ycsg80.js").then((m) => m.default),
24
+ publish: () => import("./publish-g5nh5myh.js").then((m) => m.default),
25
+ validate: () => import("./validate-1yh59x0x.js").then((m) => m.default),
26
+ preflight: () => import("./preflight-8ccx4kdc.js").then((m) => m.default),
27
+ doctor: () => import("./doctor-wbjkrcpc.js").then((m) => m.default),
28
+ ext: () => import("./ext-pesyrs68.js").then((m) => m.default)
29
29
  };
30
30
  var VERSION = package_default.version;
31
31
  var main = defineCommand({
@@ -50,7 +50,7 @@ async function preflightRequiredInvocations(rawArgs) {
50
50
  const [command, ...commandArgs] = rawArgs;
51
51
  try {
52
52
  if (command === "new") {
53
- const { newArgs } = await import("./new-ftd4fec4.js");
53
+ const { newArgs } = await import("./new-8yah8z91.js");
54
54
  rejectUnknownFlags(commandArgs, newArgs, "new");
55
55
  const parsed = parseArgs(commandArgs, {
56
56
  ...newArgs,
@@ -62,7 +62,7 @@ async function preflightRequiredInvocations(rawArgs) {
62
62
  return;
63
63
  }
64
64
  if (command === "preflight") {
65
- const { preflightArgs } = await import("./preflight-8sqarcsr.js");
65
+ const { preflightArgs } = await import("./preflight-8ccx4kdc.js");
66
66
  rejectUnknownFlags(commandArgs, preflightArgs, "preflight");
67
67
  const parsed = parseArgs(commandArgs, {
68
68
  ...preflightArgs,
@@ -82,7 +82,7 @@ async function preflightRequiredInvocations(rawArgs) {
82
82
  }
83
83
  if (subcommand.startsWith("-"))
84
84
  return;
85
- const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-ahwxt6gv.js");
85
+ const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-pesyrs68.js");
86
86
  if (!EXT_SUBCOMMANDS.includes(subcommand)) {
87
87
  throw new UsageError(`gutterpress ext: unknown command "${subcommand}"`);
88
88
  }