gutterpress 0.11.20-alpha.1 → 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 (38) hide show
  1. package/dist/api/index.js +3 -2
  2. package/dist/{build-cmf4nbpm.js → build-a8ycsg80.js} +5 -4
  3. package/dist/checks/pdf/index.d.ts +1 -0
  4. package/dist/checks/pdf/safe-margins.d.ts +3 -0
  5. package/dist/{cli-x1nfhaf1.js → cli-9k2tb493.js} +461 -607
  6. package/dist/{cli-7shp2kmj.js → cli-hn9pk0hp.js} +1 -1
  7. package/dist/cli-sth219v1.js +418 -0
  8. package/dist/{cli-rtpm04dv.js → cli-x4p99w8d.js} +5 -5
  9. package/dist/{cli-mmm22cza.js → cli-y9bcya7r.js} +121 -20
  10. package/dist/cli.js +12 -12
  11. package/dist/{doctor-0d6esj02.js → doctor-wbjkrcpc.js} +2 -2
  12. package/dist/{engine-adxptzf0.js → engine-fdtv01a2.js} +2 -2
  13. package/dist/{engine-1v8kmv3a.js → engine-yxnnp3hx.js} +1 -1
  14. package/dist/{ext-65nfh7st.js → ext-pesyrs68.js} +5 -4
  15. package/dist/{gutterpress-viewer-0r4rt4de.js → gutterpress-viewer-1yse8cx3.js} +355 -102
  16. package/dist/{index-cngw658w.js → index-r87y7wzw.js} +1 -1
  17. package/dist/index-sth219v1.js +418 -0
  18. package/dist/{index-1y885h1j.js → index-tjvrchxj.js} +576 -623
  19. package/dist/{index-pnvzgftg.js → index-zagagnwq.js} +5 -5
  20. package/dist/index.js +4 -3
  21. package/dist/lib/build-fingerprint.d.ts +8 -0
  22. package/dist/lib/ghostscript.d.ts +70 -16
  23. package/dist/lib/pdf-inspect.d.ts +45 -0
  24. package/dist/lib/presets.d.ts +3 -1
  25. package/dist/{manifest.schema-nrykd2c6.json → manifest.schema-c9x8912a.json} +21 -3
  26. package/dist/{new-y4a2xz3g.js → new-8yah8z91.js} +5 -4
  27. package/dist/pdf-inspect-zdmy7qca.js +35 -0
  28. package/dist/{preflight-kt7wfgbd.js → preflight-8ccx4kdc.js} +5 -4
  29. package/dist/preview/file-watcher.d.ts +7 -1
  30. package/dist/{preview-bridge-evecsh4w.js → preview-bridge-8wjcjec3.js} +3 -2
  31. package/dist/{preview-interface-k2mj5kvq.js → preview-interface-nnmrxyqm.js} +37 -41
  32. package/dist/{preview-shell-7jfe6vsc.js → preview-shell-wk9k3gcd.js} +18 -6
  33. package/dist/{preview-5ze728kp.js → preview-w7v9w36q.js} +5 -4
  34. package/dist/{publish-5tpcyysh.js → publish-g5nh5myh.js} +5 -4
  35. package/dist/schema/manifest.types.d.ts +47 -8
  36. package/dist/test-helpers/testkit.d.ts +2 -2
  37. package/dist/{validate-p0hkqkfe.js → validate-1yh59x0x.js} +5 -4
  38. package/package.json +1 -1
@@ -6,20 +6,22 @@ import {
6
6
  defaultConfigDir,
7
7
  error,
8
8
  findPostScriptShadingPages,
9
- hasLiveTransparency,
9
+ flattenTransparentPages,
10
10
  info,
11
11
  log,
12
+ planTransparentPages,
12
13
  resolveGhostscript,
13
14
  setLogLevel,
14
15
  stampCreator,
15
16
  stripAnnotations,
17
+ transparentPages,
16
18
  warn
17
- } from "./cli-mmm22cza.js";
19
+ } from "./cli-y9bcya7r.js";
18
20
  import {
19
21
  DEBOUNCE,
20
22
  UsageError,
21
23
  resolvePort
22
- } from "./cli-7shp2kmj.js";
24
+ } from "./cli-hn9pk0hp.js";
23
25
  import {
24
26
  blackTextToGray
25
27
  } from "./cli-ht1bcqdd.js";
@@ -28,11 +30,24 @@ import {
28
30
  getAssetPath,
29
31
  getAssetsDir,
30
32
  lastPageMode
31
- } from "./cli-rtpm04dv.js";
33
+ } from "./cli-x4p99w8d.js";
32
34
  import {
33
35
  BuildError,
34
36
  EXIT_CODES
35
37
  } from "./cli-46ycxe6r.js";
38
+ import {
39
+ countLinkAnnotations,
40
+ getImageResolutions,
41
+ getOpPass,
42
+ getOutlineCount,
43
+ getPageLabels,
44
+ getPageSize,
45
+ getTextPass,
46
+ isLoadable,
47
+ loadPdf,
48
+ pageHasMarks,
49
+ retainPdfCache
50
+ } from "./cli-sth219v1.js";
36
51
  import {
37
52
  INSTALL_HINTS,
38
53
  execCapture,
@@ -106,6 +121,11 @@ function pinnedNpmSpecifier(name, version) {
106
121
  // src/lib/presets.ts
107
122
  var PRESET_IDS = ["dtrpg", "book", "custom"];
108
123
  var DTRPG_PRESET = {
124
+ id: "dtrpg",
125
+ spec: {
126
+ label: "DriveThruRPG — Quick Specifications for Print Books",
127
+ url: "https://help.drivethrupartners.com/hc/en-us/articles/12780800178583-Quick-Specifications-for-Print-Books"
128
+ },
109
129
  defaultTargets: ["dtrpg"],
110
130
  extensions: [],
111
131
  source: {
@@ -117,6 +137,8 @@ var DTRPG_PRESET = {
117
137
  icc: "profiles/CGATS21_CRPC1.icc",
118
138
  stripAnnotations: true,
119
139
  blackText: "k-only",
140
+ transparency: "flatten-page",
141
+ flattenPages: [],
120
142
  rasterDpi: undefined
121
143
  },
122
144
  page: {
@@ -124,7 +146,8 @@ var DTRPG_PRESET = {
124
146
  height: 810,
125
147
  tolerance: 0.5,
126
148
  bleed: 9,
127
- safe: 18
149
+ safe: 36,
150
+ artSafe: 18
128
151
  },
129
152
  ink: {
130
153
  maxTac: 240,
@@ -153,6 +176,10 @@ var DTRPG_PRESET = {
153
176
  "pdf.print.embedded-fonts": {
154
177
  enabled: true,
155
178
  severity: "error"
179
+ },
180
+ "pdf.print.safe-margins": {
181
+ enabled: true,
182
+ severity: "error"
156
183
  }
157
184
  },
158
185
  source: {
@@ -185,6 +212,7 @@ var DTRPG_PRESET = {
185
212
  }
186
213
  };
187
214
  var BOOK_PRESET = {
215
+ id: "book",
188
216
  defaultTargets: [],
189
217
  extensions: [],
190
218
  source: {
@@ -196,6 +224,8 @@ var BOOK_PRESET = {
196
224
  icc: "profiles/CGATS21_CRPC1.icc",
197
225
  stripAnnotations: true,
198
226
  blackText: "icc",
227
+ transparency: "flatten-page",
228
+ flattenPages: [],
199
229
  rasterDpi: undefined
200
230
  },
201
231
  page: {
@@ -203,7 +233,8 @@ var BOOK_PRESET = {
203
233
  height: 648,
204
234
  tolerance: 0.5,
205
235
  bleed: undefined,
206
- safe: 18
236
+ safe: 18,
237
+ artSafe: undefined
207
238
  },
208
239
  ink: {
209
240
  maxTac: 400,
@@ -225,6 +256,10 @@ var BOOK_PRESET = {
225
256
  enabled: true,
226
257
  severity: "error"
227
258
  },
259
+ "pdf.print.safe-margins": {
260
+ enabled: true,
261
+ severity: "warning"
262
+ },
228
263
  "pdf.print.pdfx-markers": false,
229
264
  "pdf.print.pdfx-metadata": false
230
265
  },
@@ -259,6 +294,7 @@ var BOOK_PRESET = {
259
294
  };
260
295
  var CUSTOM_PRESET = {
261
296
  ...BOOK_PRESET,
297
+ id: "custom",
262
298
  page: null,
263
299
  defaultTargets: []
264
300
  };
@@ -304,7 +340,8 @@ var DTRPG_TARGET = {
304
340
  "pdf.structure.qpdf": { enabled: true, severity: "error" },
305
341
  "pdf.print.pdfx-markers": { enabled: true, severity: "error" },
306
342
  "pdf.print.pdfx-metadata": { enabled: true, severity: "error" },
307
- "pdf.print.embedded-fonts": { enabled: true, severity: "error" }
343
+ "pdf.print.embedded-fonts": { enabled: true, severity: "error" },
344
+ "pdf.print.safe-margins": { enabled: true, severity: "error" }
308
345
  },
309
346
  assets: {
310
347
  allowedColorSpaces: [...DTRPG_PRESET.validate.assets.allowedColorSpaces]
@@ -319,7 +356,8 @@ var DTRPG_TARGET = {
319
356
  "pdf.structure.qpdf",
320
357
  "pdf.print.pdfx-markers",
321
358
  "pdf.print.pdfx-metadata",
322
- "pdf.print.embedded-fonts"
359
+ "pdf.print.embedded-fonts",
360
+ "pdf.print.safe-margins"
323
361
  ],
324
362
  requiredTools: ["qpdf", "gs"]
325
363
  };
@@ -334,6 +372,7 @@ var ITCH_TARGET = {
334
372
  "pdf.print.pdfx-markers": false,
335
373
  "pdf.print.pdfx-metadata": false,
336
374
  "pdf.print.ink-coverage": false,
375
+ "pdf.print.safe-margins": false,
337
376
  "asset.image.tac-raster": false
338
377
  },
339
378
  assets: {
@@ -552,7 +591,8 @@ function resolveCustomPage(c, m) {
552
591
  height,
553
592
  tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5,
554
593
  bleed: c.page?.bleed ?? m.page?.bleed,
555
- safe: c.page?.safe ?? m.page?.safe
594
+ safe: c.page?.safe ?? m.page?.safe,
595
+ artSafe: c.page?.artSafe ?? m.page?.artSafe
556
596
  };
557
597
  }
558
598
  function resolveWithPreset(cliOverrides, manifest, preset) {
@@ -585,6 +625,14 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
585
625
  if (lastPage !== undefined && lastPage !== "blank" && lastPage !== "short-by-one") {
586
626
  throw new UsageError(`Manifest field \`print.lastPage\` must be "blank" or "short-by-one", got ${JSON.stringify(lastPage)}.`);
587
627
  }
628
+ const transparency = c.pdfx?.transparency ?? m.pdfx?.transparency;
629
+ if (transparency !== undefined && transparency !== "flatten-page" && transparency !== "preserve-text") {
630
+ throw new UsageError(`Manifest field \`pdfx.transparency\` must be "flatten-page" or "preserve-text", got ${JSON.stringify(transparency)}.`);
631
+ }
632
+ const flattenPages = c.pdfx?.flattenPages ?? m.pdfx?.flattenPages;
633
+ if (flattenPages !== undefined && !(Array.isArray(flattenPages) && flattenPages.every((n) => Number.isInteger(n) && n >= 1))) {
634
+ throw new UsageError(`Manifest field \`pdfx.flattenPages\` must be a list of page numbers (1, 2, …), got ${JSON.stringify(flattenPages)}.`);
635
+ }
588
636
  const rasterDpi = c.pdfx?.rasterDpi ?? m.pdfx?.rasterDpi;
589
637
  if (rasterDpi !== undefined && !(Number.isInteger(rasterDpi) && rasterDpi >= 72 && rasterDpi <= 1200)) {
590
638
  throw new UsageError(`Manifest field \`pdfx.rasterDpi\` must be a whole number from 72 to 1200, got ${JSON.stringify(rasterDpi)}.`);
@@ -597,6 +645,8 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
597
645
  return {
598
646
  title: c.title ?? m.title ?? "Document",
599
647
  authors: c.authors ?? m.authors ?? [],
648
+ preset: preset.id,
649
+ spec: preset.spec,
600
650
  styles: c.styles ?? m.styles,
601
651
  extensions,
602
652
  targets: resolveTargets(c.targets ?? m.targets, preset.defaultTargets),
@@ -4876,346 +4926,6 @@ function inspectionFailed(checkId, message, extra = {}) {
4876
4926
  };
4877
4927
  }
4878
4928
 
4879
- // src/lib/pdf-inspect.ts
4880
- import { stat as stat2, readFile as readFile7 } from "node:fs/promises";
4881
- import { getDocumentProxy } from "unpdf";
4882
- import * as pdfjs from "unpdf/pdfjs";
4883
- function getOps(mod) {
4884
- if ("OPS" in mod && mod.OPS && typeof mod.OPS === "object") {
4885
- return mod.OPS;
4886
- }
4887
- throw new Error("unpdf/pdfjs missing OPS export");
4888
- }
4889
- var OPS = getOps(pdfjs);
4890
- var docCache = new Map;
4891
- var DOC_CACHE_MAX = 8;
4892
- function destroyEntry(entry) {
4893
- entry.doc.then((d) => d.destroy()).catch(() => {});
4894
- }
4895
- var EVICT_DESTROY_GRACE_MS = 60000;
4896
- var gracePending = new Map;
4897
- function destroyEntryAfterGrace(entry) {
4898
- const t = setTimeout(() => {
4899
- gracePending.delete(t);
4900
- destroyEntry(entry);
4901
- }, EVICT_DESTROY_GRACE_MS);
4902
- t.unref?.();
4903
- gracePending.set(t, entry);
4904
- }
4905
- function evictLru() {
4906
- while (docCache.size > DOC_CACHE_MAX) {
4907
- const oldestKey = docCache.keys().next().value;
4908
- if (oldestKey === undefined)
4909
- break;
4910
- const evicted = docCache.get(oldestKey);
4911
- docCache.delete(oldestKey);
4912
- if (evicted)
4913
- destroyEntryAfterGrace(evicted);
4914
- }
4915
- }
4916
- async function loadPdf(path6) {
4917
- let st;
4918
- try {
4919
- st = await stat2(path6);
4920
- } catch {
4921
- return null;
4922
- }
4923
- const hit = docCache.get(path6);
4924
- if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
4925
- docCache.delete(path6);
4926
- docCache.set(path6, hit);
4927
- try {
4928
- return await hit.doc;
4929
- } catch {
4930
- return null;
4931
- }
4932
- }
4933
- if (hit)
4934
- destroyEntry(hit);
4935
- const docPromise = readFile7(path6).then((buf) => getDocumentProxy(new Uint8Array(buf)));
4936
- docCache.delete(path6);
4937
- docCache.set(path6, { mtimeMs: st.mtimeMs, size: st.size, doc: docPromise });
4938
- evictLru();
4939
- try {
4940
- return await docPromise;
4941
- } catch {
4942
- if (docCache.get(path6)?.doc === docPromise)
4943
- docCache.delete(path6);
4944
- return null;
4945
- }
4946
- }
4947
- var activeRetains = 0;
4948
- function retainPdfCache() {
4949
- activeRetains++;
4950
- let released = false;
4951
- return () => {
4952
- if (released)
4953
- return;
4954
- released = true;
4955
- activeRetains--;
4956
- if (activeRetains === 0)
4957
- clearPdfCache();
4958
- };
4959
- }
4960
- function clearPdfCache() {
4961
- for (const [t, entry] of gracePending) {
4962
- clearTimeout(t);
4963
- destroyEntry(entry);
4964
- }
4965
- gracePending.clear();
4966
- for (const entry of docCache.values())
4967
- destroyEntry(entry);
4968
- docCache.clear();
4969
- }
4970
- function getPageSize(page) {
4971
- const vp = page.getViewport({ scale: 1 });
4972
- return { w: vp.width, h: vp.height };
4973
- }
4974
- async function getOutlineCount(doc) {
4975
- try {
4976
- const outline = await doc.getOutline();
4977
- return outline ? outline.length : 0;
4978
- } catch {
4979
- return 0;
4980
- }
4981
- }
4982
- async function getPageLabels(doc) {
4983
- try {
4984
- return await doc.getPageLabels();
4985
- } catch {
4986
- return null;
4987
- }
4988
- }
4989
- async function countLinkAnnotations(doc, stopAtFirst = false) {
4990
- let count = 0;
4991
- for (let i = 1;i <= doc.numPages; i++) {
4992
- try {
4993
- const page = await doc.getPage(i);
4994
- const annots = await page.getAnnotations();
4995
- for (const a of annots) {
4996
- if (a.subtype === "Link") {
4997
- count++;
4998
- if (stopAtFirst)
4999
- return count;
5000
- }
5001
- }
5002
- } catch {}
5003
- }
5004
- return count;
5005
- }
5006
- var textCache = new WeakMap;
5007
- function getTextPass(doc) {
5008
- let p = textCache.get(doc);
5009
- if (!p) {
5010
- p = (async () => {
5011
- const textByPage = [];
5012
- const positions = [];
5013
- const runsByPage = [];
5014
- for (let i = 1;i <= doc.numPages; i++) {
5015
- try {
5016
- const page = await doc.getPage(i);
5017
- const tc = await page.getTextContent();
5018
- let pageText = "";
5019
- const runs = [];
5020
- for (const item of tc.items) {
5021
- const it = item;
5022
- if (typeof it.str !== "string")
5023
- continue;
5024
- pageText += it.str;
5025
- if (it.transform && it.str.trim().length > 0) {
5026
- const x = it.transform[4];
5027
- const y = it.transform[5];
5028
- positions.push({ x, y });
5029
- runs.push({ s: it.str, x, y, w: it.width ?? 0, h: it.height ?? 0 });
5030
- }
5031
- }
5032
- textByPage.push(pageText);
5033
- runsByPage.push(runs);
5034
- } catch {
5035
- textByPage.push("");
5036
- runsByPage.push([]);
5037
- }
5038
- }
5039
- return { textByPage, positions, runsByPage };
5040
- })();
5041
- textCache.set(doc, p);
5042
- }
5043
- return p;
5044
- }
5045
- var opCache = new WeakMap;
5046
- function multiply(m1, m2) {
5047
- return [
5048
- m1[0] * m2[0] + m1[2] * m2[1],
5049
- m1[1] * m2[0] + m1[3] * m2[1],
5050
- m1[0] * m2[2] + m1[2] * m2[3],
5051
- m1[1] * m2[2] + m1[3] * m2[3],
5052
- m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
5053
- m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
5054
- ];
5055
- }
5056
- function fontIsEmbedded(f) {
5057
- if (f.isType3Font)
5058
- return true;
5059
- if (f.missingFile === true)
5060
- return false;
5061
- if (f.isStandardFont === true)
5062
- return false;
5063
- return true;
5064
- }
5065
- function getOpPass(doc) {
5066
- let p = opCache.get(doc);
5067
- if (!p) {
5068
- p = (async () => {
5069
- const imagesByPage = new Map;
5070
- const fonts = [];
5071
- const seenFonts = new Set;
5072
- for (let i = 1;i <= doc.numPages; i++) {
5073
- let page;
5074
- try {
5075
- page = await doc.getPage(i);
5076
- } catch {
5077
- continue;
5078
- }
5079
- let ops;
5080
- try {
5081
- ops = await page.getOperatorList();
5082
- } catch {
5083
- continue;
5084
- }
5085
- let ctm = [1, 0, 0, 1, 0, 0];
5086
- const stack = [];
5087
- const pageImages = [];
5088
- for (let j = 0;j < ops.fnArray.length; j++) {
5089
- const fn = ops.fnArray[j];
5090
- const args = ops.argsArray[j];
5091
- if (fn === OPS.save) {
5092
- stack.push(ctm.slice());
5093
- } else if (fn === OPS.restore) {
5094
- ctm = stack.pop() ?? ctm;
5095
- } else if (fn === OPS.transform) {
5096
- ctm = multiply(ctm, args);
5097
- } else if (fn === OPS.paintImageXObject || fn === OPS.paintImageXObjectRepeat || fn === OPS.paintInlineImageXObject) {
5098
- const name = typeof args[0] === "string" ? args[0] : "(inline)";
5099
- pageImages.push({
5100
- name,
5101
- placedW: Math.hypot(ctm[0], ctm[1]),
5102
- placedH: Math.hypot(ctm[2], ctm[3]),
5103
- x: ctm[4],
5104
- y: ctm[5],
5105
- page: i
5106
- });
5107
- } else if (fn === OPS.setFont) {
5108
- const ref = args[0];
5109
- if (typeof ref === "string" && !seenFonts.has(ref)) {
5110
- seenFonts.add(ref);
5111
- try {
5112
- if (page.commonObjs.has(ref)) {
5113
- const f = page.commonObjs.get(ref);
5114
- fonts.push({
5115
- name: f?.name ?? ref,
5116
- embedded: fontIsEmbedded(f ?? {})
5117
- });
5118
- }
5119
- } catch {}
5120
- }
5121
- }
5122
- }
5123
- if (pageImages.length > 0)
5124
- imagesByPage.set(i, pageImages);
5125
- }
5126
- return { imagesByPage, fonts };
5127
- })();
5128
- opCache.set(doc, p);
5129
- }
5130
- return p;
5131
- }
5132
- function getImagePixelSize(page, name, timeoutMs = 2000) {
5133
- return new Promise((resolve4) => {
5134
- let done = false;
5135
- const finish = (v) => {
5136
- if (!done) {
5137
- done = true;
5138
- resolve4(v);
5139
- }
5140
- };
5141
- const t = setTimeout(() => finish(null), timeoutMs);
5142
- try {
5143
- page.objs.get(name, (obj) => {
5144
- clearTimeout(t);
5145
- finish(obj && obj.width && obj.height ? { width: obj.width, height: obj.height } : null);
5146
- });
5147
- } catch {
5148
- clearTimeout(t);
5149
- finish(null);
5150
- }
5151
- });
5152
- }
5153
- var PAINT_OPS = new Set([
5154
- "stroke",
5155
- "closeStroke",
5156
- "fill",
5157
- "eoFill",
5158
- "fillStroke",
5159
- "eoFillStroke",
5160
- "closeFillStroke",
5161
- "closeEOFillStroke",
5162
- "shadingFill",
5163
- "paintImageXObject",
5164
- "paintInlineImageXObject",
5165
- "paintImageXObjectRepeat",
5166
- "paintImageMaskXObject",
5167
- "paintImageMaskXObjectGroup",
5168
- "paintImageMaskXObjectRepeat",
5169
- "paintSolidColorImageMask",
5170
- "showText",
5171
- "showSpacedText",
5172
- "nextLineShowText",
5173
- "nextLineSetSpacingShowText"
5174
- ].map((name) => OPS[name]).filter((op) => op !== undefined));
5175
- async function pageHasMarks(doc, pageNum) {
5176
- const ops = await (await doc.getPage(pageNum)).getOperatorList();
5177
- return ops.fnArray.some((fn, i) => fn === OPS.constructPath ? PAINT_OPS.has(ops.argsArray[i][0]) : PAINT_OPS.has(fn));
5178
- }
5179
- async function getImageResolutions(doc) {
5180
- const { imagesByPage } = await getOpPass(doc);
5181
- const out = [];
5182
- for (const [pageNum, imgs] of imagesByPage) {
5183
- let page;
5184
- try {
5185
- page = await doc.getPage(pageNum);
5186
- } catch {
5187
- continue;
5188
- }
5189
- for (const img of imgs) {
5190
- if (img.name === "(inline)")
5191
- continue;
5192
- const px = await getImagePixelSize(page, img.name);
5193
- if (!px)
5194
- continue;
5195
- const placedWin = img.placedW / 72;
5196
- const placedHin = img.placedH / 72;
5197
- if (placedWin <= 0 || placedHin <= 0)
5198
- continue;
5199
- out.push({
5200
- page: pageNum,
5201
- xDpi: px.width / placedWin,
5202
- yDpi: px.height / placedHin
5203
- });
5204
- }
5205
- }
5206
- return out;
5207
- }
5208
- async function isLoadable(doc) {
5209
- try {
5210
- for (let i = 1;i <= doc.numPages; i++) {
5211
- await doc.getPage(i);
5212
- }
5213
- return true;
5214
- } catch {
5215
- return false;
5216
- }
5217
- }
5218
-
5219
4929
  // src/checks/pdf/qpdf-structure.ts
5220
4930
  var check = {
5221
4931
  id: "pdf.structure.qpdf",
@@ -5272,6 +4982,79 @@ var check2 = {
5272
4982
  };
5273
4983
  registerCheck(check2);
5274
4984
 
4985
+ // src/checks/pdf/safe-margins.ts
4986
+ var TOLERANCE_PT = 0.5;
4987
+ var ABOVE_BASELINE = 0.8;
4988
+ var BELOW_BASELINE = 0.2;
4989
+ var inches = (pt) => `${+(pt / 72).toFixed(2)}in`;
4990
+ var check3 = {
4991
+ id: "pdf.print.safe-margins",
4992
+ name: "Safe margins",
4993
+ description: "Fails when text sits closer to the trim than `page.safe` (DriveThruRPG: 0.5in on every edge). Text only: art is judged in the preview's safe-area overlay, not here. Skipped when `page.safe` is not set",
4994
+ category: "pdf",
4995
+ phase: "post-build",
4996
+ async run(ctx) {
4997
+ const { width, height, bleed = 0, safe = 0 } = ctx.config.page;
4998
+ if (!ctx.pdfPath || !safe)
4999
+ return [];
5000
+ const doc = await loadPdf(ctx.pdfPath);
5001
+ if (!doc) {
5002
+ return [inspectionFailed(check3.id, "Could not parse the PDF's text positions.", { file: ctx.pdfPath })];
5003
+ }
5004
+ const rule = ctx.config.spec?.label ?? "page.safe";
5005
+ const results = [];
5006
+ try {
5007
+ for (let pageNum = 1;pageNum <= doc.numPages; pageNum++) {
5008
+ const recto = pageNum % 2 === 1;
5009
+ const trim = {
5010
+ left: recto ? 0 : bleed,
5011
+ right: width - (recto ? bleed : 0),
5012
+ bottom: bleed,
5013
+ top: height - bleed
5014
+ };
5015
+ const { items } = await (await doc.getPage(pageNum)).getTextContent();
5016
+ for (const item of items) {
5017
+ if (!("str" in item) || !item.str.trim())
5018
+ continue;
5019
+ const [a, b, c, d, e, f] = item.transform;
5020
+ const along = item.width / Math.hypot(a, b);
5021
+ const xs = [];
5022
+ const ys = [];
5023
+ for (const s of [0, 1]) {
5024
+ for (const u of [-BELOW_BASELINE, ABOVE_BASELINE]) {
5025
+ xs.push(e + s * along * a + u * c);
5026
+ ys.push(f + s * along * b + u * d);
5027
+ }
5028
+ }
5029
+ const gaps = [
5030
+ [recto ? "binding" : "outside", Math.min(...xs) - trim.left],
5031
+ [recto ? "outside" : "binding", trim.right - Math.max(...xs)],
5032
+ ["bottom", Math.min(...ys) - trim.bottom],
5033
+ ["top", trim.top - Math.max(...ys)]
5034
+ ];
5035
+ const [edge, gap] = gaps.reduce((worst, g) => g[1] < worst[1] ? g : worst);
5036
+ const shortfall = safe - gap;
5037
+ if (shortfall <= TOLERANCE_PT)
5038
+ continue;
5039
+ const text = item.str.trim();
5040
+ results.push(finding(check3.id, {
5041
+ severity: "error",
5042
+ message: `Page ${pageNum}: "${text.slice(0, 40)}${text.length > 40 ? "…" : ""}" is ${inches(shortfall)} too close to the ${edge} edge.`,
5043
+ file: ctx.pdfPath,
5044
+ detail: `${rule}: text must stay at least ${inches(safe)} inside the trim on every edge.`,
5045
+ code: "text-outside-safe-margin",
5046
+ data: { page: pageNum, edge, shortfallPt: +shortfall.toFixed(2) }
5047
+ }));
5048
+ }
5049
+ }
5050
+ } catch {
5051
+ return [inspectionFailed(check3.id, "Could not read the PDF's text positions.", { file: ctx.pdfPath })];
5052
+ }
5053
+ return results;
5054
+ }
5055
+ };
5056
+ registerCheck(check3);
5057
+
5275
5058
  // src/checks/pdf/pdfx-structure.ts
5276
5059
  function isObject(value) {
5277
5060
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -5380,7 +5163,7 @@ function getPdfxMetadataIssues(objects, flavor) {
5380
5163
  }
5381
5164
 
5382
5165
  // src/checks/pdf/pdfx-markers.ts
5383
- var check3 = {
5166
+ var check4 = {
5384
5167
  id: "pdf.print.pdfx-markers",
5385
5168
  name: "PDF/X Markers",
5386
5169
  description: "Checks PDF/X OutputIntent structure via qpdf JSON",
@@ -5399,27 +5182,27 @@ var check3 = {
5399
5182
  const objects = parseQpdfObjectsJson(stdout);
5400
5183
  if (!objects) {
5401
5184
  return [
5402
- inspectionFailed(check3.id, "Unable to parse qpdf JSON output for PDF/X checks.", { file: ctx.pdfPath })
5185
+ inspectionFailed(check4.id, "Unable to parse qpdf JSON output for PDF/X checks.", { file: ctx.pdfPath })
5403
5186
  ];
5404
5187
  }
5405
- return getPdfxOutputIntentIssues(objects).map((message) => finding(check3.id, {
5188
+ return getPdfxOutputIntentIssues(objects).map((message) => finding(check4.id, {
5406
5189
  severity: "error",
5407
5190
  message,
5408
5191
  file: ctx.pdfPath
5409
5192
  }));
5410
5193
  } catch {
5411
5194
  return [
5412
- inspectionFailed(check3.id, "Failed to inspect PDF objects with qpdf for PDF/X markers.", { file: ctx.pdfPath })
5195
+ inspectionFailed(check4.id, "Failed to inspect PDF objects with qpdf for PDF/X markers.", { file: ctx.pdfPath })
5413
5196
  ];
5414
5197
  }
5415
5198
  }
5416
5199
  };
5417
- registerCheck(check3);
5200
+ registerCheck(check4);
5418
5201
 
5419
5202
  // src/lib/pdf-parse.ts
5420
- import { readFile as readFile8 } from "node:fs/promises";
5203
+ import { readFile as readFile7 } from "node:fs/promises";
5421
5204
  async function readPdfBytes(pdfPath) {
5422
- return readFile8(pdfPath, "latin1");
5205
+ return readFile7(pdfPath, "latin1");
5423
5206
  }
5424
5207
  function parseInkCov(out) {
5425
5208
  const lines = out.split(/\r?\n/).filter((l) => l.includes("CMYK"));
@@ -5467,7 +5250,7 @@ async function getPerPageInkCoverage(pdfPath) {
5467
5250
  }
5468
5251
 
5469
5252
  // src/checks/pdf/color-spaces.ts
5470
- var check4 = {
5253
+ var check5 = {
5471
5254
  id: "pdf.print.color-spaces",
5472
5255
  name: "Color Spaces",
5473
5256
  description: "Checks for forbidden color spaces (DeviceRGB, Lab, Separation, DeviceN)",
@@ -5480,7 +5263,7 @@ var check4 = {
5480
5263
  const results = [];
5481
5264
  if (/\/DeviceRGB/.test(bytes)) {
5482
5265
  results.push({
5483
- checkId: check4.id,
5266
+ checkId: check5.id,
5484
5267
  severity: "error",
5485
5268
  message: "DeviceRGB found (interior must be CMYK or grayscale only).",
5486
5269
  file: ctx.pdfPath
@@ -5488,7 +5271,7 @@ var check4 = {
5488
5271
  }
5489
5272
  if (/\/Lab\b/.test(bytes)) {
5490
5273
  results.push({
5491
- checkId: check4.id,
5274
+ checkId: check5.id,
5492
5275
  severity: "error",
5493
5276
  message: "Lab color space found (not allowed).",
5494
5277
  file: ctx.pdfPath
@@ -5496,7 +5279,7 @@ var check4 = {
5496
5279
  }
5497
5280
  if (/\/Separation/.test(bytes)) {
5498
5281
  results.push({
5499
- checkId: check4.id,
5282
+ checkId: check5.id,
5500
5283
  severity: "error",
5501
5284
  message: "Spot color (Separation) found (not allowed).",
5502
5285
  file: ctx.pdfPath
@@ -5504,7 +5287,7 @@ var check4 = {
5504
5287
  }
5505
5288
  if (/\/DeviceN/.test(bytes)) {
5506
5289
  results.push({
5507
- checkId: check4.id,
5290
+ checkId: check5.id,
5508
5291
  severity: "error",
5509
5292
  message: "Spot color (DeviceN) found (not allowed).",
5510
5293
  file: ctx.pdfPath
@@ -5513,10 +5296,10 @@ var check4 = {
5513
5296
  return results;
5514
5297
  }
5515
5298
  };
5516
- registerCheck(check4);
5299
+ registerCheck(check5);
5517
5300
 
5518
5301
  // src/checks/pdf/embedded-fonts.ts
5519
- var check5 = {
5302
+ var check6 = {
5520
5303
  id: "pdf.print.embedded-fonts",
5521
5304
  name: "Embedded Fonts",
5522
5305
  description: "Verifies all fonts in the PDF are embedded",
@@ -5532,7 +5315,7 @@ var check5 = {
5532
5315
  if (fonts.length === 0) {
5533
5316
  return [
5534
5317
  {
5535
- checkId: check5.id,
5318
+ checkId: check6.id,
5536
5319
  severity: "warning",
5537
5320
  message: "No fonts detected (unexpected).",
5538
5321
  file: ctx.pdfPath,
@@ -5545,7 +5328,7 @@ var check5 = {
5545
5328
  const names = notEmbedded.map((f) => f.name);
5546
5329
  return [
5547
5330
  {
5548
- checkId: check5.id,
5331
+ checkId: check6.id,
5549
5332
  severity: "error",
5550
5333
  message: `Not all fonts are embedded (${names.join(", ")}). Check @font-face and Chromium output.`,
5551
5334
  file: ctx.pdfPath,
@@ -5557,10 +5340,10 @@ var check5 = {
5557
5340
  return [];
5558
5341
  }
5559
5342
  };
5560
- registerCheck(check5);
5343
+ registerCheck(check6);
5561
5344
 
5562
5345
  // src/checks/pdf/ink-coverage.ts
5563
- var check6 = {
5346
+ var check7 = {
5564
5347
  id: "pdf.print.ink-coverage",
5565
5348
  name: "Ink Coverage (TAC)",
5566
5349
  description: "Checks total area coverage (TAC) against maximum ink limits",
@@ -5574,7 +5357,7 @@ var check6 = {
5574
5357
  if (!inkResult.ok) {
5575
5358
  return [
5576
5359
  {
5577
- checkId: check6.id,
5360
+ checkId: check7.id,
5578
5361
  severity: "warning",
5579
5362
  message: "Ink coverage could not be measured",
5580
5363
  file: ctx.pdfPath,
@@ -5599,7 +5382,7 @@ var check6 = {
5599
5382
  }
5600
5383
  return [
5601
5384
  {
5602
- checkId: check6.id,
5385
+ checkId: check7.id,
5603
5386
  severity: "warning",
5604
5387
  message: `Total ink coverage too high on ${offending.length} page(s) (max ${maxTac.toFixed(1)}%, recommended <=${ctx.config.ink.maxTac}%)`,
5605
5388
  file: ctx.pdfPath,
@@ -5623,10 +5406,10 @@ var check6 = {
5623
5406
  ];
5624
5407
  }
5625
5408
  };
5626
- registerCheck(check6);
5409
+ registerCheck(check7);
5627
5410
 
5628
5411
  // src/checks/pdf/rasterized-pages.ts
5629
- var check7 = {
5412
+ var check8 = {
5630
5413
  id: "pdf.print.rasterized-pages",
5631
5414
  name: "Rasterized Pages",
5632
5415
  description: "Detects pages that appear to be fully rasterized (CSS filters, blend modes, transparency)",
@@ -5666,7 +5449,7 @@ var check7 = {
5666
5449
  rasterizedPages.sort((a, b) => a - b);
5667
5450
  return [
5668
5451
  {
5669
- checkId: check7.id,
5452
+ checkId: check8.id,
5670
5453
  severity: "warning",
5671
5454
  message: `Possible rasterized pages detected: ${rasterizedPages.join(", ")}`,
5672
5455
  file: ctx.pdfPath,
@@ -5677,11 +5460,11 @@ var check7 = {
5677
5460
  ];
5678
5461
  }
5679
5462
  };
5680
- registerCheck(check7);
5463
+ registerCheck(check8);
5681
5464
 
5682
5465
  // src/checks/pdf/black-text.ts
5683
- import { readFile as readFile9 } from "node:fs/promises";
5684
- var check8 = {
5466
+ import { readFile as readFile8 } from "node:fs/promises";
5467
+ var check9 = {
5685
5468
  id: "pdf.print.black-text",
5686
5469
  name: "Black Text",
5687
5470
  description: "Warns when text at or below 24pt is painted in a dark four-colour mix (black, or a near-black colour such as a dark brown)",
@@ -5693,11 +5476,11 @@ var check8 = {
5693
5476
  return [];
5694
5477
  try {
5695
5478
  const { findRichBlackText, LARGE_TEXT_PT } = await import("./black-text-gray-7dz61456.js");
5696
- const found = await findRichBlackText(await readFile9(ctx.pdfPath));
5479
+ const found = await findRichBlackText(await readFile8(ctx.pdfPath));
5697
5480
  if (found.runs === 0)
5698
5481
  return [];
5699
5482
  return [
5700
- finding(check8.id, {
5483
+ finding(check9.id, {
5701
5484
  severity: "warning",
5702
5485
  message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
5703
5486
  file: ctx.pdfPath,
@@ -5708,17 +5491,17 @@ var check8 = {
5708
5491
  ];
5709
5492
  } catch {
5710
5493
  return [
5711
- inspectionFailed(check8.id, "Could not inspect the PDF's text colours.", {
5494
+ inspectionFailed(check9.id, "Could not inspect the PDF's text colours.", {
5712
5495
  file: ctx.pdfPath
5713
5496
  })
5714
5497
  ];
5715
5498
  }
5716
5499
  }
5717
5500
  };
5718
- registerCheck(check8);
5501
+ registerCheck(check9);
5719
5502
 
5720
5503
  // src/checks/pdf/signature.ts
5721
- var check9 = {
5504
+ var check10 = {
5722
5505
  id: "pdf.print.signature",
5723
5506
  name: "Signature",
5724
5507
  description: "Fails when the interior's page count does not fit the print signature (a multiple of it ending on a blank page, or one short of a multiple)",
@@ -5730,7 +5513,7 @@ var check9 = {
5730
5513
  return [];
5731
5514
  const doc = await loadPdf(ctx.pdfPath);
5732
5515
  if (!doc) {
5733
- return [inspectionFailed(check9.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
5516
+ return [inspectionFailed(check10.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
5734
5517
  }
5735
5518
  const size = ctx.config.print.signature;
5736
5519
  const pages = doc.numPages;
@@ -5739,7 +5522,7 @@ var check9 = {
5739
5522
  const data = { pages, signature: size };
5740
5523
  if (pages % size !== 0) {
5741
5524
  return [
5742
- finding(check9.id, {
5525
+ finding(check10.id, {
5743
5526
  severity: "error",
5744
5527
  message: `Page count ${pages} does not fit a signature of ${size}: it must be a multiple of ${size}, or one short of one.`,
5745
5528
  file: ctx.pdfPath,
@@ -5755,10 +5538,10 @@ var check9 = {
5755
5538
  if (!await pageHasMarks(doc, pages))
5756
5539
  return [];
5757
5540
  } catch {
5758
- return [inspectionFailed(check9.id, "Could not read the last page.", { file: ctx.pdfPath })];
5541
+ return [inspectionFailed(check10.id, "Could not read the last page.", { file: ctx.pdfPath })];
5759
5542
  }
5760
5543
  return [
5761
- finding(check9.id, {
5544
+ finding(check10.id, {
5762
5545
  severity: "error",
5763
5546
  message: `The last page (${pages}) has content, but the printer needs it blank for its own information.`,
5764
5547
  file: ctx.pdfPath,
@@ -5769,10 +5552,10 @@ var check9 = {
5769
5552
  ];
5770
5553
  }
5771
5554
  };
5772
- registerCheck(check9);
5555
+ registerCheck(check10);
5773
5556
 
5774
5557
  // src/checks/pdf/bookmarks.ts
5775
- var check10 = {
5558
+ var check11 = {
5776
5559
  id: "pdf.nav.bookmarks",
5777
5560
  name: "Bookmarks",
5778
5561
  description: "Checks for PDF outline (bookmarks) tree",
@@ -5786,14 +5569,14 @@ var check10 = {
5786
5569
  const doc = await loadPdf(ctx.pdfPath);
5787
5570
  if (!doc) {
5788
5571
  return [
5789
- inspectionFailed(check10.id, "Could not inspect PDF for bookmarks.", {
5572
+ inspectionFailed(check11.id, "Could not inspect PDF for bookmarks.", {
5790
5573
  file: ctx.pdfPath
5791
5574
  })
5792
5575
  ];
5793
5576
  }
5794
5577
  if (await getOutlineCount(doc) === 0) {
5795
5578
  return [
5796
- finding(check10.id, {
5579
+ finding(check11.id, {
5797
5580
  severity: "warning",
5798
5581
  message: "PDF does not contain bookmarks (outline tree).",
5799
5582
  file: ctx.pdfPath
@@ -5803,10 +5586,10 @@ var check10 = {
5803
5586
  return [];
5804
5587
  }
5805
5588
  };
5806
- registerCheck(check10);
5589
+ registerCheck(check11);
5807
5590
 
5808
5591
  // src/checks/pdf/toc-links.ts
5809
- var check11 = {
5592
+ var check12 = {
5810
5593
  id: "pdf.nav.toc-links",
5811
5594
  name: "TOC Links",
5812
5595
  description: "Detects link annotations on TOC pages",
@@ -5821,7 +5604,7 @@ var check11 = {
5821
5604
  if (!doc) {
5822
5605
  return [
5823
5606
  {
5824
- checkId: check11.id,
5607
+ checkId: check12.id,
5825
5608
  severity: "warning",
5826
5609
  message: "Could not inspect PDF for TOC links.",
5827
5610
  file: ctx.pdfPath
@@ -5832,7 +5615,7 @@ var check11 = {
5832
5615
  if (!hasLinks) {
5833
5616
  return [
5834
5617
  {
5835
- checkId: check11.id,
5618
+ checkId: check12.id,
5836
5619
  severity: "warning",
5837
5620
  message: "No link annotations found in PDF (expected for TOC).",
5838
5621
  file: ctx.pdfPath
@@ -5842,10 +5625,10 @@ var check11 = {
5842
5625
  return [];
5843
5626
  }
5844
5627
  };
5845
- registerCheck(check11);
5628
+ registerCheck(check12);
5846
5629
 
5847
5630
  // src/checks/pdf/cross-refs.ts
5848
- var check12 = {
5631
+ var check13 = {
5849
5632
  id: "pdf.nav.cross-refs",
5850
5633
  name: "Cross References",
5851
5634
  description: "Counts and verifies internal link annotations",
@@ -5858,7 +5641,7 @@ var check12 = {
5858
5641
  if (!doc) {
5859
5642
  return [
5860
5643
  {
5861
- checkId: check12.id,
5644
+ checkId: check13.id,
5862
5645
  severity: "info",
5863
5646
  message: "Could not inspect PDF for cross-references.",
5864
5647
  file: ctx.pdfPath
@@ -5868,7 +5651,7 @@ var check12 = {
5868
5651
  const linkCount = await countLinkAnnotations(doc);
5869
5652
  return [
5870
5653
  {
5871
- checkId: check12.id,
5654
+ checkId: check13.id,
5872
5655
  severity: "info",
5873
5656
  message: `PDF contains ${linkCount} internal link annotation${linkCount !== 1 ? "s" : ""}.`,
5874
5657
  file: ctx.pdfPath
@@ -5876,10 +5659,10 @@ var check12 = {
5876
5659
  ];
5877
5660
  }
5878
5661
  };
5879
- registerCheck(check12);
5662
+ registerCheck(check13);
5880
5663
 
5881
5664
  // src/checks/pdf/page-labels.ts
5882
- var check13 = {
5665
+ var check14 = {
5883
5666
  id: "pdf.nav.page-labels",
5884
5667
  name: "Page Labels",
5885
5668
  description: "Verifies PDF page labels / folio numbering",
@@ -5894,7 +5677,7 @@ var check13 = {
5894
5677
  const labels = await getPageLabels(doc);
5895
5678
  return [
5896
5679
  {
5897
- checkId: check13.id,
5680
+ checkId: check14.id,
5898
5681
  severity: "info",
5899
5682
  message: labels && labels.length > 0 ? "PDF contains page labels." : "PDF does not contain explicit page labels.",
5900
5683
  file: ctx.pdfPath
@@ -5902,10 +5685,10 @@ var check13 = {
5902
5685
  ];
5903
5686
  }
5904
5687
  };
5905
- registerCheck(check13);
5688
+ registerCheck(check14);
5906
5689
 
5907
5690
  // src/checks/pdf/image-resolution.ts
5908
- var check14 = {
5691
+ var check15 = {
5909
5692
  id: "pdf.print.image-resolution",
5910
5693
  name: "Image Resolution",
5911
5694
  description: "Checks DPI of images embedded in the PDF",
@@ -5932,7 +5715,7 @@ var check14 = {
5932
5715
  lowResPages.sort((a, b) => a - b);
5933
5716
  return [
5934
5717
  {
5935
- checkId: check14.id,
5718
+ checkId: check15.id,
5936
5719
  severity: "warning",
5937
5720
  message: `Low-resolution images (below ${minDpi} DPI) found on pages: ${lowResPages.join(", ")}`,
5938
5721
  file: ctx.pdfPath
@@ -5942,10 +5725,10 @@ var check14 = {
5942
5725
  return [];
5943
5726
  }
5944
5727
  };
5945
- registerCheck(check14);
5728
+ registerCheck(check15);
5946
5729
 
5947
5730
  // src/checks/pdf/transparency.ts
5948
- var check15 = {
5731
+ var check16 = {
5949
5732
  id: "pdf.print.transparency",
5950
5733
  name: "Transparency",
5951
5734
  description: "Checks for transparency markers (/Transparency, /SMask, /BM) in the PDF",
@@ -5967,7 +5750,7 @@ var check15 = {
5967
5750
  if (found.length > 0) {
5968
5751
  return [
5969
5752
  {
5970
- checkId: check15.id,
5753
+ checkId: check16.id,
5971
5754
  severity: "warning",
5972
5755
  message: `Transparency detected in PDF: ${found.join(", ")}. This may cause issues with some printers.`,
5973
5756
  file: ctx.pdfPath
@@ -5977,7 +5760,7 @@ var check15 = {
5977
5760
  return [];
5978
5761
  }
5979
5762
  };
5980
- registerCheck(check15);
5763
+ registerCheck(check16);
5981
5764
 
5982
5765
  // src/checks/pdf/bleed.ts
5983
5766
  function findBox(bytes, box) {
@@ -5987,7 +5770,7 @@ function findBox(bytes, box) {
5987
5770
  return null;
5988
5771
  return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
5989
5772
  }
5990
- var check16 = {
5773
+ var check17 = {
5991
5774
  id: "pdf.print.bleed",
5992
5775
  name: "Bleed Box",
5993
5776
  description: "Compares MediaBox vs TrimBox/BleedBox to verify bleed area",
@@ -6001,7 +5784,7 @@ var check16 = {
6001
5784
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
6002
5785
  if (!bytes) {
6003
5786
  return [
6004
- inspectionFailed(check16.id, "Could not inspect PDF for bleed boxes.", {
5787
+ inspectionFailed(check17.id, "Could not inspect PDF for bleed boxes.", {
6005
5788
  file: ctx.pdfPath
6006
5789
  })
6007
5790
  ];
@@ -6011,14 +5794,14 @@ var check16 = {
6011
5794
  const bleed = findBox(bytes, "BleedBox");
6012
5795
  if (!media) {
6013
5796
  return [
6014
- inspectionFailed(check16.id, "Could not parse MediaBox from PDF.", {
5797
+ inspectionFailed(check17.id, "Could not parse MediaBox from PDF.", {
6015
5798
  file: ctx.pdfPath
6016
5799
  })
6017
5800
  ];
6018
5801
  }
6019
5802
  if (!trim && !bleed) {
6020
5803
  return [
6021
- finding(check16.id, {
5804
+ finding(check17.id, {
6022
5805
  severity: "warning",
6023
5806
  message: "No TrimBox or BleedBox found. Bleed area cannot be verified.",
6024
5807
  file: ctx.pdfPath
@@ -6031,7 +5814,7 @@ var check16 = {
6031
5814
  const bleedSize = ctx.config.validate.pdf.bleedSize;
6032
5815
  if (mediaW - trimW < bleedSize * 2 * 0.9) {
6033
5816
  return [
6034
- finding(check16.id, {
5817
+ finding(check17.id, {
6035
5818
  severity: "warning",
6036
5819
  message: `Bleed area appears insufficient. Expected at least ${bleedSize}pt on each side.`,
6037
5820
  file: ctx.pdfPath
@@ -6042,10 +5825,10 @@ var check16 = {
6042
5825
  return [];
6043
5826
  }
6044
5827
  };
6045
- registerCheck(check16);
5828
+ registerCheck(check17);
6046
5829
 
6047
5830
  // src/checks/pdf/pdfx-metadata.ts
6048
- var check17 = {
5831
+ var check18 = {
6049
5832
  id: "pdf.print.pdfx-metadata",
6050
5833
  name: "PDF/X Metadata",
6051
5834
  description: "Verifies PDF/X DOCINFO metadata via qpdf JSON",
@@ -6064,25 +5847,25 @@ var check17 = {
6064
5847
  const objects = parseQpdfObjectsJson(stdout);
6065
5848
  if (!objects) {
6066
5849
  return [
6067
- inspectionFailed(check17.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
5850
+ inspectionFailed(check18.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
6068
5851
  ];
6069
5852
  }
6070
- return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check17.id, {
5853
+ return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check18.id, {
6071
5854
  severity: "error",
6072
5855
  message,
6073
5856
  file: ctx.pdfPath
6074
5857
  }));
6075
5858
  } catch {
6076
5859
  return [
6077
- inspectionFailed(check17.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
5860
+ inspectionFailed(check18.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
6078
5861
  ];
6079
5862
  }
6080
5863
  }
6081
5864
  };
6082
- registerCheck(check17);
5865
+ registerCheck(check18);
6083
5866
 
6084
5867
  // src/checks/source/markdownlint.ts
6085
- import { readFile as readFile10 } from "node:fs/promises";
5868
+ import { readFile as readFile9 } from "node:fs/promises";
6086
5869
  import { lint } from "markdownlint/sync";
6087
5870
  import { parse as parseYaml2 } from "yaml";
6088
5871
 
@@ -6115,7 +5898,7 @@ function stripJsonComments(s) {
6115
5898
  return s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
6116
5899
  }
6117
5900
  async function loadConfig(configPath) {
6118
- const raw = await readFile10(configPath, "utf8");
5901
+ const raw = await readFile9(configPath, "utf8");
6119
5902
  let parsed;
6120
5903
  if (configPath.endsWith(".json")) {
6121
5904
  parsed = JSON.parse(raw);
@@ -6134,7 +5917,7 @@ async function loadConfig(configPath) {
6134
5917
  return {};
6135
5918
  }
6136
5919
  var MARKDOWNLINT_CHECK_ID = "source.markdownlint";
6137
- var check18 = {
5920
+ var check19 = {
6138
5921
  id: MARKDOWNLINT_CHECK_ID,
6139
5922
  name: "Markdownlint",
6140
5923
  description: "Runs markdownlint with project config to validate Markdown files",
@@ -6159,7 +5942,7 @@ var check18 = {
6159
5942
  continue;
6160
5943
  for (const v of violations) {
6161
5944
  out.push({
6162
- checkId: check18.id,
5945
+ checkId: check19.id,
6163
5946
  severity: "warning",
6164
5947
  message: `${v.ruleDescription} (${v.ruleNames.join("/")})`,
6165
5948
  file,
@@ -6172,15 +5955,15 @@ var check18 = {
6172
5955
  return out;
6173
5956
  }
6174
5957
  };
6175
- registerCheck(check18);
5958
+ registerCheck(check19);
6176
5959
 
6177
5960
  // src/checks/source/htmlhint.ts
6178
5961
  import { existsSync as existsSync7 } from "node:fs";
6179
- import { readFile as readFile11 } from "node:fs/promises";
5962
+ import { readFile as readFile10 } from "node:fs/promises";
6180
5963
  import htmlhint from "htmlhint";
6181
5964
  var { HTMLHint } = htmlhint;
6182
5965
  var CONFIG_NAMES2 = [".htmlhintrc"];
6183
- var check19 = {
5966
+ var check20 = {
6184
5967
  id: "source.htmlhint",
6185
5968
  name: "HTMLHint",
6186
5969
  description: "Runs htmlhint with project config to validate generated HTML",
@@ -6196,11 +5979,11 @@ var check19 = {
6196
5979
  const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
6197
5980
  if (!resolvedConfig && !configPath)
6198
5981
  return [];
6199
- const ruleset = resolvedConfig ? JSON.parse(await readFile11(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
6200
- const html = await readFile11(ctx.htmlPath, "utf8");
5982
+ const ruleset = resolvedConfig ? JSON.parse(await readFile10(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
5983
+ const html = await readFile10(ctx.htmlPath, "utf8");
6201
5984
  const messages = HTMLHint.verify(html, ruleset);
6202
5985
  return messages.map((m) => ({
6203
- checkId: check19.id,
5986
+ checkId: check20.id,
6204
5987
  severity: m.type === "error" ? "error" : "warning",
6205
5988
  message: `${m.rule.id}: ${m.message}`,
6206
5989
  file: ctx.htmlPath,
@@ -6209,10 +5992,10 @@ var check19 = {
6209
5992
  }));
6210
5993
  }
6211
5994
  };
6212
- registerCheck(check19);
5995
+ registerCheck(check20);
6213
5996
 
6214
5997
  // src/checks/source/stylelint.ts
6215
- import { readFile as readFile12 } from "node:fs/promises";
5998
+ import { readFile as readFile11 } from "node:fs/promises";
6216
5999
 
6217
6000
  // src/lib/printsafe.ts
6218
6001
  import postcss from "postcss";
@@ -6554,7 +6337,7 @@ function checkCssRaw(css, from) {
6554
6337
  warnings.push({
6555
6338
  rule: ruleRiskyProps,
6556
6339
  severity: "warning",
6557
- message: `"${decl.prop}: ${decl.value}" prints blank in PDF/X exports (--format pdfx): Chromium emits repeating-*-gradient() and conic-gradient() in a form Ghostscript cannot convert. Plain PDF output is unaffected. Use linear-gradient() or radial-gradient() instead; for 0deg/90deg stripes write "linear-gradient(90deg, #b33 0 6px, #c8c5bf 6px 10px)" with "background-size: 10px 100%", and for anything else a small tiled image.`,
6340
+ message: `"${decl.prop}: ${decl.value}" is at risk in PDF/X exports (--format pdfx): Chromium emits repeating-*-gradient() and conic-gradient() in a form Ghostscript has been seen to paint as nothing or as irregular stripes, so it can print blank or distorted, unless the page also uses transparency and Ghostscript flattens the whole page to an image, which keeps the gradient but loses that page's live text. Plain PDF output is unaffected. Use linear-gradient() or radial-gradient() instead; for 0deg/90deg stripes write "linear-gradient(90deg, #b33 0 6px, #c8c5bf 6px 10px)" with "background-size: 10px 100%", and for anything else a small tiled image.`,
6558
6341
  ...nodeLoc(decl)
6559
6342
  });
6560
6343
  } else if (isUnpairedFitContentWidth(decl)) {
@@ -6602,7 +6385,7 @@ function checkCssRaw(css, from) {
6602
6385
  }
6603
6386
 
6604
6387
  // src/checks/source/stylelint.ts
6605
- var check20 = {
6388
+ var check21 = {
6606
6389
  id: "source.stylelint",
6607
6390
  name: "Print-safety CSS",
6608
6391
  description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
@@ -6619,10 +6402,10 @@ var check20 = {
6619
6402
  for (const file of files) {
6620
6403
  let css;
6621
6404
  try {
6622
- css = await readFile12(file, "utf8");
6405
+ css = await readFile11(file, "utf8");
6623
6406
  } catch (err) {
6624
6407
  results.push({
6625
- checkId: check20.id,
6408
+ checkId: check21.id,
6626
6409
  severity: "error",
6627
6410
  message: `Cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`,
6628
6411
  file
@@ -6631,7 +6414,7 @@ var check20 = {
6631
6414
  }
6632
6415
  for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
6633
6416
  results.push({
6634
- checkId: check20.id,
6417
+ checkId: check21.id,
6635
6418
  severity: w.severity,
6636
6419
  message: `${w.rule}: ${w.message}`,
6637
6420
  file,
@@ -6643,10 +6426,10 @@ var check20 = {
6643
6426
  return results;
6644
6427
  }
6645
6428
  };
6646
- registerCheck(check20);
6429
+ registerCheck(check21);
6647
6430
 
6648
6431
  // src/checks/source/css-ownership.ts
6649
- import { readFile as readFile13 } from "node:fs/promises";
6432
+ import { readFile as readFile12 } from "node:fs/promises";
6650
6433
 
6651
6434
  // src/lib/style-resolver.ts
6652
6435
  import { existsSync as existsSync8 } from "node:fs";
@@ -6714,7 +6497,7 @@ import postcss2 from "postcss";
6714
6497
  import { parse as parseYaml3 } from "yaml";
6715
6498
  var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
6716
6499
  async function loadContract(configPath) {
6717
- const raw = await readFile13(configPath, "utf8");
6500
+ const raw = await readFile12(configPath, "utf8");
6718
6501
  const parsed = parseYaml3(raw);
6719
6502
  if (parsed && typeof parsed === "object")
6720
6503
  return parsed;
@@ -6827,7 +6610,7 @@ function checkOwnership(root, file, maps) {
6827
6610
  });
6828
6611
  return out;
6829
6612
  }
6830
- var check21 = {
6613
+ var check22 = {
6831
6614
  id: "source.css-ownership",
6832
6615
  name: "CSS ownership contract",
6833
6616
  description: "Checks an optional per-project CSS ownership contract (.gutterpress/css-contract.yaml): which file may declare which properties/at-rules, and which selectors must never appear unscoped.",
@@ -6850,7 +6633,7 @@ var check21 = {
6850
6633
  } catch (err) {
6851
6634
  return [
6852
6635
  {
6853
- checkId: check21.id,
6636
+ checkId: check22.id,
6854
6637
  severity: "error",
6855
6638
  message: `Failed to parse CSS ownership contract "${configPath}": ${err instanceof Error ? err.message : String(err)}`,
6856
6639
  file: configPath
@@ -6865,7 +6648,7 @@ var check21 = {
6865
6648
  continue;
6866
6649
  let css;
6867
6650
  try {
6868
- css = await readFile13(absFile, "utf8");
6651
+ css = await readFile12(absFile, "utf8");
6869
6652
  } catch {
6870
6653
  continue;
6871
6654
  }
@@ -6892,7 +6675,7 @@ var check21 = {
6892
6675
  }
6893
6676
  for (const v of violations) {
6894
6677
  results.push({
6895
- checkId: check21.id,
6678
+ checkId: check22.id,
6896
6679
  severity: "warning",
6897
6680
  message: v.message,
6898
6681
  file: absFile,
@@ -6904,7 +6687,7 @@ var check21 = {
6904
6687
  return results;
6905
6688
  }
6906
6689
  };
6907
- registerCheck(check21);
6690
+ registerCheck(check22);
6908
6691
 
6909
6692
  // src/checks/source/local-refs.ts
6910
6693
  import { existsSync as existsSync9 } from "node:fs";
@@ -6912,7 +6695,7 @@ import { dirname as dirname3, resolve as resolve5 } from "node:path";
6912
6695
 
6913
6696
  // src/lib/asset-inline.ts
6914
6697
  import { createHash as createHash2 } from "node:crypto";
6915
- import { readFile as readFile14 } from "node:fs/promises";
6698
+ import { readFile as readFile13 } from "node:fs/promises";
6916
6699
  import path7 from "node:path";
6917
6700
  import postcss3 from "postcss";
6918
6701
  var FONT_EXTS = new Set([".woff2", ".woff", ".ttf", ".otf"]);
@@ -6977,7 +6760,7 @@ function mimeFor(ext) {
6977
6760
  }
6978
6761
  async function readOrThrow(absPath, kind, referencedFrom) {
6979
6762
  try {
6980
- return await readFile14(absPath);
6763
+ return await readFile13(absPath);
6981
6764
  } catch {
6982
6765
  throw new BuildError(`Missing ${kind}: ${absPath}
6983
6766
  referenced from ${referencedFrom}
@@ -7120,7 +6903,7 @@ async function collectStyleDependencies(projectDir, stylePaths) {
7120
6903
  found.add(abs);
7121
6904
  let source;
7122
6905
  try {
7123
- source = await readFile14(abs, "utf-8");
6906
+ source = await readFile13(abs, "utf-8");
7124
6907
  } catch {
7125
6908
  return;
7126
6909
  }
@@ -7197,7 +6980,7 @@ async function inlineShapeUrls(html, baseDir) {
7197
6980
  continue;
7198
6981
  let bytes;
7199
6982
  try {
7200
- bytes = await readFile14(abs);
6983
+ bytes = await readFile13(abs);
7201
6984
  } catch {
7202
6985
  continue;
7203
6986
  }
@@ -7215,7 +6998,7 @@ async function inlineShapeUrls(html, baseDir) {
7215
6998
  }
7216
6999
 
7217
7000
  // src/checks/source/local-ref-parser.ts
7218
- import { readFile as readFile15 } from "node:fs/promises";
7001
+ import { readFile as readFile14 } from "node:fs/promises";
7219
7002
  function newlineOffsets(content) {
7220
7003
  const offsets = [];
7221
7004
  for (let i = 0;i < content.length; i++)
@@ -7284,7 +7067,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
7284
7067
  for (const file of files) {
7285
7068
  let refs;
7286
7069
  try {
7287
- refs = collectRenderedLocalRefs(await readFile15(file, "utf8"));
7070
+ refs = collectRenderedLocalRefs(await readFile14(file, "utf8"));
7288
7071
  } catch {
7289
7072
  results.push(inspectionFailed(checkId, `Could not read source file: ${file}`, { file }));
7290
7073
  continue;
@@ -7295,7 +7078,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
7295
7078
  }
7296
7079
 
7297
7080
  // src/checks/source/local-refs.ts
7298
- var check22 = {
7081
+ var check23 = {
7299
7082
  id: "source.links.local-refs",
7300
7083
  name: "Local Markdown References",
7301
7084
  description: "Checks local markdown links and image refs resolve on disk",
@@ -7303,12 +7086,12 @@ var check22 = {
7303
7086
  phase: "pre-build",
7304
7087
  async run(ctx) {
7305
7088
  const results = [];
7306
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check22.id, results)) {
7089
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
7307
7090
  if (isNonFilesystemRef(ref))
7308
7091
  continue;
7309
7092
  const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
7310
7093
  if (escape) {
7311
- results.push(finding(check22.id, {
7094
+ results.push(finding(check23.id, {
7312
7095
  severity: "error",
7313
7096
  message: escape,
7314
7097
  file,
@@ -7318,7 +7101,7 @@ var check22 = {
7318
7101
  }
7319
7102
  if (localRefExists(ref, kind, file, ctx.inputDir))
7320
7103
  continue;
7321
- results.push(finding(check22.id, {
7104
+ results.push(finding(check23.id, {
7322
7105
  severity: kind === "image" ? "warning" : "error",
7323
7106
  code: kind === "image" ? "missing-image-placeholder" : undefined,
7324
7107
  message: kind === "image" ? `Local image not found; build will substitute a magenta placeholder: ${ref}` : `Local reference not found: ${ref}`,
@@ -7343,7 +7126,7 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
7343
7126
  return existsSync9(fileRelative);
7344
7127
  }
7345
7128
  }
7346
- registerCheck(check22);
7129
+ registerCheck(check23);
7347
7130
 
7348
7131
  // src/lib/build-staging.ts
7349
7132
  import path8 from "node:path";
@@ -7609,7 +7392,7 @@ async function createStageRoot() {
7609
7392
  }
7610
7393
 
7611
7394
  // src/checks/source/dangling-links.ts
7612
- var check23 = {
7395
+ var check24 = {
7613
7396
  id: "source.links.dangling",
7614
7397
  name: "Dangling Links",
7615
7398
  description: "Flags relative links the printed book cannot open (their href is dropped at print time)",
@@ -7617,10 +7400,10 @@ var check23 = {
7617
7400
  phase: "pre-build",
7618
7401
  async run(ctx) {
7619
7402
  const results = [];
7620
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
7403
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check24.id, results)) {
7621
7404
  if (kind !== "link" || isPrintResolvableHref(ref))
7622
7405
  continue;
7623
- results.push(finding(check23.id, {
7406
+ results.push(finding(check24.id, {
7624
7407
  severity: "warning",
7625
7408
  code: "dangling-link",
7626
7409
  data: { ref },
@@ -7632,11 +7415,11 @@ var check23 = {
7632
7415
  return results;
7633
7416
  }
7634
7417
  };
7635
- registerCheck(check23);
7418
+ registerCheck(check24);
7636
7419
 
7637
7420
  // src/checks/source/accessibility-alt-text.ts
7638
- import { readFile as readFile16 } from "node:fs/promises";
7639
- var check24 = {
7421
+ import { readFile as readFile15 } from "node:fs/promises";
7422
+ var check25 = {
7640
7423
  id: "source.accessibility.alt-text",
7641
7424
  name: "Image Alt Text",
7642
7425
  description: "Checks markdown images include non-empty alt text",
@@ -7649,7 +7432,7 @@ var check24 = {
7649
7432
  const results = [];
7650
7433
  for (const file of files) {
7651
7434
  try {
7652
- const content = await readFile16(file, "utf8");
7435
+ const content = await readFile15(file, "utf8");
7653
7436
  const lines = content.split(`
7654
7437
  `);
7655
7438
  let inFence = false;
@@ -7665,7 +7448,7 @@ var check24 = {
7665
7448
  if (alt.trim())
7666
7449
  continue;
7667
7450
  results.push({
7668
- checkId: check24.id,
7451
+ checkId: check25.id,
7669
7452
  severity: "warning",
7670
7453
  message: "Image is missing alt text",
7671
7454
  file,
@@ -7690,11 +7473,11 @@ function extractImageAlts(line) {
7690
7473
  }
7691
7474
  return alts;
7692
7475
  }
7693
- registerCheck(check24);
7476
+ registerCheck(check25);
7694
7477
 
7695
7478
  // src/checks/source/accessibility-heading-order.ts
7696
- import { readFile as readFile17 } from "node:fs/promises";
7697
- var check25 = {
7479
+ import { readFile as readFile16 } from "node:fs/promises";
7480
+ var check26 = {
7698
7481
  id: "source.accessibility.heading-order",
7699
7482
  name: "Heading Order",
7700
7483
  description: "Checks markdown heading levels do not jump by more than one",
@@ -7707,7 +7490,7 @@ var check25 = {
7707
7490
  const results = [];
7708
7491
  for (const file of files) {
7709
7492
  try {
7710
- const content = await readFile17(file, "utf8");
7493
+ const content = await readFile16(file, "utf8");
7711
7494
  const lines = content.split(`
7712
7495
  `);
7713
7496
  let inFence = false;
@@ -7722,7 +7505,7 @@ var check25 = {
7722
7505
  if (inFence)
7723
7506
  continue;
7724
7507
  const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
7725
- if (comment && parseDisableDirective(comment[1])?.includes(check25.id)) {
7508
+ if (comment && parseDisableDirective(comment[1])?.includes(check26.id)) {
7726
7509
  silencedLine = i + 1;
7727
7510
  continue;
7728
7511
  }
@@ -7732,7 +7515,7 @@ var check25 = {
7732
7515
  const level = match[1].length;
7733
7516
  if (prevLevel != null && level > prevLevel + 1 && i !== silencedLine) {
7734
7517
  results.push({
7735
- checkId: check25.id,
7518
+ checkId: check26.id,
7736
7519
  severity: "warning",
7737
7520
  message: `Heading level jump from h${prevLevel} to h${level}`,
7738
7521
  file,
@@ -7746,10 +7529,10 @@ var check25 = {
7746
7529
  return results;
7747
7530
  }
7748
7531
  };
7749
- registerCheck(check25);
7532
+ registerCheck(check26);
7750
7533
 
7751
7534
  // src/checks/source/layout-markers.ts
7752
- import { readFile as readFile18 } from "node:fs/promises";
7535
+ import { readFile as readFile17 } from "node:fs/promises";
7753
7536
 
7754
7537
  // src/lib/unknown-classes.ts
7755
7538
  import postcss4 from "postcss";
@@ -7852,12 +7635,12 @@ async function collectKnownClasses(ctx, plugins) {
7852
7635
  const sheets = (await collectStyleDependencies(ctx.inputDir, roots)).filter((f) => /\.css$/i.test(f));
7853
7636
  for (const sheet of sheets) {
7854
7637
  try {
7855
- addCssClasses(await readFile18(sheet, "utf8"), names);
7638
+ addCssClasses(await readFile17(sheet, "utf8"), names);
7856
7639
  } catch {}
7857
7640
  }
7858
7641
  return names;
7859
7642
  }
7860
- var check26 = {
7643
+ var check27 = {
7861
7644
  id: "source.markdown.layout-markers",
7862
7645
  name: "Layout Markers",
7863
7646
  description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class and any other class no stylesheet mentions",
@@ -7869,14 +7652,14 @@ var check26 = {
7869
7652
  return [];
7870
7653
  const results = [];
7871
7654
  const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
7872
- results.push(inspectionFailed(check26.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
7655
+ results.push(inspectionFailed(check27.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
7873
7656
  });
7874
7657
  const md = createMarkdownRenderer(plugins);
7875
7658
  let known;
7876
7659
  const knownClasses = () => known ??= collectKnownClasses(ctx, plugins);
7877
7660
  for (const file of files) {
7878
7661
  try {
7879
- const content = await readFile18(file, "utf8");
7662
+ const content = await readFile17(file, "utf8");
7880
7663
  const env = {};
7881
7664
  const tokens = md.parse(content, env);
7882
7665
  md.renderer.render(tokens, md.options, env);
@@ -7888,7 +7671,7 @@ var check26 = {
7888
7671
  if (names.has(cls) || warnedLines.has(use.line))
7889
7672
  continue;
7890
7673
  const guess = nearestKnownClass(cls, names);
7891
- results.push(finding(check26.id, {
7674
+ results.push(finding(check27.id, {
7892
7675
  severity: "warning",
7893
7676
  message: `Class ".${cls}" is not in any stylesheet (Gutterpress's own, the plugins', or this book's), so it styles nothing.` + (guess ? ` Did you mean ".${guess}"?` : "") + (use.count > 1 ? ` Used ${use.count} times in this file; the first is reported.` : ""),
7894
7677
  file,
@@ -7898,7 +7681,7 @@ var check26 = {
7898
7681
  }
7899
7682
  }
7900
7683
  for (const w of env.layoutWarnings ?? []) {
7901
- results.push(finding(check26.id, {
7684
+ results.push(finding(check27.id, {
7902
7685
  severity: w.severity ?? "warning",
7903
7686
  message: w.message,
7904
7687
  file,
@@ -7907,16 +7690,16 @@ var check26 = {
7907
7690
  }));
7908
7691
  }
7909
7692
  } catch (error2) {
7910
- results.push(inspectionFailed(check26.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
7693
+ results.push(inspectionFailed(check27.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
7911
7694
  }
7912
7695
  }
7913
7696
  return results;
7914
7697
  }
7915
7698
  };
7916
- registerCheck(check26);
7699
+ registerCheck(check27);
7917
7700
 
7918
7701
  // src/checks/source/merge-markers.ts
7919
- import { readFile as readFile19 } from "node:fs/promises";
7702
+ import { readFile as readFile18 } from "node:fs/promises";
7920
7703
  import path9 from "node:path";
7921
7704
 
7922
7705
  // src/checks/asset/extensions.ts
@@ -7946,7 +7729,7 @@ var CLOSE_SENTINEL = ">>>>>>> online version";
7946
7729
  function withoutOnlineTag(relPath) {
7947
7730
  return relPath.replace(/\.online(?=\.[^./]*$|$)/, "");
7948
7731
  }
7949
- var check27 = {
7732
+ var check28 = {
7950
7733
  id: "source.sync.merge-markers",
7951
7734
  name: "Combined Versions",
7952
7735
  description: "Finds passages and files still holding two versions (yours and the online copy) after a sync",
@@ -7958,9 +7741,9 @@ var check27 = {
7958
7741
  for (const file of textFiles) {
7959
7742
  let content;
7960
7743
  try {
7961
- content = await readFile19(file, "utf8");
7744
+ content = await readFile18(file, "utf8");
7962
7745
  } catch {
7963
- results.push(inspectionFailed(check27.id, `Could not read source file: ${file}`, { file }));
7746
+ results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
7964
7747
  continue;
7965
7748
  }
7966
7749
  if (!content.includes(OPEN_SENTINEL) && !content.includes(CLOSE_SENTINEL))
@@ -7972,7 +7755,7 @@ var check27 = {
7972
7755
  const line = lines[i].endsWith("\r") ? lines[i].slice(0, -1) : lines[i];
7973
7756
  if (line === OPEN_SENTINEL) {
7974
7757
  inBlock = true;
7975
- results.push(finding(check27.id, {
7758
+ results.push(finding(check28.id, {
7976
7759
  severity: "error",
7977
7760
  code: "two-versions-passage",
7978
7761
  message: "This passage has two versions (yours and the online copy) — keep what you want, then delete the marker lines.",
@@ -7981,7 +7764,7 @@ var check27 = {
7981
7764
  }));
7982
7765
  } else if (line === CLOSE_SENTINEL) {
7983
7766
  if (!inBlock) {
7984
- results.push(finding(check27.id, {
7767
+ results.push(finding(check28.id, {
7985
7768
  severity: "error",
7986
7769
  code: "leftover-version-marker",
7987
7770
  message: "This marker line is left over from combining two versions — delete it.",
@@ -8002,7 +7785,7 @@ var check27 = {
8002
7785
  });
8003
7786
  for (const sibling of siblings.sort()) {
8004
7787
  const rel = path9.relative(ctx.inputDir, sibling).split(path9.sep).join("/");
8005
- results.push(finding(check27.id, {
7788
+ results.push(finding(check28.id, {
8006
7789
  severity: "warning",
8007
7790
  code: "kept-both-versions",
8008
7791
  message: `Two versions of ${withoutOnlineTag(rel)} are in your book — keep the one you want, then delete the other.`,
@@ -8010,15 +7793,15 @@ var check27 = {
8010
7793
  }));
8011
7794
  }
8012
7795
  } catch (error2) {
8013
- results.push(inspectionFailed(check27.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
7796
+ results.push(inspectionFailed(check28.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
8014
7797
  }
8015
7798
  return results;
8016
7799
  }
8017
7800
  };
8018
- registerCheck(check27);
7801
+ registerCheck(check28);
8019
7802
 
8020
7803
  // src/checks/source/characters.ts
8021
- import { readFile as readFile20 } from "node:fs/promises";
7804
+ import { readFile as readFile19 } from "node:fs/promises";
8022
7805
 
8023
7806
  // src/checks/source/character-table.ts
8024
7807
  var CHARACTER_PATTERN = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u00A0\\u00AD\\u00B4\\u02BC" + "\\u061C\\u2000-\\u2015\\u2018\\u2019\\u201B-\\u201D\\u201F\\u2022\\u2026" + "\\u2028-\\u202F\\u2032\\u2033\\u205F\\u2060\\u2066-\\u2069\\u2190\\u2192" + "\\u2194\\u21D2\\u2212\\u25AA\\u25E6\\u2605\\u2713\\u2714\\u2717\\u3000" + "\\uE000-\\uF8FF\\uFEFF\\uFF02\\uFF07\\uFFFD]" + "|\\p{Emoji_Presentation}|\\p{Extended_Pictographic}(?=\\uFE0F)", "gu");
@@ -8130,7 +7913,7 @@ function codePointLabel(char) {
8130
7913
 
8131
7914
  // src/checks/source/characters.ts
8132
7915
  var PROBE = new RegExp(CHARACTER_PATTERN.source, "u");
8133
- var check28 = {
7916
+ var check29 = {
8134
7917
  id: "source.characters",
8135
7918
  name: "Unusual Characters",
8136
7919
  description: "Finds invisible and control characters, odd spaces, and pasted typography (em dashes, curly quotes, emoji) in markdown",
@@ -8141,9 +7924,9 @@ var check28 = {
8141
7924
  for (const file of (ctx.markdownFiles ?? []).slice().sort()) {
8142
7925
  let content;
8143
7926
  try {
8144
- content = await readFile20(file, "utf8");
7927
+ content = await readFile19(file, "utf8");
8145
7928
  } catch {
8146
- results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
7929
+ results.push(inspectionFailed(check29.id, `Could not read source file: ${file}`, { file }));
8147
7930
  continue;
8148
7931
  }
8149
7932
  if (!PROBE.test(content))
@@ -8154,7 +7937,7 @@ var check28 = {
8154
7937
  for (let i = 0;i < lines.length; i++) {
8155
7938
  const line = lines[i];
8156
7939
  const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
8157
- if (comment && parseDisableDirective(comment[1])?.includes(check28.id)) {
7940
+ if (comment && parseDisableDirective(comment[1])?.includes(check29.id)) {
8158
7941
  silencedLine = i + 1;
8159
7942
  continue;
8160
7943
  }
@@ -8163,7 +7946,7 @@ var check28 = {
8163
7946
  for (const match of line.matchAll(CHARACTER_PATTERN)) {
8164
7947
  const char = match[0];
8165
7948
  const { name, code, fix } = describeCharacter(char);
8166
- results.push(finding(check28.id, {
7949
+ results.push(finding(check29.id, {
8167
7950
  severity: code === "typographer" ? "info" : "warning",
8168
7951
  code,
8169
7952
  message: `${name} (${codePointLabel(char)}). ${fix}`,
@@ -8178,19 +7961,19 @@ var check28 = {
8178
7961
  return results;
8179
7962
  }
8180
7963
  };
8181
- registerCheck(check28);
7964
+ registerCheck(check29);
8182
7965
 
8183
7966
  // src/checks/asset/image-file-size.ts
8184
- import { stat as stat4 } from "node:fs/promises";
7967
+ import { stat as stat3 } from "node:fs/promises";
8185
7968
 
8186
7969
  // src/lib/image-inspect.ts
8187
- import { stat as stat3, readFile as readFile21 } from "node:fs/promises";
7970
+ import { stat as stat2, readFile as readFile20 } from "node:fs/promises";
8188
7971
  var DEFAULT_DPI = 72;
8189
7972
  var cache = new Map;
8190
7973
  async function inspectImage(path10) {
8191
7974
  let st;
8192
7975
  try {
8193
- st = await stat3(path10);
7976
+ st = await stat2(path10);
8194
7977
  } catch {
8195
7978
  return null;
8196
7979
  }
@@ -8199,7 +7982,7 @@ async function inspectImage(path10) {
8199
7982
  return hit.info;
8200
7983
  let info3 = null;
8201
7984
  try {
8202
- const buf = await readFile21(path10);
7985
+ const buf = await readFile20(path10);
8203
7986
  info3 = parseImage(buf);
8204
7987
  } catch {
8205
7988
  info3 = null;
@@ -8404,7 +8187,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
8404
8187
  }
8405
8188
 
8406
8189
  // src/checks/asset/image-file-size.ts
8407
- var check29 = {
8190
+ var check30 = {
8408
8191
  id: "asset.image.file-size",
8409
8192
  name: "Image File Size",
8410
8193
  description: "Checks that image files do not exceed the maximum size limit",
@@ -8421,18 +8204,18 @@ var check29 = {
8421
8204
  const results = [];
8422
8205
  for (const file of files) {
8423
8206
  try {
8424
- const info3 = await stat4(file);
8207
+ const info3 = await stat3(file);
8425
8208
  if (info3.size > maxSize) {
8426
8209
  const sizeMb = (info3.size / 1e6).toFixed(1);
8427
8210
  const maxMb = (maxSize / 1e6).toFixed(1);
8428
- results.push(finding(check29.id, {
8211
+ results.push(finding(check30.id, {
8429
8212
  severity: "warning",
8430
8213
  message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
8431
8214
  file
8432
8215
  }));
8433
8216
  }
8434
8217
  } catch {
8435
- results.push(inspectionFailed(check29.id, `Could not stat image file: ${file}`, {
8218
+ results.push(inspectionFailed(check30.id, `Could not stat image file: ${file}`, {
8436
8219
  file
8437
8220
  }));
8438
8221
  }
@@ -8440,10 +8223,10 @@ var check29 = {
8440
8223
  return results;
8441
8224
  }
8442
8225
  };
8443
- registerCheck(check29);
8226
+ registerCheck(check30);
8444
8227
 
8445
8228
  // src/checks/asset/image-resolution.ts
8446
- var check30 = {
8229
+ var check31 = {
8447
8230
  id: "asset.image.resolution",
8448
8231
  name: "Image Resolution",
8449
8232
  description: "Checks source image DPI from embedded density metadata",
@@ -8465,7 +8248,7 @@ var check30 = {
8465
8248
  const { xDpi, yDpi } = info3;
8466
8249
  if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
8467
8250
  results.push({
8468
- checkId: check30.id,
8251
+ checkId: check31.id,
8469
8252
  severity: "warning",
8470
8253
  message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
8471
8254
  file
@@ -8475,7 +8258,7 @@ var check30 = {
8475
8258
  return results;
8476
8259
  }
8477
8260
  };
8478
- registerCheck(check30);
8261
+ registerCheck(check31);
8479
8262
 
8480
8263
  // src/checks/asset/image-color-space.ts
8481
8264
  var LABEL = {
@@ -8483,7 +8266,7 @@ var LABEL = {
8483
8266
  gray: "Gray",
8484
8267
  cmyk: "CMYK"
8485
8268
  };
8486
- var check31 = {
8269
+ var check32 = {
8487
8270
  id: "asset.image.color-space",
8488
8271
  name: "Image Color Space",
8489
8272
  description: "Validates image color spaces against allowed list",
@@ -8504,7 +8287,7 @@ var check31 = {
8504
8287
  const cs = info3?.colorSpace;
8505
8288
  if (cs && !allowedLower.has(cs)) {
8506
8289
  results.push({
8507
- checkId: check31.id,
8290
+ checkId: check32.id,
8508
8291
  severity: "warning",
8509
8292
  message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
8510
8293
  file
@@ -8514,10 +8297,10 @@ var check31 = {
8514
8297
  return results;
8515
8298
  }
8516
8299
  };
8517
- registerCheck(check31);
8300
+ registerCheck(check32);
8518
8301
 
8519
8302
  // src/checks/asset/image-alpha.ts
8520
- var check32 = {
8303
+ var check33 = {
8521
8304
  id: "asset.image.alpha-channel",
8522
8305
  name: "Image Alpha Channel",
8523
8306
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -8535,7 +8318,7 @@ var check32 = {
8535
8318
  const info3 = await inspectImage(file);
8536
8319
  if (info3?.hasAlpha) {
8537
8320
  results.push({
8538
- checkId: check32.id,
8321
+ checkId: check33.id,
8539
8322
  severity: "warning",
8540
8323
  message: "Image contains alpha channel, which may cause print issues",
8541
8324
  file
@@ -8545,10 +8328,10 @@ var check32 = {
8545
8328
  return results;
8546
8329
  }
8547
8330
  };
8548
- registerCheck(check32);
8331
+ registerCheck(check33);
8549
8332
 
8550
8333
  // src/checks/asset/image-tac.ts
8551
- var check33 = {
8334
+ var check34 = {
8552
8335
  id: "asset.image.tac-raster",
8553
8336
  name: "Image TAC (Raster)",
8554
8337
  description: "Rasterizes and checks TAC per image using Ghostscript",
@@ -8567,7 +8350,7 @@ var check33 = {
8567
8350
  const ghostscript = await resolveGhostscript();
8568
8351
  if (!ghostscript) {
8569
8352
  return [
8570
- inspectionFailed(check33.id, "Could not inspect image ink coverage: Ghostscript executable not found")
8353
+ inspectionFailed(check34.id, "Could not inspect image ink coverage: Ghostscript executable not found")
8571
8354
  ];
8572
8355
  }
8573
8356
  for (const file of files) {
@@ -8585,7 +8368,7 @@ var check33 = {
8585
8368
  if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
8586
8369
  const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
8587
8370
  if (tac > maxTac) {
8588
- results.push(finding(check33.id, {
8371
+ results.push(finding(check34.id, {
8589
8372
  severity: "warning",
8590
8373
  message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
8591
8374
  file,
@@ -8597,16 +8380,16 @@ var check33 = {
8597
8380
  }
8598
8381
  }
8599
8382
  } catch {
8600
- results.push(inspectionFailed(check33.id, `Could not inspect image ink coverage: ${file}`, { file }));
8383
+ results.push(inspectionFailed(check34.id, `Could not inspect image ink coverage: ${file}`, { file }));
8601
8384
  }
8602
8385
  }
8603
8386
  return results;
8604
8387
  }
8605
8388
  };
8606
- registerCheck(check33);
8389
+ registerCheck(check34);
8607
8390
 
8608
8391
  // src/checks/asset/approved-fonts.ts
8609
- var check34 = {
8392
+ var check35 = {
8610
8393
  id: "asset.font.approved-files",
8611
8394
  name: "Approved Font Files",
8612
8395
  description: "Checks font files against the approved file patterns",
@@ -8645,7 +8428,7 @@ var check34 = {
8645
8428
  for (const font of allFonts) {
8646
8429
  if (!approvedFonts.has(font)) {
8647
8430
  results.push({
8648
- checkId: check34.id,
8431
+ checkId: check35.id,
8649
8432
  severity: "warning",
8650
8433
  message: "Font file not in approved list",
8651
8434
  file: font
@@ -8655,7 +8438,7 @@ var check34 = {
8655
8438
  return results;
8656
8439
  }
8657
8440
  };
8658
- registerCheck(check34);
8441
+ registerCheck(check35);
8659
8442
 
8660
8443
  // src/checks/asset/font-license.ts
8661
8444
  import { existsSync as existsSync10 } from "node:fs";
@@ -8670,7 +8453,7 @@ var LICENSE_NAMES = [
8670
8453
  "OFL-1.1.txt",
8671
8454
  "COPYING"
8672
8455
  ];
8673
- var check35 = {
8456
+ var check36 = {
8674
8457
  id: "asset.font.license",
8675
8458
  name: "Font License",
8676
8459
  description: "Checks for license files in font directories",
@@ -8697,7 +8480,7 @@ var check35 = {
8697
8480
  const hasLicense = LICENSE_NAMES.some((name) => existsSync10(resolve6(fontDir, name)));
8698
8481
  if (!hasLicense) {
8699
8482
  results.push({
8700
- checkId: check35.id,
8483
+ checkId: check36.id,
8701
8484
  severity: "warning",
8702
8485
  message: `No font license file found in directory`,
8703
8486
  file: fontDir
@@ -8707,10 +8490,10 @@ var check35 = {
8707
8490
  return results;
8708
8491
  }
8709
8492
  };
8710
- registerCheck(check35);
8493
+ registerCheck(check36);
8711
8494
 
8712
8495
  // src/checks/heuristic/text-density.ts
8713
- var check36 = {
8496
+ var check37 = {
8714
8497
  id: "heuristic.whitespace.text-density",
8715
8498
  name: "Text Density",
8716
8499
  description: "Checks characters-per-page ratio",
@@ -8739,7 +8522,7 @@ var check36 = {
8739
8522
  });
8740
8523
  if (lowPages.length > 0) {
8741
8524
  results.push({
8742
- checkId: check36.id,
8525
+ checkId: check37.id,
8743
8526
  severity: "info",
8744
8527
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
8745
8528
  file: ctx.pdfPath
@@ -8747,7 +8530,7 @@ var check36 = {
8747
8530
  }
8748
8531
  if (highPages.length > 0) {
8749
8532
  results.push({
8750
- checkId: check36.id,
8533
+ checkId: check37.id,
8751
8534
  severity: "info",
8752
8535
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
8753
8536
  file: ctx.pdfPath
@@ -8756,11 +8539,11 @@ var check36 = {
8756
8539
  return results;
8757
8540
  }
8758
8541
  };
8759
- registerCheck(check36);
8542
+ registerCheck(check37);
8760
8543
 
8761
8544
  // src/checks/heuristic/section-density.ts
8762
- import { readFile as readFile22 } from "node:fs/promises";
8763
- var check37 = {
8545
+ import { readFile as readFile21 } from "node:fs/promises";
8546
+ var check38 = {
8764
8547
  id: "heuristic.chunking.section-density",
8765
8548
  name: "Section Density",
8766
8549
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -8776,7 +8559,7 @@ var check37 = {
8776
8559
  const results = [];
8777
8560
  for (const file of files) {
8778
8561
  try {
8779
- const content = await readFile22(file, "utf8");
8562
+ const content = await readFile21(file, "utf8");
8780
8563
  const lines = content.split(`
8781
8564
  `);
8782
8565
  let currentSectionStart = 0;
@@ -8786,7 +8569,7 @@ var check37 = {
8786
8569
  const line = lines[i];
8787
8570
  if (/^#{1,6}\s/.test(line)) {
8788
8571
  if (paragraphCount > maxParas) {
8789
- results.push(finding(check37.id, {
8572
+ results.push(finding(check38.id, {
8790
8573
  severity: "info",
8791
8574
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
8792
8575
  file,
@@ -8806,7 +8589,7 @@ var check37 = {
8806
8589
  }
8807
8590
  }
8808
8591
  if (paragraphCount > maxParas) {
8809
- results.push(finding(check37.id, {
8592
+ results.push(finding(check38.id, {
8810
8593
  severity: "info",
8811
8594
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
8812
8595
  file,
@@ -8814,7 +8597,7 @@ var check37 = {
8814
8597
  }));
8815
8598
  }
8816
8599
  } catch {
8817
- results.push(inspectionFailed(check37.id, `Could not read source file: ${file}`, {
8600
+ results.push(inspectionFailed(check38.id, `Could not read source file: ${file}`, {
8818
8601
  file
8819
8602
  }));
8820
8603
  }
@@ -8822,10 +8605,10 @@ var check37 = {
8822
8605
  return results;
8823
8606
  }
8824
8607
  };
8825
- registerCheck(check37);
8608
+ registerCheck(check38);
8826
8609
 
8827
8610
  // src/checks/heuristic/layer-count.ts
8828
- var check38 = {
8611
+ var check39 = {
8829
8612
  id: "heuristic.decoration.layer-count",
8830
8613
  name: "Layer Count",
8831
8614
  description: "Counts image objects per page",
@@ -8850,7 +8633,7 @@ var check38 = {
8850
8633
  heavyPages.sort((a, b) => a - b);
8851
8634
  return [
8852
8635
  {
8853
- checkId: check38.id,
8636
+ checkId: check39.id,
8854
8637
  severity: "info",
8855
8638
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
8856
8639
  file: ctx.pdfPath
@@ -8860,10 +8643,10 @@ var check38 = {
8860
8643
  return [];
8861
8644
  }
8862
8645
  };
8863
- registerCheck(check38);
8646
+ registerCheck(check39);
8864
8647
 
8865
8648
  // src/checks/heuristic/placement-variance.ts
8866
- var check39 = {
8649
+ var check40 = {
8867
8650
  id: "heuristic.layout.placement-variance",
8868
8651
  name: "Placement Variance",
8869
8652
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -8881,7 +8664,7 @@ var check39 = {
8881
8664
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
8882
8665
  return [
8883
8666
  {
8884
- checkId: check39.id,
8667
+ checkId: check40.id,
8885
8668
  severity: "info",
8886
8669
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
8887
8670
  file: ctx.pdfPath
@@ -8889,21 +8672,21 @@ var check39 = {
8889
8672
  ];
8890
8673
  }
8891
8674
  };
8892
- registerCheck(check39);
8675
+ registerCheck(check40);
8893
8676
 
8894
8677
  // src/checks/tool-check.ts
8895
8678
  async function checkToolAvailability(config, opts = {}) {
8896
8679
  const { checks: checks2 } = selectChecks(opts, config);
8897
8680
  const toolToChecks = new Map;
8898
- for (const check40 of checks2) {
8899
- if (!check40.requiredTools?.length)
8681
+ for (const check41 of checks2) {
8682
+ if (!check41.requiredTools?.length)
8900
8683
  continue;
8901
- for (const tool of check40.requiredTools) {
8684
+ for (const tool of check41.requiredTools) {
8902
8685
  const existing = toolToChecks.get(tool);
8903
8686
  if (existing) {
8904
- existing.push(check40.id);
8687
+ existing.push(check41.id);
8905
8688
  } else {
8906
- toolToChecks.set(tool, [check40.id]);
8689
+ toolToChecks.set(tool, [check41.id]);
8907
8690
  }
8908
8691
  }
8909
8692
  }
@@ -8919,11 +8702,11 @@ async function checkToolAvailability(config, opts = {}) {
8919
8702
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
8920
8703
  const missingSet = new Set(missing);
8921
8704
  const skippedChecks = [];
8922
- for (const check40 of checks2) {
8923
- if (!check40.requiredTools?.length)
8705
+ for (const check41 of checks2) {
8706
+ if (!check41.requiredTools?.length)
8924
8707
  continue;
8925
- if (check40.requiredTools.some((t) => missingSet.has(t))) {
8926
- skippedChecks.push(check40.id);
8708
+ if (check41.requiredTools.some((t) => missingSet.has(t))) {
8709
+ skippedChecks.push(check41.id);
8927
8710
  }
8928
8711
  }
8929
8712
  return { available, missing, skippedChecks, toolToChecks };
@@ -9080,7 +8863,7 @@ async function restoreForCommand(dir, options) {
9080
8863
  }
9081
8864
 
9082
8865
  // src/lib/markdown/index.ts
9083
- import { readdir as readdir3, readFile as readFile23, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
8866
+ import { readdir as readdir3, readFile as readFile22, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
9084
8867
 
9085
8868
  // src/lib/remote-auth/sync-messages.ts
9086
8869
  function isOnlineSibling(filepath) {
@@ -9222,7 +9005,7 @@ ${css.trim()}
9222
9005
  }
9223
9006
  return assembleBookHtml({
9224
9007
  files,
9225
- readText: (relPath) => readFile23(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
9008
+ readText: (relPath) => readFile22(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
9226
9009
  projectCss: inlined.css,
9227
9010
  preloadImages: cssAssetCopies.map((c) => c.to),
9228
9011
  title: opts.title,
@@ -9254,7 +9037,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
9254
9037
  }
9255
9038
 
9256
9039
  // src/lib/markdownlint-fix.ts
9257
- import { readFile as readFile24, writeFile as writeFile3 } from "node:fs/promises";
9040
+ import { readFile as readFile23, writeFile as writeFile3 } from "node:fs/promises";
9258
9041
  import { applyFixes } from "markdownlint";
9259
9042
  import { lint as lint2 } from "markdownlint/sync";
9260
9043
  async function uncommittedAmong(inputDir, files) {
@@ -9294,7 +9077,7 @@ async function applyMarkdownlintFixes(ctx) {
9294
9077
  for (const [file, violations] of Object.entries(results)) {
9295
9078
  if (!Array.isArray(violations) || violations.length === 0)
9296
9079
  continue;
9297
- const before = await readFile24(file, "utf8");
9080
+ const before = await readFile23(file, "utf8");
9298
9081
  const after = applyFixes(before, violations);
9299
9082
  if (after !== before)
9300
9083
  pending.push({ file, content: after });
@@ -9403,16 +9186,16 @@ async function runChecks(ctx, opts = {}) {
9403
9186
  }
9404
9187
  const release = retainPdfCache();
9405
9188
  try {
9406
- for (const check40 of checks2) {
9189
+ for (const check41 of checks2) {
9407
9190
  try {
9408
- let results = await check40.run(ctx);
9409
- const severityOverride = getCheckSeverityOverride(check40.id, ctx.config);
9191
+ let results = await check41.run(ctx);
9192
+ const severityOverride = getCheckSeverityOverride(check41.id, ctx.config);
9410
9193
  if (severityOverride) {
9411
9194
  for (const r of results) {
9412
9195
  r.severity = severityOverride;
9413
9196
  }
9414
9197
  }
9415
- const lintLevel = ruleLevel(ctx.config.lint.rules, check40.id);
9198
+ const lintLevel = ruleLevel(ctx.config.lint.rules, check41.id);
9416
9199
  if (lintLevel === "off")
9417
9200
  results = [];
9418
9201
  else if (lintLevel) {
@@ -9420,15 +9203,15 @@ async function runChecks(ctx, opts = {}) {
9420
9203
  r.severity = lintLevel === "warn" ? "warning" : "error";
9421
9204
  }
9422
9205
  if (results.length === 0) {
9423
- passed.push(check40.id);
9206
+ passed.push(check41.id);
9424
9207
  } else {
9425
9208
  allResults.push(...results);
9426
9209
  }
9427
9210
  } catch (err) {
9428
9211
  allResults.push({
9429
- checkId: check40.id,
9212
+ checkId: check41.id,
9430
9213
  severity: "error",
9431
- message: `Check "${check40.id}" threw: ${err instanceof Error ? err.message : String(err)}`
9214
+ message: `Check "${check41.id}" threw: ${err instanceof Error ? err.message : String(err)}`
9432
9215
  });
9433
9216
  }
9434
9217
  }
@@ -9720,7 +9503,7 @@ async function executeValidation(args) {
9720
9503
  };
9721
9504
  if (args.fix) {
9722
9505
  const selected = selectChecks(runnerOptions, config).checks;
9723
- if (selected.some((check40) => check40.id === MARKDOWNLINT_CHECK_ID)) {
9506
+ if (selected.some((check41) => check41.id === MARKDOWNLINT_CHECK_ID)) {
9724
9507
  await applyMarkdownlintFixes(context);
9725
9508
  } else {
9726
9509
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -9807,7 +9590,7 @@ import { randomBytes } from "node:crypto";
9807
9590
 
9808
9591
  // src/lib/build-fingerprint.ts
9809
9592
  import * as fs from "node:fs";
9810
- import { mkdir as mkdir5, readFile as readFile25, writeFile as writeFile4 } from "node:fs/promises";
9593
+ import { mkdir as mkdir5, readFile as readFile24, writeFile as writeFile4 } from "node:fs/promises";
9811
9594
  import { createHash as createHash4 } from "node:crypto";
9812
9595
  import path12 from "node:path";
9813
9596
  import git from "isomorphic-git";
@@ -9894,8 +9677,8 @@ async function getEngineBundleHash() {
9894
9677
  getAssetPath("engine/gutterpress-agent.js")
9895
9678
  ]);
9896
9679
  const [viewerBytes, agentBytes] = await Promise.all([
9897
- readFile25(viewerPath),
9898
- readFile25(agentPath)
9680
+ readFile24(viewerPath),
9681
+ readFile24(agentPath)
9899
9682
  ]);
9900
9683
  const hash = createHash4("sha256");
9901
9684
  hash.update(viewerBytes);
@@ -9958,7 +9741,10 @@ async function writeBuildFingerprint(input) {
9958
9741
  resolvedFlavor: input.pdfx.resolvedFlavor,
9959
9742
  iccPath: input.pdfx.iccPath,
9960
9743
  stripAnnotations: input.pdfx.stripAnnotations,
9961
- blackText: input.pdfx.blackText ?? null
9744
+ blackText: input.pdfx.blackText ?? null,
9745
+ transparency: input.pdfx.transparency ?? null,
9746
+ flattenPages: input.pdfx.flattenPages ?? null,
9747
+ rasterDpi: input.pdfx.rasterDpi ?? null
9962
9748
  },
9963
9749
  outputDir: input.recordedOutputDir ? path12.resolve(input.recordedOutputDir) : outputDir
9964
9750
  },
@@ -10265,7 +10051,7 @@ class PdfOutput {
10265
10051
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10266
10052
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10267
10053
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10268
- const { buildNativePdf } = await import("./engine-1v8kmv3a.js");
10054
+ const { buildNativePdf } = await import("./engine-yxnnp3hx.js");
10269
10055
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10270
10056
  title: config.title,
10271
10057
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10287,12 +10073,40 @@ class PdfOutput {
10287
10073
  if (pdfxMode) {
10288
10074
  const icc = opts.iccPath ?? config.pdfx.icc;
10289
10075
  effectiveIccPath = await resolveIccProfile(icc, manifestDir, opts.iccPath);
10290
- if (await hasLiveTransparency(await fsp2.readFile(rawPdf))) {
10291
- log.warn("This book uses transparency — an image with an alpha channel, or a CSS " + `rule like \`opacity\` or \`mix-blend-mode\`. PDF/${pdfxMode === "x1a" ? "X-1a" : "X-3"} has no way to represent that (both are based on PDF 1.3, which predates PDF transparency), so Ghostscript will flatten every page that uses it into a single raster ` + "image — that page loses its embedded fonts and searchable text in the " + "PDF/X output. To keep vector text, remove the `opacity`/blend rule and flatten any alpha-channel image against its intended background before including it (export it with no alpha channel), or build --format pdf instead of pdfx if this book doesn't need print-ready CMYK. `pdfx.rasterDpi` sets the resolution of those page images (default 720).");
10076
+ const rawBytes = await fsp2.readFile(rawPdf);
10077
+ const transparent = await transparentPages(rawBytes);
10078
+ const blankGradientPages = await findPostScriptShadingPages(rawBytes);
10079
+ const flattenedGradientPages = blankGradientPages.filter((n) => transparent.includes(n));
10080
+ const vectorGradientPages = blankGradientPages.filter((n) => !transparent.includes(n));
10081
+ const GRADIENT_FIX = "Replace it with a plain linear-gradient() or radial-gradient(), or a small tiled image. For 0deg/90deg stripes, `linear-gradient(90deg, #b33 0 6px, #c8c5bf 6px 10px)` with `background-size: 10px 100%` converts correctly.";
10082
+ const pageList = (pages) => `Page${pages.length === 1 ? "" : "s"} ${pages.join(", ")}`;
10083
+ if (vectorGradientPages.length > 0) {
10084
+ log.warn(`${pageList(vectorGradientPages)}: a repeating-*-gradient() or conic-gradient() ` + "can print blank or distorted — " + `Ghostscript has been seen to paint nothing, or irregular stripes, for it when converting to PDF/X; check the page visually. ${GRADIENT_FIX}`);
10292
10085
  }
10293
- const blankGradientPages = await findPostScriptShadingPages(await fsp2.readFile(rawPdf));
10294
- if (blankGradientPages.length > 0) {
10295
- log.warn(`Page${blankGradientPages.length === 1 ? "" : "s"} ${blankGradientPages.join(", ")}: ` + "a repeating-*-gradient() or conic-gradient() will print blank — " + "Ghostscript cannot convert it to PDF/X. Replace it with a plain linear-gradient() or radial-gradient(), or a small tiled image. For 0deg/90deg stripes, `linear-gradient(90deg, #b33 0 6px, #c8c5bf 6px 10px)` with `background-size: 10px 100%` converts correctly.");
10086
+ if (flattenedGradientPages.length > 0 && config.pdfx.transparency !== "preserve-text") {
10087
+ log.warn(`${pageList(flattenedGradientPages)}: a repeating-*-gradient() or conic-gradient() is painted only as part of the one raster image Ghostscript makes of the whole page, so the page loses its embedded fonts and searchable text. Check the page visually. ${GRADIENT_FIX}`);
10088
+ }
10089
+ const flattenPages = config.pdfx.flattenPages;
10090
+ if (transparent.length > 0 && config.pdfx.transparency === "preserve-text") {
10091
+ const { unsplittableTextReasons } = await import("./pdf-inspect-zdmy7qca.js");
10092
+ const reasons = await unsplittableTextReasons(rawBytes, transparent).catch((err) => {
10093
+ log.warn(`pdfx.transparency: preserve-text could not inspect this PDF's text (${err instanceof Error ? err.message : String(err)}), so every page that uses transparency is flattened whole, as with flatten-page.`);
10094
+ return null;
10095
+ });
10096
+ if (reasons) {
10097
+ const plan = planTransparentPages(transparent, reasons, flattenPages, blankGradientPages);
10098
+ await flattenTransparentPages(rawPdf, plan.split, stage, config.pdfx.rasterDpi);
10099
+ reportTransparentPlan(plan, pdfxMode, config.pdfx.rasterDpi ?? 300, pageList);
10100
+ }
10101
+ }
10102
+ const unused = flattenPages.filter((n) => !transparent.includes(n));
10103
+ if (flattenPages.length > 0 && config.pdfx.transparency !== "preserve-text") {
10104
+ log.warn("pdfx.flattenPages has no effect without pdfx.transparency: preserve-text.");
10105
+ } else if (unused.length > 0) {
10106
+ log.warn(`pdfx.flattenPages lists ${pageList(unused).toLowerCase()}, which ${unused.length === 1 ? "has" : "have"} no transparency (or do not exist): nothing to flatten there.`);
10107
+ }
10108
+ if (transparent.length > 0 && config.pdfx.transparency !== "preserve-text") {
10109
+ log.warn("This book uses transparency — an image with an alpha channel, or a CSS " + `rule like \`opacity\` or \`mix-blend-mode\`. PDF/${pdfxMode === "x1a" ? "X-1a" : "X-3"} has no way to represent that (both are based on PDF 1.3, which predates PDF transparency), so Ghostscript will flatten every page that uses it into a single raster ` + "image — that page loses its embedded fonts and searchable text in the " + "PDF/X output. To keep vector text, remove the `opacity`/blend rule and flatten any alpha-channel image against its intended background before including it (export it with no alpha channel), or build --format pdf instead of pdfx if this book doesn't need print-ready CMYK. Or set `pdfx.transparency: preserve-text` to keep the text live on the pages where that is possible. `pdfx.rasterDpi` sets the resolution of those page images (default 720).");
10296
10110
  }
10297
10111
  const shouldStrip = opts.stripAnnotations ?? config.pdfx.stripAnnotations;
10298
10112
  shouldStripAnnotations = shouldStrip;
@@ -10335,7 +10149,10 @@ class PdfOutput {
10335
10149
  resolvedFlavor: config.pdfx.flavor,
10336
10150
  iccPath: effectiveIccPath,
10337
10151
  stripAnnotations: shouldStripAnnotations,
10338
- blackText: pdfxMode ? config.pdfx.blackText : null
10152
+ blackText: pdfxMode ? config.pdfx.blackText : null,
10153
+ transparency: pdfxMode ? config.pdfx.transparency : null,
10154
+ flattenPages: pdfxMode ? config.pdfx.flattenPages : null,
10155
+ rasterDpi: pdfxMode ? config.pdfx.rasterDpi ?? null : null
10339
10156
  }
10340
10157
  }, `Wrote: ${reportedPdf}`, { htmlPath: htmlFile, pdfPath: reportedPdf }, pdfName, engineDiagnostics);
10341
10158
  } finally {
@@ -10372,6 +10189,35 @@ async function runBuild(opts) {
10372
10189
  await fsp2.rm(ctx.workDir, { recursive: true, force: true });
10373
10190
  }
10374
10191
  }
10192
+ var FALLBACK_CAUSE = {
10193
+ listed: "listed in pdfx.flattenPages",
10194
+ "text-transparency": "text drawn with transparency or blending (opacity, mix-blend-mode, a soft mask, a translucent text-shadow); keep that text opaque",
10195
+ "type3-font": "text in a Type 3 font: for a synthetic bold (a weight the font has no face for), use a face with that weight or set the font's own weight; for -webkit-text-stroke, remove the stroke or accept this fallback",
10196
+ "text-pattern-or-clip": 'text filled with a pattern or used as a clip (SVG fill="url(#…)", gradient text, background-clip: text)',
10197
+ gradient: "a repeating or conic gradient; use a linear or radial gradient, or a small tiled image"
10198
+ };
10199
+ function reportTransparentPlan({ split, artOnly, kept }, pdfxMode, dpi, pageList) {
10200
+ const one = (pages, singular, plural) => pages.length === 1 ? singular : plural;
10201
+ if (split.length > 0) {
10202
+ log.warn(`${pageList(split)} ${one(split, "uses", "use")} transparency, which PDF/${pdfxMode === "x1a" ? "X-1a" : "X-3"} (PDF 1.3) cannot represent. Each was flattened to a ${dpi} DPI picture with its text kept live on top (pdfx.transparency: preserve-text), so fonts and searchable text survive. On those pages, rules, backgrounds and shapes are raster, and text always sits above the artwork: text another element covers shows through. List a page in pdfx.flattenPages to flatten it whole instead.`);
10203
+ }
10204
+ if (artOnly.length > 0) {
10205
+ log.info(`${pageList(artOnly)} ${one(artOnly, "uses", "use")} transparency but ${one(artOnly, "shows", "show")} no text, so there is no text to keep: Ghostscript flattens ${one(artOnly, "it", "each")} whole (resolution: pdfx.rasterDpi).`);
10206
+ }
10207
+ if (kept.size > 0) {
10208
+ const pages = [...kept.keys()];
10209
+ const byCause = new Map;
10210
+ for (const [n, cause] of kept)
10211
+ byCause.set(cause, [...byCause.get(cause) ?? [], n]);
10212
+ const lines = [...byCause].map(([cause, ns]) => ` - ${one(ns, "page", "pages")} ${ns.join(", ")}: ${FALLBACK_CAUSE[cause]}`);
10213
+ log.warn(`${pageList(pages)} could not keep ${one(pages, "its", "their")} text live; Ghostscript will flatten ${one(pages, "that page", "each of those pages")} into one raster image, losing its embedded fonts and searchable text. Each page lists the first cause found; fixing it can reveal another:
10214
+ ` + lines.join(`
10215
+ `));
10216
+ }
10217
+ if (split.length === 0 && [...kept.values()].some((cause) => cause !== "listed")) {
10218
+ log.warn("pdfx.transparency: preserve-text kept no page's text live in this book: every page with transparency and text falls back (see above).");
10219
+ }
10220
+ }
10375
10221
 
10376
10222
  // src/lib/open-path.ts
10377
10223
  import { spawn } from "node:child_process";
@@ -10405,7 +10251,7 @@ function openPath(filePath) {
10405
10251
  import path16 from "path";
10406
10252
  import { tmpdir } from "os";
10407
10253
  import { randomBytes as randomBytes2 } from "crypto";
10408
- import { readdir as readdir4, readFile as readFile26, writeFile as writeFile5 } from "node:fs/promises";
10254
+ import { readdir as readdir4, readFile as readFile25, writeFile as writeFile5 } from "node:fs/promises";
10409
10255
 
10410
10256
  // src/utils/file-utils.ts
10411
10257
  import { promises as fs3 } from "fs";
@@ -10523,8 +10369,16 @@ async function renderPreviewBook(inputPath, config, opts) {
10523
10369
  });
10524
10370
  }
10525
10371
  function injectPreviewScripts(html, page) {
10526
- const area = page ? JSON.stringify({ width: page.width, height: page.height, bleed: page.bleed ?? 0, safe: page.safe ?? 0 }) : "";
10527
- const meta = area ? ` <meta name="gp-print-area" content='${area}'>
10372
+ const area = page ? JSON.stringify({
10373
+ width: page.width,
10374
+ height: page.height,
10375
+ bleed: page.bleed ?? 0,
10376
+ safe: page.safe ?? 0,
10377
+ artSafe: page.artSafe,
10378
+ preset: page.preset,
10379
+ spec: page.spec
10380
+ }) : "";
10381
+ const meta = area ? ` <meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>
10528
10382
  ` : "";
10529
10383
  const scripts = meta + ` <script src="/engine/gutterpress-viewer.js"></script>
10530
10384
  ` + `<script src="/preview/scripts/preview-interface.js"></script>
@@ -10560,7 +10414,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
10560
10414
  for (const ref of nextImageRefs)
10561
10415
  imageRefs.add(ref);
10562
10416
  }
10563
- await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, config.page), "utf-8");
10417
+ await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, config.page && { ...config.page, preset: config.preset, spec: config.spec }), "utf-8");
10564
10418
  }
10565
10419
  function isDotPathUnderRoot(candidatePath, root) {
10566
10420
  const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -10958,7 +10812,7 @@ async function cleanupOrphanTempDirs() {
10958
10812
  const dir = path16.join(TEMP_DIR_BASE, entry);
10959
10813
  const pidFile = path16.join(dir, PID_FILE_NAME);
10960
10814
  try {
10961
- const raw = await readFile26(pidFile, "utf8");
10815
+ const raw = await readFile25(pidFile, "utf8");
10962
10816
  const pid = parseInt(raw.trim(), 10);
10963
10817
  if (!isProcessAlive(pid)) {
10964
10818
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -11065,7 +10919,7 @@ function createServerState(inputPath, tempDir, config, options) {
11065
10919
  import http from "node:http";
11066
10920
  import net from "node:net";
11067
10921
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
11068
- import { readFile as readFile27, stat as stat5 } from "node:fs/promises";
10922
+ import { readFile as readFile26, stat as stat4 } from "node:fs/promises";
11069
10923
  import path17 from "path";
11070
10924
  import { WebSocket, WebSocketServer } from "ws";
11071
10925
  var HMR_PATH = "/__gutterpress-hmr";
@@ -11124,7 +10978,7 @@ async function findAvailablePort(startPort, host = "127.0.0.1") {
11124
10978
  async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders = {}) {
11125
10979
  let filePath = absPath;
11126
10980
  try {
11127
- const s = await stat5(filePath);
10981
+ const s = await stat4(filePath);
11128
10982
  if (s.isDirectory())
11129
10983
  filePath = path17.join(filePath, "index.html");
11130
10984
  } catch {
@@ -11134,7 +10988,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
11134
10988
  }
11135
10989
  let data;
11136
10990
  try {
11137
- data = await readFile27(filePath);
10991
+ data = await readFile26(filePath);
11138
10992
  } catch {
11139
10993
  res.writeHead(404);
11140
10994
  res.end("Not Found");
@@ -11163,7 +11017,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11163
11017
  }
11164
11018
  let fileStat;
11165
11019
  try {
11166
- fileStat = await stat5(absPath, { bigint: true });
11020
+ fileStat = await stat4(absPath, { bigint: true });
11167
11021
  } catch {
11168
11022
  await serveStatic(absPath, res);
11169
11023
  return;
@@ -11183,7 +11037,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11183
11037
  let data;
11184
11038
  if (!validator || validator.stamp !== stamp) {
11185
11039
  try {
11186
- data = await readFile27(absPath);
11040
+ data = await readFile26(absPath);
11187
11041
  } catch {
11188
11042
  await serveStatic(absPath, res);
11189
11043
  return;
@@ -11211,7 +11065,7 @@ async function isMissingReferencedImage(pathname, absPath, refs) {
11211
11065
  if (!refs.has(imageRefKey(pathname)))
11212
11066
  return false;
11213
11067
  try {
11214
- await stat5(absPath);
11068
+ await stat4(absPath);
11215
11069
  return false;
11216
11070
  } catch (err) {
11217
11071
  return err?.code === "ENOENT";
@@ -11493,7 +11347,7 @@ async function startPreviewServer(options) {
11493
11347
  }
11494
11348
 
11495
11349
  // src/lib/project-scaffold.ts
11496
- import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile28, stat as stat6, writeFile as writeFile6 } from "node:fs/promises";
11350
+ import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile27, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
11497
11351
  import { constants as FS, existsSync as existsSync12 } from "node:fs";
11498
11352
  import path18 from "node:path";
11499
11353
 
@@ -11692,7 +11546,7 @@ async function scaffoldProject(options) {
11692
11546
  async function firstSourceFile(manifestPath) {
11693
11547
  let text;
11694
11548
  try {
11695
- text = await readFile28(manifestPath, "utf8");
11549
+ text = await readFile27(manifestPath, "utf8");
11696
11550
  } catch {
11697
11551
  return;
11698
11552
  }
@@ -11704,7 +11558,7 @@ async function firstSourceFile(manifestPath) {
11704
11558
  return m ? m[1].trim() : undefined;
11705
11559
  }
11706
11560
  async function fillTemplateFile(filePath, substitutions) {
11707
- let text = await readFile28(filePath, "utf8");
11561
+ let text = await readFile27(filePath, "utf8");
11708
11562
  for (const [token, value] of Object.entries(substitutions)) {
11709
11563
  text = text.split(token).join(value);
11710
11564
  }
@@ -11712,7 +11566,7 @@ async function fillTemplateFile(filePath, substitutions) {
11712
11566
  }
11713
11567
 
11714
11568
  // src/lib/extension-scaffold.ts
11715
- import { access as access2, mkdir as mkdir8, readFile as readFile29, writeFile as writeFile7 } from "node:fs/promises";
11569
+ import { access as access2, mkdir as mkdir8, readFile as readFile28, writeFile as writeFile7 } from "node:fs/promises";
11716
11570
  import { constants as FS2 } from "node:fs";
11717
11571
  import path19 from "node:path";
11718
11572
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -11829,7 +11683,7 @@ async function scaffoldExtension(options) {
11829
11683
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
11830
11684
  const dest = path19.join(extensionDir, relDest);
11831
11685
  await mkdir8(path19.dirname(dest), { recursive: true });
11832
- const text = await readFile29(src, "utf8");
11686
+ const text = await readFile28(src, "utf8");
11833
11687
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
11834
11688
  await writeFile7(dest, substitute(text, table), "utf8");
11835
11689
  written.push(relDest);
@@ -11874,7 +11728,7 @@ var SKIP_ENTRIES = new Set([
11874
11728
 
11875
11729
  // src/lib/extension-import.ts
11876
11730
  import { existsSync as existsSync13 } from "node:fs";
11877
- import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile30, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11731
+ import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile29, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11878
11732
  import { tmpdir as tmpdir2 } from "node:os";
11879
11733
  import path20 from "node:path";
11880
11734
  import { unzipSync } from "fflate";
@@ -11947,7 +11801,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
11947
11801
  if (!existsSync13(cssPath)) {
11948
11802
  throw new Error("A theme must contain a theme.css file.");
11949
11803
  }
11950
- const css = await readFile30(cssPath, "utf8");
11804
+ const css = await readFile29(cssPath, "utf8");
11951
11805
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
11952
11806
  if (reject) {
11953
11807
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -11975,7 +11829,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
11975
11829
  for (let i = 0;i < extraSheets.length; i++) {
11976
11830
  const rel = extraSheets[i];
11977
11831
  const sheetPath = extraSheetPaths[i];
11978
- const sheetCss = await readFile30(sheetPath, "utf8");
11832
+ const sheetCss = await readFile29(sheetPath, "utf8");
11979
11833
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
11980
11834
  if (sheetReject) {
11981
11835
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -12008,7 +11862,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
12008
11862
  let pkg = {};
12009
11863
  if (metaExists) {
12010
11864
  try {
12011
- const parsed = JSON.parse(await readFile30(file, "utf8"));
11865
+ const parsed = JSON.parse(await readFile29(file, "utf8"));
12012
11866
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
12013
11867
  pkg = parsed;
12014
11868
  }
@@ -12110,11 +11964,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
12110
11964
  async function importExtensionFromFile(projectDir, filePath) {
12111
11965
  const ext = path20.extname(filePath).toLowerCase();
12112
11966
  if (ext === ".zip") {
12113
- const buf = await readFile30(filePath);
11967
+ const buf = await readFile29(filePath);
12114
11968
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
12115
11969
  }
12116
11970
  if (ext === ".css") {
12117
- const css = await readFile30(filePath, "utf8");
11971
+ const css = await readFile29(filePath, "utf8");
12118
11972
  return importExtensionFromCssText(projectDir, css, prettify(path20.basename(filePath, ".css")));
12119
11973
  }
12120
11974
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -12898,7 +12752,7 @@ var drivethrurpgProvider = {
12898
12752
  };
12899
12753
 
12900
12754
  // src/lib/publish/providers/gdrive.ts
12901
- import { mkdtemp as mkdtemp3, readFile as readFile31, readdir as readdir6, rm as rm5, stat as stat7, writeFile as writeFile10 } from "node:fs/promises";
12755
+ import { mkdtemp as mkdtemp3, readFile as readFile30, readdir as readdir6, rm as rm5, stat as stat6, writeFile as writeFile10 } from "node:fs/promises";
12902
12756
  import { tmpdir as tmpdir3 } from "node:os";
12903
12757
  import path22 from "node:path";
12904
12758
  import { Zip, ZipDeflate } from "fflate";
@@ -13290,12 +13144,12 @@ async function collectZipEntries(root, dir, out, onWarn) {
13290
13144
  await collectZipEntries(root, full, out, onWarn);
13291
13145
  } else if (entry.isFile()) {
13292
13146
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13293
- out[rel] = await readFile31(full);
13147
+ out[rel] = await readFile30(full);
13294
13148
  } else if (entry.isSymbolicLink()) {
13295
- const resolved = await stat7(full).catch(() => null);
13149
+ const resolved = await stat6(full).catch(() => null);
13296
13150
  if (resolved?.isFile()) {
13297
13151
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13298
- out[rel] = await readFile31(full);
13152
+ out[rel] = await readFile30(full);
13299
13153
  } else if (resolved?.isDirectory()) {
13300
13154
  await collectZipEntries(root, full, out, onWarn);
13301
13155
  } else {
@@ -13365,7 +13219,7 @@ var gdriveProvider = {
13365
13219
  cleanup: async () => {}
13366
13220
  };
13367
13221
  try {
13368
- const artifactStat = await stat7(source.filePath);
13222
+ const artifactStat = await stat6(source.filePath);
13369
13223
  const about = await driveAbout(fetchImpl, accessToken);
13370
13224
  if (about.quota.limitBytes != null && about.quota.freeBytes != null) {
13371
13225
  if (artifactStat.size > about.quota.freeBytes) {
@@ -13410,7 +13264,7 @@ var gdriveProvider = {
13410
13264
  };
13411
13265
 
13412
13266
  // src/lib/publish/butler.ts
13413
- import { chmod, mkdir as mkdir11, stat as stat8, writeFile as writeFile11 } from "node:fs/promises";
13267
+ import { chmod, mkdir as mkdir11, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
13414
13268
  import path23 from "node:path";
13415
13269
  import { unzipSync as unzipSync2 } from "fflate";
13416
13270
  var BUTLER_DOWNLOAD_TIMEOUT_MS = 300000;
@@ -13431,7 +13285,7 @@ function butlerBinaryName(platform = process.platform) {
13431
13285
  }
13432
13286
  async function fileExists2(p) {
13433
13287
  try {
13434
- return (await stat8(p)).isFile();
13288
+ return (await stat7(p)).isFile();
13435
13289
  } catch {
13436
13290
  return false;
13437
13291
  }
@@ -13674,7 +13528,7 @@ var kdpProvider = {
13674
13528
  };
13675
13529
 
13676
13530
  // src/lib/publish/providers/local-folder.ts
13677
- import { cp as cp4, mkdir as mkdir12, stat as stat9 } from "node:fs/promises";
13531
+ import { cp as cp4, mkdir as mkdir12, stat as stat8 } from "node:fs/promises";
13678
13532
  import path24 from "node:path";
13679
13533
  var LOCAL_PUBLISH_DEFAULT_DIR = "dist";
13680
13534
  var info8 = {
@@ -13701,7 +13555,7 @@ var localFolderProvider = {
13701
13555
  async preflight(req) {
13702
13556
  const dir = localPublishDir(req);
13703
13557
  try {
13704
- const s = await stat9(dir);
13558
+ const s = await stat8(dir);
13705
13559
  if (!s.isDirectory()) {
13706
13560
  return [
13707
13561
  { severity: "error", id: "local/not-a-folder", message: `${dir} exists but is not a folder.` }
@@ -13956,7 +13810,7 @@ function publishProviderFor(id) {
13956
13810
  }
13957
13811
 
13958
13812
  // src/lib/publish/run-publish.ts
13959
- import { readdir as readdir7, stat as stat10 } from "node:fs/promises";
13813
+ import { readdir as readdir7, stat as stat9 } from "node:fs/promises";
13960
13814
  import path25 from "node:path";
13961
13815
  function resolvePublishFormat(info10, providerConfig) {
13962
13816
  if (info10.formats && info10.formats.length > 0) {
@@ -13998,7 +13852,7 @@ var PDF_HINT = "Build the PDF first (gutterpress build, or export it from the ap
13998
13852
  var HTML_HINT = "Build the website export first (gutterpress build --format html).";
13999
13853
  async function artifactIssues(artifact) {
14000
13854
  try {
14001
- const s = await stat10(artifact.path);
13855
+ const s = await stat9(artifact.path);
14002
13856
  if (artifact.format === "pdf" && !s.isFile()) {
14003
13857
  return [
14004
13858
  {
@@ -14044,7 +13898,7 @@ async function artifactIssues(artifact) {
14044
13898
  var BUILD_FINGERPRINT_FILENAME = "build-fingerprint.json";
14045
13899
  async function htmlDirIssues(dir) {
14046
13900
  const issues = [];
14047
- const isThere = async (rel) => stat10(path25.join(dir, rel)).then(() => true, () => false);
13901
+ const isThere = async (rel) => stat9(path25.join(dir, rel)).then(() => true, () => false);
14048
13902
  if (!await isThere(BOOK_HTML)) {
14049
13903
  issues.push({
14050
13904
  severity: "error",
@@ -14282,7 +14136,7 @@ import git8 from "isomorphic-git";
14282
14136
  // src/lib/remote-auth/scorched-earth.ts
14283
14137
  import git9 from "isomorphic-git";
14284
14138
  // src/lib/publish/selections.ts
14285
- import { chmod as chmod2, mkdir as mkdir13, readFile as readFile32, writeFile as writeFile12 } from "node:fs/promises";
14139
+ import { chmod as chmod2, mkdir as mkdir13, readFile as readFile31, writeFile as writeFile12 } from "node:fs/promises";
14286
14140
  import path26 from "node:path";
14287
14141
  class PublishSelectionsStore {
14288
14142
  filePath;
@@ -14292,7 +14146,7 @@ class PublishSelectionsStore {
14292
14146
  }
14293
14147
  async read() {
14294
14148
  try {
14295
- const raw = await readFile32(this.filePath, "utf8");
14149
+ const raw = await readFile31(this.filePath, "utf8");
14296
14150
  const parsed = JSON.parse(raw);
14297
14151
  if (parsed && typeof parsed === "object") {
14298
14152
  return {