gutterpress 0.11.19 → 0.11.20-alpha.2

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 (32) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-czbaj4qf.js → build-qmjg9r2d.js} +4 -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-cgbzdrwz.js → cli-7z29z4wz.js} +1 -1
  6. package/dist/{cli-dzw28t3s.js → cli-gh0ym80r.js} +2 -1
  7. package/dist/{cli-xx94a1k6.js → cli-j802s31j.js} +5 -5
  8. package/dist/{cli-aw614ztv.js → cli-zacevwcy.js} +299 -184
  9. package/dist/cli.js +12 -12
  10. package/dist/{doctor-6e9perj7.js → doctor-fe7tcs4v.js} +2 -2
  11. package/dist/{engine-fe0n7v9p.js → engine-9xb1ky8d.js} +1 -1
  12. package/dist/{engine-zehw6tkv.js → engine-yax79k9m.js} +2 -2
  13. package/dist/{ext-kavfk1zn.js → ext-ahwxt6gv.js} +4 -4
  14. package/dist/{gutterpress-viewer-k4mpqjh0.js → gutterpress-viewer-retj6ym2.js} +515 -281
  15. package/dist/{index-jt552wtf.js → index-3nr5rzzp.js} +5 -5
  16. package/dist/{index-b866g2ph.js → index-6xwtb7cw.js} +1 -1
  17. package/dist/{index-g7vszjqf.js → index-razpty47.js} +298 -182
  18. package/dist/index.js +3 -3
  19. package/dist/lib/ghostscript.d.ts +7 -0
  20. package/dist/lib/presets.d.ts +3 -1
  21. package/dist/{manifest.schema-5pzhscqh.json → manifest.schema-74s1ykyv.json} +20 -1
  22. package/dist/{new-ckpas1ya.js → new-ftd4fec4.js} +4 -4
  23. package/dist/{preflight-yh57q9nn.js → preflight-8sqarcsr.js} +4 -4
  24. package/dist/preview/file-watcher.d.ts +16 -2
  25. package/dist/{preview-bridge-evecsh4w.js → preview-bridge-8wjcjec3.js} +3 -2
  26. package/dist/{preview-interface-2n1d9pbf.js → preview-interface-nnmrxyqm.js} +37 -41
  27. package/dist/{preview-a4x2ezjp.js → preview-q76xn95h.js} +4 -4
  28. package/dist/{preview-shell-hyp7g158.js → preview-shell-wk9k3gcd.js} +18 -6
  29. package/dist/{publish-sfn5hmnx.js → publish-7gdy6gbf.js} +4 -4
  30. package/dist/schema/manifest.types.d.ts +38 -3
  31. package/dist/{validate-281rsdsv.js → validate-e3ryegce.js} +4 -4
  32. package/package.json +1 -1
@@ -9,7 +9,7 @@ import {
9
9
  getAssetsDir,
10
10
  lastPageMode,
11
11
  parseSize
12
- } from "./index-jt552wtf.js";
12
+ } from "./index-3nr5rzzp.js";
13
13
  import {
14
14
  INSTALL_HINTS,
15
15
  enhancedPath,
@@ -141,6 +141,11 @@ function resolvePort(raw) {
141
141
  // src/lib/presets.ts
142
142
  var PRESET_IDS = ["dtrpg", "book", "custom"];
143
143
  var DTRPG_PRESET = {
144
+ id: "dtrpg",
145
+ spec: {
146
+ label: "DriveThruRPG — Quick Specifications for Print Books",
147
+ url: "https://help.drivethrupartners.com/hc/en-us/articles/12780800178583-Quick-Specifications-for-Print-Books"
148
+ },
144
149
  defaultTargets: ["dtrpg"],
145
150
  extensions: [],
146
151
  source: {
@@ -151,13 +156,16 @@ var DTRPG_PRESET = {
151
156
  flavor: "x1a",
152
157
  icc: "profiles/CGATS21_CRPC1.icc",
153
158
  stripAnnotations: true,
154
- blackText: "k-only"
159
+ blackText: "k-only",
160
+ rasterDpi: undefined
155
161
  },
156
162
  page: {
157
163
  width: 621,
158
164
  height: 810,
159
165
  tolerance: 0.5,
160
- bleed: 9
166
+ bleed: 9,
167
+ safe: 36,
168
+ artSafe: 18
161
169
  },
162
170
  ink: {
163
171
  maxTac: 240,
@@ -186,6 +194,10 @@ var DTRPG_PRESET = {
186
194
  "pdf.print.embedded-fonts": {
187
195
  enabled: true,
188
196
  severity: "error"
197
+ },
198
+ "pdf.print.safe-margins": {
199
+ enabled: true,
200
+ severity: "error"
189
201
  }
190
202
  },
191
203
  source: {
@@ -218,6 +230,7 @@ var DTRPG_PRESET = {
218
230
  }
219
231
  };
220
232
  var BOOK_PRESET = {
233
+ id: "book",
221
234
  defaultTargets: [],
222
235
  extensions: [],
223
236
  source: {
@@ -228,13 +241,16 @@ var BOOK_PRESET = {
228
241
  flavor: "x1a",
229
242
  icc: "profiles/CGATS21_CRPC1.icc",
230
243
  stripAnnotations: true,
231
- blackText: "icc"
244
+ blackText: "icc",
245
+ rasterDpi: undefined
232
246
  },
233
247
  page: {
234
248
  width: 432,
235
249
  height: 648,
236
250
  tolerance: 0.5,
237
- bleed: undefined
251
+ bleed: undefined,
252
+ safe: 18,
253
+ artSafe: undefined
238
254
  },
239
255
  ink: {
240
256
  maxTac: 400,
@@ -256,6 +272,10 @@ var BOOK_PRESET = {
256
272
  enabled: true,
257
273
  severity: "error"
258
274
  },
275
+ "pdf.print.safe-margins": {
276
+ enabled: true,
277
+ severity: "warning"
278
+ },
259
279
  "pdf.print.pdfx-markers": false,
260
280
  "pdf.print.pdfx-metadata": false
261
281
  },
@@ -290,6 +310,7 @@ var BOOK_PRESET = {
290
310
  };
291
311
  var CUSTOM_PRESET = {
292
312
  ...BOOK_PRESET,
313
+ id: "custom",
293
314
  page: null,
294
315
  defaultTargets: []
295
316
  };
@@ -335,7 +356,8 @@ var DTRPG_TARGET = {
335
356
  "pdf.structure.qpdf": { enabled: true, severity: "error" },
336
357
  "pdf.print.pdfx-markers": { enabled: true, severity: "error" },
337
358
  "pdf.print.pdfx-metadata": { enabled: true, severity: "error" },
338
- "pdf.print.embedded-fonts": { enabled: true, severity: "error" }
359
+ "pdf.print.embedded-fonts": { enabled: true, severity: "error" },
360
+ "pdf.print.safe-margins": { enabled: true, severity: "error" }
339
361
  },
340
362
  assets: {
341
363
  allowedColorSpaces: [...DTRPG_PRESET.validate.assets.allowedColorSpaces]
@@ -350,7 +372,8 @@ var DTRPG_TARGET = {
350
372
  "pdf.structure.qpdf",
351
373
  "pdf.print.pdfx-markers",
352
374
  "pdf.print.pdfx-metadata",
353
- "pdf.print.embedded-fonts"
375
+ "pdf.print.embedded-fonts",
376
+ "pdf.print.safe-margins"
354
377
  ],
355
378
  requiredTools: ["qpdf", "gs"]
356
379
  };
@@ -365,6 +388,7 @@ var ITCH_TARGET = {
365
388
  "pdf.print.pdfx-markers": false,
366
389
  "pdf.print.pdfx-metadata": false,
367
390
  "pdf.print.ink-coverage": false,
391
+ "pdf.print.safe-margins": false,
368
392
  "asset.image.tac-raster": false
369
393
  },
370
394
  assets: {
@@ -588,7 +612,9 @@ function resolveCustomPage(c, m) {
588
612
  width,
589
613
  height,
590
614
  tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5,
591
- bleed: c.page?.bleed ?? m.page?.bleed
615
+ bleed: c.page?.bleed ?? m.page?.bleed,
616
+ safe: c.page?.safe ?? m.page?.safe,
617
+ artSafe: c.page?.artSafe ?? m.page?.artSafe
592
618
  };
593
619
  }
594
620
  function resolveWithPreset(cliOverrides, manifest, preset) {
@@ -621,6 +647,10 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
621
647
  if (lastPage !== undefined && lastPage !== "blank" && lastPage !== "short-by-one") {
622
648
  throw new UsageError(`Manifest field \`print.lastPage\` must be "blank" or "short-by-one", got ${JSON.stringify(lastPage)}.`);
623
649
  }
650
+ const rasterDpi = c.pdfx?.rasterDpi ?? m.pdfx?.rasterDpi;
651
+ if (rasterDpi !== undefined && !(Number.isInteger(rasterDpi) && rasterDpi >= 72 && rasterDpi <= 1200)) {
652
+ throw new UsageError(`Manifest field \`pdfx.rasterDpi\` must be a whole number from 72 to 1200, got ${JSON.stringify(rasterDpi)}.`);
653
+ }
624
654
  const rawExtensions = c.extensions ?? m.extensions ?? [];
625
655
  if (!Array.isArray(rawExtensions)) {
626
656
  throw new UsageError("Manifest field `extensions` must be a list of specifiers.");
@@ -629,6 +659,8 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
629
659
  return {
630
660
  title: c.title ?? m.title ?? "Document",
631
661
  authors: c.authors ?? m.authors ?? [],
662
+ preset: preset.id,
663
+ spec: preset.spec,
632
664
  styles: c.styles ?? m.styles,
633
665
  extensions,
634
666
  targets: resolveTargets(c.targets ?? m.targets, preset.defaultTargets),
@@ -5935,6 +5967,7 @@ async function convertToPdfxCmyk(inputPdf, outPdf, config) {
5935
5967
  "-dDownsampleColorImages=false",
5936
5968
  "-dDownsampleGrayImages=false",
5937
5969
  "-dDownsampleMonoImages=false",
5970
+ ...config.rasterDpi ? [`-r${config.rasterDpi}`] : [],
5938
5971
  "-sProcessColorModel=DeviceCMYK",
5939
5972
  "-sColorConversionStrategy=CMYK",
5940
5973
  "-dOverrideICC=true",
@@ -5984,7 +6017,7 @@ import git from "isomorphic-git";
5984
6017
  // package.json
5985
6018
  var package_default = {
5986
6019
  name: "gutterpress",
5987
- version: "0.11.19",
6020
+ version: "0.11.20-alpha.2",
5988
6021
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5989
6022
  author: "itlackey",
5990
6023
  license: "MPL-2.0",
@@ -6977,6 +7010,79 @@ var check3 = {
6977
7010
  };
6978
7011
  registerCheck(check3);
6979
7012
 
7013
+ // src/checks/pdf/safe-margins.ts
7014
+ var TOLERANCE_PT = 0.5;
7015
+ var ABOVE_BASELINE = 0.8;
7016
+ var BELOW_BASELINE = 0.2;
7017
+ var inches = (pt) => `${+(pt / 72).toFixed(2)}in`;
7018
+ var check4 = {
7019
+ id: "pdf.print.safe-margins",
7020
+ name: "Safe margins",
7021
+ 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",
7022
+ category: "pdf",
7023
+ phase: "post-build",
7024
+ async run(ctx) {
7025
+ const { width, height, bleed = 0, safe = 0 } = ctx.config.page;
7026
+ if (!ctx.pdfPath || !safe)
7027
+ return [];
7028
+ const doc = await loadPdf(ctx.pdfPath);
7029
+ if (!doc) {
7030
+ return [inspectionFailed(check4.id, "Could not parse the PDF's text positions.", { file: ctx.pdfPath })];
7031
+ }
7032
+ const rule = ctx.config.spec?.label ?? "page.safe";
7033
+ const results = [];
7034
+ try {
7035
+ for (let pageNum = 1;pageNum <= doc.numPages; pageNum++) {
7036
+ const recto = pageNum % 2 === 1;
7037
+ const trim = {
7038
+ left: recto ? 0 : bleed,
7039
+ right: width - (recto ? bleed : 0),
7040
+ bottom: bleed,
7041
+ top: height - bleed
7042
+ };
7043
+ const { items } = await (await doc.getPage(pageNum)).getTextContent();
7044
+ for (const item of items) {
7045
+ if (!("str" in item) || !item.str.trim())
7046
+ continue;
7047
+ const [a, b, c, d, e, f] = item.transform;
7048
+ const along = item.width / Math.hypot(a, b);
7049
+ const xs = [];
7050
+ const ys = [];
7051
+ for (const s of [0, 1]) {
7052
+ for (const u of [-BELOW_BASELINE, ABOVE_BASELINE]) {
7053
+ xs.push(e + s * along * a + u * c);
7054
+ ys.push(f + s * along * b + u * d);
7055
+ }
7056
+ }
7057
+ const gaps = [
7058
+ [recto ? "binding" : "outside", Math.min(...xs) - trim.left],
7059
+ [recto ? "outside" : "binding", trim.right - Math.max(...xs)],
7060
+ ["bottom", Math.min(...ys) - trim.bottom],
7061
+ ["top", trim.top - Math.max(...ys)]
7062
+ ];
7063
+ const [edge, gap] = gaps.reduce((worst, g) => g[1] < worst[1] ? g : worst);
7064
+ const shortfall = safe - gap;
7065
+ if (shortfall <= TOLERANCE_PT)
7066
+ continue;
7067
+ const text = item.str.trim();
7068
+ results.push(finding(check4.id, {
7069
+ severity: "error",
7070
+ message: `Page ${pageNum}: "${text.slice(0, 40)}${text.length > 40 ? "…" : ""}" is ${inches(shortfall)} too close to the ${edge} edge.`,
7071
+ file: ctx.pdfPath,
7072
+ detail: `${rule}: text must stay at least ${inches(safe)} inside the trim on every edge.`,
7073
+ code: "text-outside-safe-margin",
7074
+ data: { page: pageNum, edge, shortfallPt: +shortfall.toFixed(2) }
7075
+ }));
7076
+ }
7077
+ }
7078
+ } catch {
7079
+ return [inspectionFailed(check4.id, "Could not read the PDF's text positions.", { file: ctx.pdfPath })];
7080
+ }
7081
+ return results;
7082
+ }
7083
+ };
7084
+ registerCheck(check4);
7085
+
6980
7086
  // src/checks/pdf/pdfx-structure.ts
6981
7087
  function isObject(value) {
6982
7088
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -7085,7 +7191,7 @@ function getPdfxMetadataIssues(objects, flavor) {
7085
7191
  }
7086
7192
 
7087
7193
  // src/checks/pdf/pdfx-markers.ts
7088
- var check4 = {
7194
+ var check5 = {
7089
7195
  id: "pdf.print.pdfx-markers",
7090
7196
  name: "PDF/X Markers",
7091
7197
  description: "Checks PDF/X OutputIntent structure via qpdf JSON",
@@ -7104,22 +7210,22 @@ var check4 = {
7104
7210
  const objects = parseQpdfObjectsJson(stdout);
7105
7211
  if (!objects) {
7106
7212
  return [
7107
- inspectionFailed(check4.id, "Unable to parse qpdf JSON output for PDF/X checks.", { file: ctx.pdfPath })
7213
+ inspectionFailed(check5.id, "Unable to parse qpdf JSON output for PDF/X checks.", { file: ctx.pdfPath })
7108
7214
  ];
7109
7215
  }
7110
- return getPdfxOutputIntentIssues(objects).map((message) => finding(check4.id, {
7216
+ return getPdfxOutputIntentIssues(objects).map((message) => finding(check5.id, {
7111
7217
  severity: "error",
7112
7218
  message,
7113
7219
  file: ctx.pdfPath
7114
7220
  }));
7115
7221
  } catch {
7116
7222
  return [
7117
- inspectionFailed(check4.id, "Failed to inspect PDF objects with qpdf for PDF/X markers.", { file: ctx.pdfPath })
7223
+ inspectionFailed(check5.id, "Failed to inspect PDF objects with qpdf for PDF/X markers.", { file: ctx.pdfPath })
7118
7224
  ];
7119
7225
  }
7120
7226
  }
7121
7227
  };
7122
- registerCheck(check4);
7228
+ registerCheck(check5);
7123
7229
 
7124
7230
  // src/lib/pdf-parse.ts
7125
7231
  import { readFile as readFile14 } from "node:fs/promises";
@@ -7172,7 +7278,7 @@ async function getPerPageInkCoverage(pdfPath) {
7172
7278
  }
7173
7279
 
7174
7280
  // src/checks/pdf/color-spaces.ts
7175
- var check5 = {
7281
+ var check6 = {
7176
7282
  id: "pdf.print.color-spaces",
7177
7283
  name: "Color Spaces",
7178
7284
  description: "Checks for forbidden color spaces (DeviceRGB, Lab, Separation, DeviceN)",
@@ -7185,7 +7291,7 @@ var check5 = {
7185
7291
  const results = [];
7186
7292
  if (/\/DeviceRGB/.test(bytes)) {
7187
7293
  results.push({
7188
- checkId: check5.id,
7294
+ checkId: check6.id,
7189
7295
  severity: "error",
7190
7296
  message: "DeviceRGB found (interior must be CMYK or grayscale only).",
7191
7297
  file: ctx.pdfPath
@@ -7193,7 +7299,7 @@ var check5 = {
7193
7299
  }
7194
7300
  if (/\/Lab\b/.test(bytes)) {
7195
7301
  results.push({
7196
- checkId: check5.id,
7302
+ checkId: check6.id,
7197
7303
  severity: "error",
7198
7304
  message: "Lab color space found (not allowed).",
7199
7305
  file: ctx.pdfPath
@@ -7201,7 +7307,7 @@ var check5 = {
7201
7307
  }
7202
7308
  if (/\/Separation/.test(bytes)) {
7203
7309
  results.push({
7204
- checkId: check5.id,
7310
+ checkId: check6.id,
7205
7311
  severity: "error",
7206
7312
  message: "Spot color (Separation) found (not allowed).",
7207
7313
  file: ctx.pdfPath
@@ -7209,7 +7315,7 @@ var check5 = {
7209
7315
  }
7210
7316
  if (/\/DeviceN/.test(bytes)) {
7211
7317
  results.push({
7212
- checkId: check5.id,
7318
+ checkId: check6.id,
7213
7319
  severity: "error",
7214
7320
  message: "Spot color (DeviceN) found (not allowed).",
7215
7321
  file: ctx.pdfPath
@@ -7218,10 +7324,10 @@ var check5 = {
7218
7324
  return results;
7219
7325
  }
7220
7326
  };
7221
- registerCheck(check5);
7327
+ registerCheck(check6);
7222
7328
 
7223
7329
  // src/checks/pdf/embedded-fonts.ts
7224
- var check6 = {
7330
+ var check7 = {
7225
7331
  id: "pdf.print.embedded-fonts",
7226
7332
  name: "Embedded Fonts",
7227
7333
  description: "Verifies all fonts in the PDF are embedded",
@@ -7237,7 +7343,7 @@ var check6 = {
7237
7343
  if (fonts.length === 0) {
7238
7344
  return [
7239
7345
  {
7240
- checkId: check6.id,
7346
+ checkId: check7.id,
7241
7347
  severity: "warning",
7242
7348
  message: "No fonts detected (unexpected).",
7243
7349
  file: ctx.pdfPath,
@@ -7250,7 +7356,7 @@ var check6 = {
7250
7356
  const names = notEmbedded.map((f) => f.name);
7251
7357
  return [
7252
7358
  {
7253
- checkId: check6.id,
7359
+ checkId: check7.id,
7254
7360
  severity: "error",
7255
7361
  message: `Not all fonts are embedded (${names.join(", ")}). Check @font-face and Chromium output.`,
7256
7362
  file: ctx.pdfPath,
@@ -7262,10 +7368,10 @@ var check6 = {
7262
7368
  return [];
7263
7369
  }
7264
7370
  };
7265
- registerCheck(check6);
7371
+ registerCheck(check7);
7266
7372
 
7267
7373
  // src/checks/pdf/ink-coverage.ts
7268
- var check7 = {
7374
+ var check8 = {
7269
7375
  id: "pdf.print.ink-coverage",
7270
7376
  name: "Ink Coverage (TAC)",
7271
7377
  description: "Checks total area coverage (TAC) against maximum ink limits",
@@ -7279,7 +7385,7 @@ var check7 = {
7279
7385
  if (!inkResult.ok) {
7280
7386
  return [
7281
7387
  {
7282
- checkId: check7.id,
7388
+ checkId: check8.id,
7283
7389
  severity: "warning",
7284
7390
  message: "Ink coverage could not be measured",
7285
7391
  file: ctx.pdfPath,
@@ -7304,7 +7410,7 @@ var check7 = {
7304
7410
  }
7305
7411
  return [
7306
7412
  {
7307
- checkId: check7.id,
7413
+ checkId: check8.id,
7308
7414
  severity: "warning",
7309
7415
  message: `Total ink coverage too high on ${offending.length} page(s) (max ${maxTac.toFixed(1)}%, recommended <=${ctx.config.ink.maxTac}%)`,
7310
7416
  file: ctx.pdfPath,
@@ -7328,10 +7434,10 @@ var check7 = {
7328
7434
  ];
7329
7435
  }
7330
7436
  };
7331
- registerCheck(check7);
7437
+ registerCheck(check8);
7332
7438
 
7333
7439
  // src/checks/pdf/rasterized-pages.ts
7334
- var check8 = {
7440
+ var check9 = {
7335
7441
  id: "pdf.print.rasterized-pages",
7336
7442
  name: "Rasterized Pages",
7337
7443
  description: "Detects pages that appear to be fully rasterized (CSS filters, blend modes, transparency)",
@@ -7371,7 +7477,7 @@ var check8 = {
7371
7477
  rasterizedPages.sort((a, b) => a - b);
7372
7478
  return [
7373
7479
  {
7374
- checkId: check8.id,
7480
+ checkId: check9.id,
7375
7481
  severity: "warning",
7376
7482
  message: `Possible rasterized pages detected: ${rasterizedPages.join(", ")}`,
7377
7483
  file: ctx.pdfPath,
@@ -7382,11 +7488,11 @@ var check8 = {
7382
7488
  ];
7383
7489
  }
7384
7490
  };
7385
- registerCheck(check8);
7491
+ registerCheck(check9);
7386
7492
 
7387
7493
  // src/checks/pdf/black-text.ts
7388
7494
  import { readFile as readFile15 } from "node:fs/promises";
7389
- var check9 = {
7495
+ var check10 = {
7390
7496
  id: "pdf.print.black-text",
7391
7497
  name: "Black Text",
7392
7498
  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)",
@@ -7402,7 +7508,7 @@ var check9 = {
7402
7508
  if (found.runs === 0)
7403
7509
  return [];
7404
7510
  return [
7405
- finding(check9.id, {
7511
+ finding(check10.id, {
7406
7512
  severity: "warning",
7407
7513
  message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
7408
7514
  file: ctx.pdfPath,
@@ -7413,17 +7519,17 @@ var check9 = {
7413
7519
  ];
7414
7520
  } catch {
7415
7521
  return [
7416
- inspectionFailed(check9.id, "Could not inspect the PDF's text colours.", {
7522
+ inspectionFailed(check10.id, "Could not inspect the PDF's text colours.", {
7417
7523
  file: ctx.pdfPath
7418
7524
  })
7419
7525
  ];
7420
7526
  }
7421
7527
  }
7422
7528
  };
7423
- registerCheck(check9);
7529
+ registerCheck(check10);
7424
7530
 
7425
7531
  // src/checks/pdf/signature.ts
7426
- var check10 = {
7532
+ var check11 = {
7427
7533
  id: "pdf.print.signature",
7428
7534
  name: "Signature",
7429
7535
  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)",
@@ -7435,7 +7541,7 @@ var check10 = {
7435
7541
  return [];
7436
7542
  const doc = await loadPdf(ctx.pdfPath);
7437
7543
  if (!doc) {
7438
- return [inspectionFailed(check10.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
7544
+ return [inspectionFailed(check11.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
7439
7545
  }
7440
7546
  const size = ctx.config.print.signature;
7441
7547
  const pages = doc.numPages;
@@ -7444,7 +7550,7 @@ var check10 = {
7444
7550
  const data = { pages, signature: size };
7445
7551
  if (pages % size !== 0) {
7446
7552
  return [
7447
- finding(check10.id, {
7553
+ finding(check11.id, {
7448
7554
  severity: "error",
7449
7555
  message: `Page count ${pages} does not fit a signature of ${size}: it must be a multiple of ${size}, or one short of one.`,
7450
7556
  file: ctx.pdfPath,
@@ -7460,10 +7566,10 @@ var check10 = {
7460
7566
  if (!await pageHasMarks(doc, pages))
7461
7567
  return [];
7462
7568
  } catch {
7463
- return [inspectionFailed(check10.id, "Could not read the last page.", { file: ctx.pdfPath })];
7569
+ return [inspectionFailed(check11.id, "Could not read the last page.", { file: ctx.pdfPath })];
7464
7570
  }
7465
7571
  return [
7466
- finding(check10.id, {
7572
+ finding(check11.id, {
7467
7573
  severity: "error",
7468
7574
  message: `The last page (${pages}) has content, but the printer needs it blank for its own information.`,
7469
7575
  file: ctx.pdfPath,
@@ -7474,10 +7580,10 @@ var check10 = {
7474
7580
  ];
7475
7581
  }
7476
7582
  };
7477
- registerCheck(check10);
7583
+ registerCheck(check11);
7478
7584
 
7479
7585
  // src/checks/pdf/bookmarks.ts
7480
- var check11 = {
7586
+ var check12 = {
7481
7587
  id: "pdf.nav.bookmarks",
7482
7588
  name: "Bookmarks",
7483
7589
  description: "Checks for PDF outline (bookmarks) tree",
@@ -7491,14 +7597,14 @@ var check11 = {
7491
7597
  const doc = await loadPdf(ctx.pdfPath);
7492
7598
  if (!doc) {
7493
7599
  return [
7494
- inspectionFailed(check11.id, "Could not inspect PDF for bookmarks.", {
7600
+ inspectionFailed(check12.id, "Could not inspect PDF for bookmarks.", {
7495
7601
  file: ctx.pdfPath
7496
7602
  })
7497
7603
  ];
7498
7604
  }
7499
7605
  if (await getOutlineCount(doc) === 0) {
7500
7606
  return [
7501
- finding(check11.id, {
7607
+ finding(check12.id, {
7502
7608
  severity: "warning",
7503
7609
  message: "PDF does not contain bookmarks (outline tree).",
7504
7610
  file: ctx.pdfPath
@@ -7508,10 +7614,10 @@ var check11 = {
7508
7614
  return [];
7509
7615
  }
7510
7616
  };
7511
- registerCheck(check11);
7617
+ registerCheck(check12);
7512
7618
 
7513
7619
  // src/checks/pdf/toc-links.ts
7514
- var check12 = {
7620
+ var check13 = {
7515
7621
  id: "pdf.nav.toc-links",
7516
7622
  name: "TOC Links",
7517
7623
  description: "Detects link annotations on TOC pages",
@@ -7526,7 +7632,7 @@ var check12 = {
7526
7632
  if (!doc) {
7527
7633
  return [
7528
7634
  {
7529
- checkId: check12.id,
7635
+ checkId: check13.id,
7530
7636
  severity: "warning",
7531
7637
  message: "Could not inspect PDF for TOC links.",
7532
7638
  file: ctx.pdfPath
@@ -7537,7 +7643,7 @@ var check12 = {
7537
7643
  if (!hasLinks) {
7538
7644
  return [
7539
7645
  {
7540
- checkId: check12.id,
7646
+ checkId: check13.id,
7541
7647
  severity: "warning",
7542
7648
  message: "No link annotations found in PDF (expected for TOC).",
7543
7649
  file: ctx.pdfPath
@@ -7547,10 +7653,10 @@ var check12 = {
7547
7653
  return [];
7548
7654
  }
7549
7655
  };
7550
- registerCheck(check12);
7656
+ registerCheck(check13);
7551
7657
 
7552
7658
  // src/checks/pdf/cross-refs.ts
7553
- var check13 = {
7659
+ var check14 = {
7554
7660
  id: "pdf.nav.cross-refs",
7555
7661
  name: "Cross References",
7556
7662
  description: "Counts and verifies internal link annotations",
@@ -7563,7 +7669,7 @@ var check13 = {
7563
7669
  if (!doc) {
7564
7670
  return [
7565
7671
  {
7566
- checkId: check13.id,
7672
+ checkId: check14.id,
7567
7673
  severity: "info",
7568
7674
  message: "Could not inspect PDF for cross-references.",
7569
7675
  file: ctx.pdfPath
@@ -7573,7 +7679,7 @@ var check13 = {
7573
7679
  const linkCount = await countLinkAnnotations(doc);
7574
7680
  return [
7575
7681
  {
7576
- checkId: check13.id,
7682
+ checkId: check14.id,
7577
7683
  severity: "info",
7578
7684
  message: `PDF contains ${linkCount} internal link annotation${linkCount !== 1 ? "s" : ""}.`,
7579
7685
  file: ctx.pdfPath
@@ -7581,10 +7687,10 @@ var check13 = {
7581
7687
  ];
7582
7688
  }
7583
7689
  };
7584
- registerCheck(check13);
7690
+ registerCheck(check14);
7585
7691
 
7586
7692
  // src/checks/pdf/page-labels.ts
7587
- var check14 = {
7693
+ var check15 = {
7588
7694
  id: "pdf.nav.page-labels",
7589
7695
  name: "Page Labels",
7590
7696
  description: "Verifies PDF page labels / folio numbering",
@@ -7599,7 +7705,7 @@ var check14 = {
7599
7705
  const labels = await getPageLabels(doc);
7600
7706
  return [
7601
7707
  {
7602
- checkId: check14.id,
7708
+ checkId: check15.id,
7603
7709
  severity: "info",
7604
7710
  message: labels && labels.length > 0 ? "PDF contains page labels." : "PDF does not contain explicit page labels.",
7605
7711
  file: ctx.pdfPath
@@ -7607,10 +7713,10 @@ var check14 = {
7607
7713
  ];
7608
7714
  }
7609
7715
  };
7610
- registerCheck(check14);
7716
+ registerCheck(check15);
7611
7717
 
7612
7718
  // src/checks/pdf/image-resolution.ts
7613
- var check15 = {
7719
+ var check16 = {
7614
7720
  id: "pdf.print.image-resolution",
7615
7721
  name: "Image Resolution",
7616
7722
  description: "Checks DPI of images embedded in the PDF",
@@ -7637,7 +7743,7 @@ var check15 = {
7637
7743
  lowResPages.sort((a, b) => a - b);
7638
7744
  return [
7639
7745
  {
7640
- checkId: check15.id,
7746
+ checkId: check16.id,
7641
7747
  severity: "warning",
7642
7748
  message: `Low-resolution images (below ${minDpi} DPI) found on pages: ${lowResPages.join(", ")}`,
7643
7749
  file: ctx.pdfPath
@@ -7647,10 +7753,10 @@ var check15 = {
7647
7753
  return [];
7648
7754
  }
7649
7755
  };
7650
- registerCheck(check15);
7756
+ registerCheck(check16);
7651
7757
 
7652
7758
  // src/checks/pdf/transparency.ts
7653
- var check16 = {
7759
+ var check17 = {
7654
7760
  id: "pdf.print.transparency",
7655
7761
  name: "Transparency",
7656
7762
  description: "Checks for transparency markers (/Transparency, /SMask, /BM) in the PDF",
@@ -7672,7 +7778,7 @@ var check16 = {
7672
7778
  if (found.length > 0) {
7673
7779
  return [
7674
7780
  {
7675
- checkId: check16.id,
7781
+ checkId: check17.id,
7676
7782
  severity: "warning",
7677
7783
  message: `Transparency detected in PDF: ${found.join(", ")}. This may cause issues with some printers.`,
7678
7784
  file: ctx.pdfPath
@@ -7682,7 +7788,7 @@ var check16 = {
7682
7788
  return [];
7683
7789
  }
7684
7790
  };
7685
- registerCheck(check16);
7791
+ registerCheck(check17);
7686
7792
 
7687
7793
  // src/checks/pdf/bleed.ts
7688
7794
  function findBox(bytes, box) {
@@ -7692,7 +7798,7 @@ function findBox(bytes, box) {
7692
7798
  return null;
7693
7799
  return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
7694
7800
  }
7695
- var check17 = {
7801
+ var check18 = {
7696
7802
  id: "pdf.print.bleed",
7697
7803
  name: "Bleed Box",
7698
7804
  description: "Compares MediaBox vs TrimBox/BleedBox to verify bleed area",
@@ -7706,7 +7812,7 @@ var check17 = {
7706
7812
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
7707
7813
  if (!bytes) {
7708
7814
  return [
7709
- inspectionFailed(check17.id, "Could not inspect PDF for bleed boxes.", {
7815
+ inspectionFailed(check18.id, "Could not inspect PDF for bleed boxes.", {
7710
7816
  file: ctx.pdfPath
7711
7817
  })
7712
7818
  ];
@@ -7716,14 +7822,14 @@ var check17 = {
7716
7822
  const bleed = findBox(bytes, "BleedBox");
7717
7823
  if (!media) {
7718
7824
  return [
7719
- inspectionFailed(check17.id, "Could not parse MediaBox from PDF.", {
7825
+ inspectionFailed(check18.id, "Could not parse MediaBox from PDF.", {
7720
7826
  file: ctx.pdfPath
7721
7827
  })
7722
7828
  ];
7723
7829
  }
7724
7830
  if (!trim && !bleed) {
7725
7831
  return [
7726
- finding(check17.id, {
7832
+ finding(check18.id, {
7727
7833
  severity: "warning",
7728
7834
  message: "No TrimBox or BleedBox found. Bleed area cannot be verified.",
7729
7835
  file: ctx.pdfPath
@@ -7736,7 +7842,7 @@ var check17 = {
7736
7842
  const bleedSize = ctx.config.validate.pdf.bleedSize;
7737
7843
  if (mediaW - trimW < bleedSize * 2 * 0.9) {
7738
7844
  return [
7739
- finding(check17.id, {
7845
+ finding(check18.id, {
7740
7846
  severity: "warning",
7741
7847
  message: `Bleed area appears insufficient. Expected at least ${bleedSize}pt on each side.`,
7742
7848
  file: ctx.pdfPath
@@ -7747,10 +7853,10 @@ var check17 = {
7747
7853
  return [];
7748
7854
  }
7749
7855
  };
7750
- registerCheck(check17);
7856
+ registerCheck(check18);
7751
7857
 
7752
7858
  // src/checks/pdf/pdfx-metadata.ts
7753
- var check18 = {
7859
+ var check19 = {
7754
7860
  id: "pdf.print.pdfx-metadata",
7755
7861
  name: "PDF/X Metadata",
7756
7862
  description: "Verifies PDF/X DOCINFO metadata via qpdf JSON",
@@ -7769,22 +7875,22 @@ var check18 = {
7769
7875
  const objects = parseQpdfObjectsJson(stdout);
7770
7876
  if (!objects) {
7771
7877
  return [
7772
- inspectionFailed(check18.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
7878
+ inspectionFailed(check19.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
7773
7879
  ];
7774
7880
  }
7775
- return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check18.id, {
7881
+ return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check19.id, {
7776
7882
  severity: "error",
7777
7883
  message,
7778
7884
  file: ctx.pdfPath
7779
7885
  }));
7780
7886
  } catch {
7781
7887
  return [
7782
- inspectionFailed(check18.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
7888
+ inspectionFailed(check19.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
7783
7889
  ];
7784
7890
  }
7785
7891
  }
7786
7892
  };
7787
- registerCheck(check18);
7893
+ registerCheck(check19);
7788
7894
 
7789
7895
  // src/checks/source/htmlhint.ts
7790
7896
  import { existsSync as existsSync9 } from "node:fs";
@@ -7792,7 +7898,7 @@ import { readFile as readFile16 } from "node:fs/promises";
7792
7898
  import htmlhint from "htmlhint";
7793
7899
  var { HTMLHint } = htmlhint;
7794
7900
  var CONFIG_NAMES2 = [".htmlhintrc"];
7795
- var check19 = {
7901
+ var check20 = {
7796
7902
  id: "source.htmlhint",
7797
7903
  name: "HTMLHint",
7798
7904
  description: "Runs htmlhint with project config to validate generated HTML",
@@ -7812,7 +7918,7 @@ var check19 = {
7812
7918
  const html = await readFile16(ctx.htmlPath, "utf8");
7813
7919
  const messages = HTMLHint.verify(html, ruleset);
7814
7920
  return messages.map((m) => ({
7815
- checkId: check19.id,
7921
+ checkId: check20.id,
7816
7922
  severity: m.type === "error" ? "error" : "warning",
7817
7923
  message: `${m.rule.id}: ${m.message}`,
7818
7924
  file: ctx.htmlPath,
@@ -7821,7 +7927,7 @@ var check19 = {
7821
7927
  }));
7822
7928
  }
7823
7929
  };
7824
- registerCheck(check19);
7930
+ registerCheck(check20);
7825
7931
 
7826
7932
  // src/checks/source/stylelint.ts
7827
7933
  import { readFile as readFile17 } from "node:fs/promises";
@@ -8214,7 +8320,7 @@ function checkCssRaw(css, from) {
8214
8320
  }
8215
8321
 
8216
8322
  // src/checks/source/stylelint.ts
8217
- var check20 = {
8323
+ var check21 = {
8218
8324
  id: "source.stylelint",
8219
8325
  name: "Print-safety CSS",
8220
8326
  description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
@@ -8234,7 +8340,7 @@ var check20 = {
8234
8340
  css = await readFile17(file, "utf8");
8235
8341
  } catch (err) {
8236
8342
  results.push({
8237
- checkId: check20.id,
8343
+ checkId: check21.id,
8238
8344
  severity: "error",
8239
8345
  message: `Cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`,
8240
8346
  file
@@ -8243,7 +8349,7 @@ var check20 = {
8243
8349
  }
8244
8350
  for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
8245
8351
  results.push({
8246
- checkId: check20.id,
8352
+ checkId: check21.id,
8247
8353
  severity: w.severity,
8248
8354
  message: `${w.rule}: ${w.message}`,
8249
8355
  file,
@@ -8255,7 +8361,7 @@ var check20 = {
8255
8361
  return results;
8256
8362
  }
8257
8363
  };
8258
- registerCheck(check20);
8364
+ registerCheck(check21);
8259
8365
 
8260
8366
  // src/checks/source/css-ownership.ts
8261
8367
  import { readFile as readFile18 } from "node:fs/promises";
@@ -8376,7 +8482,7 @@ function checkOwnership(root, file, maps) {
8376
8482
  });
8377
8483
  return out;
8378
8484
  }
8379
- var check21 = {
8485
+ var check22 = {
8380
8486
  id: "source.css-ownership",
8381
8487
  name: "CSS ownership contract",
8382
8488
  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.",
@@ -8399,7 +8505,7 @@ var check21 = {
8399
8505
  } catch (err) {
8400
8506
  return [
8401
8507
  {
8402
- checkId: check21.id,
8508
+ checkId: check22.id,
8403
8509
  severity: "error",
8404
8510
  message: `Failed to parse CSS ownership contract "${configPath}": ${err instanceof Error ? err.message : String(err)}`,
8405
8511
  file: configPath
@@ -8441,7 +8547,7 @@ var check21 = {
8441
8547
  }
8442
8548
  for (const v of violations) {
8443
8549
  results.push({
8444
- checkId: check21.id,
8550
+ checkId: check22.id,
8445
8551
  severity: "warning",
8446
8552
  message: v.message,
8447
8553
  file: absFile,
@@ -8453,7 +8559,7 @@ var check21 = {
8453
8559
  return results;
8454
8560
  }
8455
8561
  };
8456
- registerCheck(check21);
8562
+ registerCheck(check22);
8457
8563
 
8458
8564
  // src/checks/source/local-refs.ts
8459
8565
  import { existsSync as existsSync10 } from "node:fs";
@@ -8540,7 +8646,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
8540
8646
  }
8541
8647
 
8542
8648
  // src/checks/source/local-refs.ts
8543
- var check22 = {
8649
+ var check23 = {
8544
8650
  id: "source.links.local-refs",
8545
8651
  name: "Local Markdown References",
8546
8652
  description: "Checks local markdown links and image refs resolve on disk",
@@ -8548,12 +8654,12 @@ var check22 = {
8548
8654
  phase: "pre-build",
8549
8655
  async run(ctx) {
8550
8656
  const results = [];
8551
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check22.id, results)) {
8657
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
8552
8658
  if (isNonFilesystemRef(ref))
8553
8659
  continue;
8554
8660
  const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
8555
8661
  if (escape) {
8556
- results.push(finding(check22.id, {
8662
+ results.push(finding(check23.id, {
8557
8663
  severity: "error",
8558
8664
  message: escape,
8559
8665
  file,
@@ -8563,7 +8669,7 @@ var check22 = {
8563
8669
  }
8564
8670
  if (localRefExists(ref, kind, file, ctx.inputDir))
8565
8671
  continue;
8566
- results.push(finding(check22.id, {
8672
+ results.push(finding(check23.id, {
8567
8673
  severity: kind === "image" ? "warning" : "error",
8568
8674
  code: kind === "image" ? "missing-image-placeholder" : undefined,
8569
8675
  message: kind === "image" ? `Local image not found; build will substitute a magenta placeholder: ${ref}` : `Local reference not found: ${ref}`,
@@ -8588,7 +8694,7 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
8588
8694
  return existsSync10(fileRelative);
8589
8695
  }
8590
8696
  }
8591
- registerCheck(check22);
8697
+ registerCheck(check23);
8592
8698
 
8593
8699
  // src/lib/build-staging.ts
8594
8700
  import path11 from "node:path";
@@ -8854,7 +8960,7 @@ async function createStageRoot() {
8854
8960
  }
8855
8961
 
8856
8962
  // src/checks/source/dangling-links.ts
8857
- var check23 = {
8963
+ var check24 = {
8858
8964
  id: "source.links.dangling",
8859
8965
  name: "Dangling Links",
8860
8966
  description: "Flags relative links the printed book cannot open (their href is dropped at print time)",
@@ -8862,10 +8968,10 @@ var check23 = {
8862
8968
  phase: "pre-build",
8863
8969
  async run(ctx) {
8864
8970
  const results = [];
8865
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
8971
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check24.id, results)) {
8866
8972
  if (kind !== "link" || isPrintResolvableHref(ref))
8867
8973
  continue;
8868
- results.push(finding(check23.id, {
8974
+ results.push(finding(check24.id, {
8869
8975
  severity: "warning",
8870
8976
  code: "dangling-link",
8871
8977
  data: { ref },
@@ -8877,11 +8983,11 @@ var check23 = {
8877
8983
  return results;
8878
8984
  }
8879
8985
  };
8880
- registerCheck(check23);
8986
+ registerCheck(check24);
8881
8987
 
8882
8988
  // src/checks/source/accessibility-alt-text.ts
8883
8989
  import { readFile as readFile20 } from "node:fs/promises";
8884
- var check24 = {
8990
+ var check25 = {
8885
8991
  id: "source.accessibility.alt-text",
8886
8992
  name: "Image Alt Text",
8887
8993
  description: "Checks markdown images include non-empty alt text",
@@ -8910,7 +9016,7 @@ var check24 = {
8910
9016
  if (alt.trim())
8911
9017
  continue;
8912
9018
  results.push({
8913
- checkId: check24.id,
9019
+ checkId: check25.id,
8914
9020
  severity: "warning",
8915
9021
  message: "Image is missing alt text",
8916
9022
  file,
@@ -8935,11 +9041,11 @@ function extractImageAlts(line) {
8935
9041
  }
8936
9042
  return alts;
8937
9043
  }
8938
- registerCheck(check24);
9044
+ registerCheck(check25);
8939
9045
 
8940
9046
  // src/checks/source/accessibility-heading-order.ts
8941
9047
  import { readFile as readFile21 } from "node:fs/promises";
8942
- var check25 = {
9048
+ var check26 = {
8943
9049
  id: "source.accessibility.heading-order",
8944
9050
  name: "Heading Order",
8945
9051
  description: "Checks markdown heading levels do not jump by more than one",
@@ -8967,7 +9073,7 @@ var check25 = {
8967
9073
  if (inFence)
8968
9074
  continue;
8969
9075
  const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
8970
- if (comment && parseDisableDirective(comment[1])?.includes(check25.id)) {
9076
+ if (comment && parseDisableDirective(comment[1])?.includes(check26.id)) {
8971
9077
  silencedLine = i + 1;
8972
9078
  continue;
8973
9079
  }
@@ -8977,7 +9083,7 @@ var check25 = {
8977
9083
  const level = match[1].length;
8978
9084
  if (prevLevel != null && level > prevLevel + 1 && i !== silencedLine) {
8979
9085
  results.push({
8980
- checkId: check25.id,
9086
+ checkId: check26.id,
8981
9087
  severity: "warning",
8982
9088
  message: `Heading level jump from h${prevLevel} to h${level}`,
8983
9089
  file,
@@ -8991,7 +9097,7 @@ var check25 = {
8991
9097
  return results;
8992
9098
  }
8993
9099
  };
8994
- registerCheck(check25);
9100
+ registerCheck(check26);
8995
9101
 
8996
9102
  // src/checks/source/layout-markers.ts
8997
9103
  import { readFile as readFile22 } from "node:fs/promises";
@@ -9102,7 +9208,7 @@ async function collectKnownClasses(ctx, plugins) {
9102
9208
  }
9103
9209
  return names;
9104
9210
  }
9105
- var check26 = {
9211
+ var check27 = {
9106
9212
  id: "source.markdown.layout-markers",
9107
9213
  name: "Layout Markers",
9108
9214
  description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class and any other class no stylesheet mentions",
@@ -9114,7 +9220,7 @@ var check26 = {
9114
9220
  return [];
9115
9221
  const results = [];
9116
9222
  const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
9117
- results.push(inspectionFailed(check26.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
9223
+ results.push(inspectionFailed(check27.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
9118
9224
  });
9119
9225
  const md = createMarkdownRenderer(plugins);
9120
9226
  let known;
@@ -9133,7 +9239,7 @@ var check26 = {
9133
9239
  if (names.has(cls) || warnedLines.has(use.line))
9134
9240
  continue;
9135
9241
  const guess = nearestKnownClass(cls, names);
9136
- results.push(finding(check26.id, {
9242
+ results.push(finding(check27.id, {
9137
9243
  severity: "warning",
9138
9244
  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.` : ""),
9139
9245
  file,
@@ -9143,7 +9249,7 @@ var check26 = {
9143
9249
  }
9144
9250
  }
9145
9251
  for (const w of env.layoutWarnings ?? []) {
9146
- results.push(finding(check26.id, {
9252
+ results.push(finding(check27.id, {
9147
9253
  severity: w.severity ?? "warning",
9148
9254
  message: w.message,
9149
9255
  file,
@@ -9152,13 +9258,13 @@ var check26 = {
9152
9258
  }));
9153
9259
  }
9154
9260
  } catch (error2) {
9155
- results.push(inspectionFailed(check26.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
9261
+ results.push(inspectionFailed(check27.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
9156
9262
  }
9157
9263
  }
9158
9264
  return results;
9159
9265
  }
9160
9266
  };
9161
- registerCheck(check26);
9267
+ registerCheck(check27);
9162
9268
 
9163
9269
  // src/checks/source/merge-markers.ts
9164
9270
  import { readFile as readFile23 } from "node:fs/promises";
@@ -9191,7 +9297,7 @@ var CLOSE_SENTINEL = ">>>>>>> online version";
9191
9297
  function withoutOnlineTag(relPath) {
9192
9298
  return relPath.replace(/\.online(?=\.[^./]*$|$)/, "");
9193
9299
  }
9194
- var check27 = {
9300
+ var check28 = {
9195
9301
  id: "source.sync.merge-markers",
9196
9302
  name: "Combined Versions",
9197
9303
  description: "Finds passages and files still holding two versions (yours and the online copy) after a sync",
@@ -9205,7 +9311,7 @@ var check27 = {
9205
9311
  try {
9206
9312
  content = await readFile23(file, "utf8");
9207
9313
  } catch {
9208
- results.push(inspectionFailed(check27.id, `Could not read source file: ${file}`, { file }));
9314
+ results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
9209
9315
  continue;
9210
9316
  }
9211
9317
  if (!content.includes(OPEN_SENTINEL) && !content.includes(CLOSE_SENTINEL))
@@ -9217,7 +9323,7 @@ var check27 = {
9217
9323
  const line = lines[i].endsWith("\r") ? lines[i].slice(0, -1) : lines[i];
9218
9324
  if (line === OPEN_SENTINEL) {
9219
9325
  inBlock = true;
9220
- results.push(finding(check27.id, {
9326
+ results.push(finding(check28.id, {
9221
9327
  severity: "error",
9222
9328
  code: "two-versions-passage",
9223
9329
  message: "This passage has two versions (yours and the online copy) — keep what you want, then delete the marker lines.",
@@ -9226,7 +9332,7 @@ var check27 = {
9226
9332
  }));
9227
9333
  } else if (line === CLOSE_SENTINEL) {
9228
9334
  if (!inBlock) {
9229
- results.push(finding(check27.id, {
9335
+ results.push(finding(check28.id, {
9230
9336
  severity: "error",
9231
9337
  code: "leftover-version-marker",
9232
9338
  message: "This marker line is left over from combining two versions — delete it.",
@@ -9247,7 +9353,7 @@ var check27 = {
9247
9353
  });
9248
9354
  for (const sibling of siblings.sort()) {
9249
9355
  const rel = path12.relative(ctx.inputDir, sibling).split(path12.sep).join("/");
9250
- results.push(finding(check27.id, {
9356
+ results.push(finding(check28.id, {
9251
9357
  severity: "warning",
9252
9358
  code: "kept-both-versions",
9253
9359
  message: `Two versions of ${withoutOnlineTag(rel)} are in your book — keep the one you want, then delete the other.`,
@@ -9255,12 +9361,12 @@ var check27 = {
9255
9361
  }));
9256
9362
  }
9257
9363
  } catch (error2) {
9258
- results.push(inspectionFailed(check27.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
9364
+ results.push(inspectionFailed(check28.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
9259
9365
  }
9260
9366
  return results;
9261
9367
  }
9262
9368
  };
9263
- registerCheck(check27);
9369
+ registerCheck(check28);
9264
9370
 
9265
9371
  // src/checks/source/characters.ts
9266
9372
  import { readFile as readFile24 } from "node:fs/promises";
@@ -9375,7 +9481,7 @@ function codePointLabel(char) {
9375
9481
 
9376
9482
  // src/checks/source/characters.ts
9377
9483
  var PROBE = new RegExp(CHARACTER_PATTERN.source, "u");
9378
- var check28 = {
9484
+ var check29 = {
9379
9485
  id: "source.characters",
9380
9486
  name: "Unusual Characters",
9381
9487
  description: "Finds invisible and control characters, odd spaces, and pasted typography (em dashes, curly quotes, emoji) in markdown",
@@ -9388,7 +9494,7 @@ var check28 = {
9388
9494
  try {
9389
9495
  content = await readFile24(file, "utf8");
9390
9496
  } catch {
9391
- results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
9497
+ results.push(inspectionFailed(check29.id, `Could not read source file: ${file}`, { file }));
9392
9498
  continue;
9393
9499
  }
9394
9500
  if (!PROBE.test(content))
@@ -9399,7 +9505,7 @@ var check28 = {
9399
9505
  for (let i = 0;i < lines.length; i++) {
9400
9506
  const line = lines[i];
9401
9507
  const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
9402
- if (comment && parseDisableDirective(comment[1])?.includes(check28.id)) {
9508
+ if (comment && parseDisableDirective(comment[1])?.includes(check29.id)) {
9403
9509
  silencedLine = i + 1;
9404
9510
  continue;
9405
9511
  }
@@ -9408,7 +9514,7 @@ var check28 = {
9408
9514
  for (const match of line.matchAll(CHARACTER_PATTERN)) {
9409
9515
  const char = match[0];
9410
9516
  const { name, code, fix } = describeCharacter(char);
9411
- results.push(finding(check28.id, {
9517
+ results.push(finding(check29.id, {
9412
9518
  severity: code === "typographer" ? "info" : "warning",
9413
9519
  code,
9414
9520
  message: `${name} (${codePointLabel(char)}). ${fix}`,
@@ -9423,7 +9529,7 @@ var check28 = {
9423
9529
  return results;
9424
9530
  }
9425
9531
  };
9426
- registerCheck(check28);
9532
+ registerCheck(check29);
9427
9533
 
9428
9534
  // src/checks/asset/image-file-size.ts
9429
9535
  import { stat as stat4 } from "node:fs/promises";
@@ -9649,7 +9755,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
9649
9755
  }
9650
9756
 
9651
9757
  // src/checks/asset/image-file-size.ts
9652
- var check29 = {
9758
+ var check30 = {
9653
9759
  id: "asset.image.file-size",
9654
9760
  name: "Image File Size",
9655
9761
  description: "Checks that image files do not exceed the maximum size limit",
@@ -9670,14 +9776,14 @@ var check29 = {
9670
9776
  if (info3.size > maxSize) {
9671
9777
  const sizeMb = (info3.size / 1e6).toFixed(1);
9672
9778
  const maxMb = (maxSize / 1e6).toFixed(1);
9673
- results.push(finding(check29.id, {
9779
+ results.push(finding(check30.id, {
9674
9780
  severity: "warning",
9675
9781
  message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
9676
9782
  file
9677
9783
  }));
9678
9784
  }
9679
9785
  } catch {
9680
- results.push(inspectionFailed(check29.id, `Could not stat image file: ${file}`, {
9786
+ results.push(inspectionFailed(check30.id, `Could not stat image file: ${file}`, {
9681
9787
  file
9682
9788
  }));
9683
9789
  }
@@ -9685,10 +9791,10 @@ var check29 = {
9685
9791
  return results;
9686
9792
  }
9687
9793
  };
9688
- registerCheck(check29);
9794
+ registerCheck(check30);
9689
9795
 
9690
9796
  // src/checks/asset/image-resolution.ts
9691
- var check30 = {
9797
+ var check31 = {
9692
9798
  id: "asset.image.resolution",
9693
9799
  name: "Image Resolution",
9694
9800
  description: "Checks source image DPI from embedded density metadata",
@@ -9710,7 +9816,7 @@ var check30 = {
9710
9816
  const { xDpi, yDpi } = info3;
9711
9817
  if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
9712
9818
  results.push({
9713
- checkId: check30.id,
9819
+ checkId: check31.id,
9714
9820
  severity: "warning",
9715
9821
  message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
9716
9822
  file
@@ -9720,7 +9826,7 @@ var check30 = {
9720
9826
  return results;
9721
9827
  }
9722
9828
  };
9723
- registerCheck(check30);
9829
+ registerCheck(check31);
9724
9830
 
9725
9831
  // src/checks/asset/image-color-space.ts
9726
9832
  var LABEL = {
@@ -9728,7 +9834,7 @@ var LABEL = {
9728
9834
  gray: "Gray",
9729
9835
  cmyk: "CMYK"
9730
9836
  };
9731
- var check31 = {
9837
+ var check32 = {
9732
9838
  id: "asset.image.color-space",
9733
9839
  name: "Image Color Space",
9734
9840
  description: "Validates image color spaces against allowed list",
@@ -9749,7 +9855,7 @@ var check31 = {
9749
9855
  const cs = info3?.colorSpace;
9750
9856
  if (cs && !allowedLower.has(cs)) {
9751
9857
  results.push({
9752
- checkId: check31.id,
9858
+ checkId: check32.id,
9753
9859
  severity: "warning",
9754
9860
  message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
9755
9861
  file
@@ -9759,10 +9865,10 @@ var check31 = {
9759
9865
  return results;
9760
9866
  }
9761
9867
  };
9762
- registerCheck(check31);
9868
+ registerCheck(check32);
9763
9869
 
9764
9870
  // src/checks/asset/image-alpha.ts
9765
- var check32 = {
9871
+ var check33 = {
9766
9872
  id: "asset.image.alpha-channel",
9767
9873
  name: "Image Alpha Channel",
9768
9874
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -9780,7 +9886,7 @@ var check32 = {
9780
9886
  const info3 = await inspectImage(file);
9781
9887
  if (info3?.hasAlpha) {
9782
9888
  results.push({
9783
- checkId: check32.id,
9889
+ checkId: check33.id,
9784
9890
  severity: "warning",
9785
9891
  message: "Image contains alpha channel, which may cause print issues",
9786
9892
  file
@@ -9790,10 +9896,10 @@ var check32 = {
9790
9896
  return results;
9791
9897
  }
9792
9898
  };
9793
- registerCheck(check32);
9899
+ registerCheck(check33);
9794
9900
 
9795
9901
  // src/checks/asset/image-tac.ts
9796
- var check33 = {
9902
+ var check34 = {
9797
9903
  id: "asset.image.tac-raster",
9798
9904
  name: "Image TAC (Raster)",
9799
9905
  description: "Rasterizes and checks TAC per image using Ghostscript",
@@ -9812,7 +9918,7 @@ var check33 = {
9812
9918
  const ghostscript = await resolveGhostscript();
9813
9919
  if (!ghostscript) {
9814
9920
  return [
9815
- inspectionFailed(check33.id, "Could not inspect image ink coverage: Ghostscript executable not found")
9921
+ inspectionFailed(check34.id, "Could not inspect image ink coverage: Ghostscript executable not found")
9816
9922
  ];
9817
9923
  }
9818
9924
  for (const file of files) {
@@ -9830,7 +9936,7 @@ var check33 = {
9830
9936
  if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
9831
9937
  const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
9832
9938
  if (tac > maxTac) {
9833
- results.push(finding(check33.id, {
9939
+ results.push(finding(check34.id, {
9834
9940
  severity: "warning",
9835
9941
  message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
9836
9942
  file,
@@ -9842,16 +9948,16 @@ var check33 = {
9842
9948
  }
9843
9949
  }
9844
9950
  } catch {
9845
- results.push(inspectionFailed(check33.id, `Could not inspect image ink coverage: ${file}`, { file }));
9951
+ results.push(inspectionFailed(check34.id, `Could not inspect image ink coverage: ${file}`, { file }));
9846
9952
  }
9847
9953
  }
9848
9954
  return results;
9849
9955
  }
9850
9956
  };
9851
- registerCheck(check33);
9957
+ registerCheck(check34);
9852
9958
 
9853
9959
  // src/checks/asset/approved-fonts.ts
9854
- var check34 = {
9960
+ var check35 = {
9855
9961
  id: "asset.font.approved-files",
9856
9962
  name: "Approved Font Files",
9857
9963
  description: "Checks font files against the approved file patterns",
@@ -9890,7 +9996,7 @@ var check34 = {
9890
9996
  for (const font of allFonts) {
9891
9997
  if (!approvedFonts.has(font)) {
9892
9998
  results.push({
9893
- checkId: check34.id,
9999
+ checkId: check35.id,
9894
10000
  severity: "warning",
9895
10001
  message: "Font file not in approved list",
9896
10002
  file: font
@@ -9900,7 +10006,7 @@ var check34 = {
9900
10006
  return results;
9901
10007
  }
9902
10008
  };
9903
- registerCheck(check34);
10009
+ registerCheck(check35);
9904
10010
 
9905
10011
  // src/checks/asset/font-license.ts
9906
10012
  import { existsSync as existsSync11 } from "node:fs";
@@ -9915,7 +10021,7 @@ var LICENSE_NAMES = [
9915
10021
  "OFL-1.1.txt",
9916
10022
  "COPYING"
9917
10023
  ];
9918
- var check35 = {
10024
+ var check36 = {
9919
10025
  id: "asset.font.license",
9920
10026
  name: "Font License",
9921
10027
  description: "Checks for license files in font directories",
@@ -9942,7 +10048,7 @@ var check35 = {
9942
10048
  const hasLicense = LICENSE_NAMES.some((name) => existsSync11(resolve7(fontDir, name)));
9943
10049
  if (!hasLicense) {
9944
10050
  results.push({
9945
- checkId: check35.id,
10051
+ checkId: check36.id,
9946
10052
  severity: "warning",
9947
10053
  message: `No font license file found in directory`,
9948
10054
  file: fontDir
@@ -9952,10 +10058,10 @@ var check35 = {
9952
10058
  return results;
9953
10059
  }
9954
10060
  };
9955
- registerCheck(check35);
10061
+ registerCheck(check36);
9956
10062
 
9957
10063
  // src/checks/heuristic/text-density.ts
9958
- var check36 = {
10064
+ var check37 = {
9959
10065
  id: "heuristic.whitespace.text-density",
9960
10066
  name: "Text Density",
9961
10067
  description: "Checks characters-per-page ratio",
@@ -9984,7 +10090,7 @@ var check36 = {
9984
10090
  });
9985
10091
  if (lowPages.length > 0) {
9986
10092
  results.push({
9987
- checkId: check36.id,
10093
+ checkId: check37.id,
9988
10094
  severity: "info",
9989
10095
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
9990
10096
  file: ctx.pdfPath
@@ -9992,7 +10098,7 @@ var check36 = {
9992
10098
  }
9993
10099
  if (highPages.length > 0) {
9994
10100
  results.push({
9995
- checkId: check36.id,
10101
+ checkId: check37.id,
9996
10102
  severity: "info",
9997
10103
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
9998
10104
  file: ctx.pdfPath
@@ -10001,11 +10107,11 @@ var check36 = {
10001
10107
  return results;
10002
10108
  }
10003
10109
  };
10004
- registerCheck(check36);
10110
+ registerCheck(check37);
10005
10111
 
10006
10112
  // src/checks/heuristic/section-density.ts
10007
10113
  import { readFile as readFile26 } from "node:fs/promises";
10008
- var check37 = {
10114
+ var check38 = {
10009
10115
  id: "heuristic.chunking.section-density",
10010
10116
  name: "Section Density",
10011
10117
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -10031,7 +10137,7 @@ var check37 = {
10031
10137
  const line = lines[i];
10032
10138
  if (/^#{1,6}\s/.test(line)) {
10033
10139
  if (paragraphCount > maxParas) {
10034
- results.push(finding(check37.id, {
10140
+ results.push(finding(check38.id, {
10035
10141
  severity: "info",
10036
10142
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
10037
10143
  file,
@@ -10051,7 +10157,7 @@ var check37 = {
10051
10157
  }
10052
10158
  }
10053
10159
  if (paragraphCount > maxParas) {
10054
- results.push(finding(check37.id, {
10160
+ results.push(finding(check38.id, {
10055
10161
  severity: "info",
10056
10162
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
10057
10163
  file,
@@ -10059,7 +10165,7 @@ var check37 = {
10059
10165
  }));
10060
10166
  }
10061
10167
  } catch {
10062
- results.push(inspectionFailed(check37.id, `Could not read source file: ${file}`, {
10168
+ results.push(inspectionFailed(check38.id, `Could not read source file: ${file}`, {
10063
10169
  file
10064
10170
  }));
10065
10171
  }
@@ -10067,10 +10173,10 @@ var check37 = {
10067
10173
  return results;
10068
10174
  }
10069
10175
  };
10070
- registerCheck(check37);
10176
+ registerCheck(check38);
10071
10177
 
10072
10178
  // src/checks/heuristic/layer-count.ts
10073
- var check38 = {
10179
+ var check39 = {
10074
10180
  id: "heuristic.decoration.layer-count",
10075
10181
  name: "Layer Count",
10076
10182
  description: "Counts image objects per page",
@@ -10095,7 +10201,7 @@ var check38 = {
10095
10201
  heavyPages.sort((a, b) => a - b);
10096
10202
  return [
10097
10203
  {
10098
- checkId: check38.id,
10204
+ checkId: check39.id,
10099
10205
  severity: "info",
10100
10206
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
10101
10207
  file: ctx.pdfPath
@@ -10105,10 +10211,10 @@ var check38 = {
10105
10211
  return [];
10106
10212
  }
10107
10213
  };
10108
- registerCheck(check38);
10214
+ registerCheck(check39);
10109
10215
 
10110
10216
  // src/checks/heuristic/placement-variance.ts
10111
- var check39 = {
10217
+ var check40 = {
10112
10218
  id: "heuristic.layout.placement-variance",
10113
10219
  name: "Placement Variance",
10114
10220
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -10126,7 +10232,7 @@ var check39 = {
10126
10232
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
10127
10233
  return [
10128
10234
  {
10129
- checkId: check39.id,
10235
+ checkId: check40.id,
10130
10236
  severity: "info",
10131
10237
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
10132
10238
  file: ctx.pdfPath
@@ -10134,7 +10240,7 @@ var check39 = {
10134
10240
  ];
10135
10241
  }
10136
10242
  };
10137
- registerCheck(check39);
10243
+ registerCheck(check40);
10138
10244
 
10139
10245
  // src/checks/runner.ts
10140
10246
  function getCheckSeverityOverride(checkId, config) {
@@ -10164,16 +10270,16 @@ async function runChecks(ctx, opts = {}) {
10164
10270
  }
10165
10271
  const release = retainPdfCache();
10166
10272
  try {
10167
- for (const check40 of checks2) {
10273
+ for (const check41 of checks2) {
10168
10274
  try {
10169
- let results = await check40.run(ctx);
10170
- const severityOverride = getCheckSeverityOverride(check40.id, ctx.config);
10275
+ let results = await check41.run(ctx);
10276
+ const severityOverride = getCheckSeverityOverride(check41.id, ctx.config);
10171
10277
  if (severityOverride) {
10172
10278
  for (const r of results) {
10173
10279
  r.severity = severityOverride;
10174
10280
  }
10175
10281
  }
10176
- const lintLevel = ruleLevel(ctx.config.lint.rules, check40.id);
10282
+ const lintLevel = ruleLevel(ctx.config.lint.rules, check41.id);
10177
10283
  if (lintLevel === "off")
10178
10284
  results = [];
10179
10285
  else if (lintLevel) {
@@ -10181,15 +10287,15 @@ async function runChecks(ctx, opts = {}) {
10181
10287
  r.severity = lintLevel === "warn" ? "warning" : "error";
10182
10288
  }
10183
10289
  if (results.length === 0) {
10184
- passed.push(check40.id);
10290
+ passed.push(check41.id);
10185
10291
  } else {
10186
10292
  allResults.push(...results);
10187
10293
  }
10188
10294
  } catch (err) {
10189
10295
  allResults.push({
10190
- checkId: check40.id,
10296
+ checkId: check41.id,
10191
10297
  severity: "error",
10192
- message: `Check "${check40.id}" threw: ${err instanceof Error ? err.message : String(err)}`
10298
+ message: `Check "${check41.id}" threw: ${err instanceof Error ? err.message : String(err)}`
10193
10299
  });
10194
10300
  }
10195
10301
  }
@@ -10229,15 +10335,15 @@ function emptyReport() {
10229
10335
  async function checkToolAvailability(config, opts = {}) {
10230
10336
  const { checks: checks2 } = selectChecks(opts, config);
10231
10337
  const toolToChecks = new Map;
10232
- for (const check40 of checks2) {
10233
- if (!check40.requiredTools?.length)
10338
+ for (const check41 of checks2) {
10339
+ if (!check41.requiredTools?.length)
10234
10340
  continue;
10235
- for (const tool of check40.requiredTools) {
10341
+ for (const tool of check41.requiredTools) {
10236
10342
  const existing = toolToChecks.get(tool);
10237
10343
  if (existing) {
10238
- existing.push(check40.id);
10344
+ existing.push(check41.id);
10239
10345
  } else {
10240
- toolToChecks.set(tool, [check40.id]);
10346
+ toolToChecks.set(tool, [check41.id]);
10241
10347
  }
10242
10348
  }
10243
10349
  }
@@ -10253,11 +10359,11 @@ async function checkToolAvailability(config, opts = {}) {
10253
10359
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
10254
10360
  const missingSet = new Set(missing);
10255
10361
  const skippedChecks = [];
10256
- for (const check40 of checks2) {
10257
- if (!check40.requiredTools?.length)
10362
+ for (const check41 of checks2) {
10363
+ if (!check41.requiredTools?.length)
10258
10364
  continue;
10259
- if (check40.requiredTools.some((t) => missingSet.has(t))) {
10260
- skippedChecks.push(check40.id);
10365
+ if (check41.requiredTools.some((t) => missingSet.has(t))) {
10366
+ skippedChecks.push(check41.id);
10261
10367
  }
10262
10368
  }
10263
10369
  return { available, missing, skippedChecks, toolToChecks };
@@ -10530,7 +10636,7 @@ async function executeValidation(args) {
10530
10636
  };
10531
10637
  if (args.fix) {
10532
10638
  const selected = selectChecks(runnerOptions, config).checks;
10533
- if (selected.some((check40) => check40.id === MARKDOWNLINT_CHECK_ID)) {
10639
+ if (selected.some((check41) => check41.id === MARKDOWNLINT_CHECK_ID)) {
10534
10640
  await applyMarkdownlintFixes(context);
10535
10641
  } else {
10536
10642
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -10903,7 +11009,7 @@ class PdfOutput {
10903
11009
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10904
11010
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10905
11011
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10906
- const { buildNativePdf } = await import("./engine-zehw6tkv.js");
11012
+ const { buildNativePdf } = await import("./engine-yax79k9m.js");
10907
11013
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10908
11014
  title: config.title,
10909
11015
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10926,7 +11032,7 @@ class PdfOutput {
10926
11032
  const icc = opts.iccPath ?? config.pdfx.icc;
10927
11033
  effectiveIccPath = await resolveIccProfile(icc, manifestDir, opts.iccPath);
10928
11034
  if (await hasLiveTransparency(await fsp2.readFile(rawPdf))) {
10929
- 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.");
11035
+ 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).");
10930
11036
  }
10931
11037
  const blankGradientPages = await findPostScriptShadingPages(await fsp2.readFile(rawPdf));
10932
11038
  if (blankGradientPages.length > 0) {
@@ -10948,7 +11054,8 @@ class PdfOutput {
10948
11054
  pdfx: pdfxMode,
10949
11055
  title: config.title,
10950
11056
  maxTac: config.ink.maxTac,
10951
- stagingDir: stage
11057
+ stagingDir: stage,
11058
+ rasterDpi: config.pdfx.rasterDpi
10952
11059
  });
10953
11060
  }
10954
11061
  if (gates.postValidate) {
@@ -11130,8 +11237,17 @@ async function renderPreviewBook(inputPath, config, opts) {
11130
11237
  }
11131
11238
  });
11132
11239
  }
11133
- function injectPreviewScripts(html, insetBleedPt = 0) {
11134
- const meta = insetBleedPt > 0 ? ` <meta name="gp-inset-bleed" content="${insetBleedPt}">
11240
+ function injectPreviewScripts(html, page) {
11241
+ const area = page ? JSON.stringify({
11242
+ width: page.width,
11243
+ height: page.height,
11244
+ bleed: page.bleed ?? 0,
11245
+ safe: page.safe ?? 0,
11246
+ artSafe: page.artSafe,
11247
+ preset: page.preset,
11248
+ spec: page.spec
11249
+ }) : "";
11250
+ const meta = area ? ` <meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>
11135
11251
  ` : "";
11136
11252
  const scripts = meta + ` <script src="/engine/gutterpress-viewer.js"></script>
11137
11253
  ` + `<script src="/preview/scripts/preview-interface.js"></script>
@@ -11167,7 +11283,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
11167
11283
  for (const ref of nextImageRefs)
11168
11284
  imageRefs.add(ref);
11169
11285
  }
11170
- await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, config.page?.bleed), "utf-8");
11286
+ await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, config.page && { ...config.page, preset: config.preset, spec: config.spec }), "utf-8");
11171
11287
  }
11172
11288
  function isDotPathUnderRoot(candidatePath, root) {
11173
11289
  const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -12416,8 +12532,8 @@ function pageSizeForChoice(choice) {
12416
12532
  }
12417
12533
  var round3 = (n) => Math.round(n * 1000) / 1000;
12418
12534
  function cssLength(pt) {
12419
- const inches = round3(pt / 72);
12420
- return Math.abs(inches * 72 - pt) < 0.000001 ? `${inches}in` : `${round3(pt)}pt`;
12535
+ const inches2 = round3(pt / 72);
12536
+ return Math.abs(inches2 * 72 - pt) < 0.000001 ? `${inches2}in` : `${round3(pt)}pt`;
12421
12537
  }
12422
12538
  function formatPageSize(page) {
12423
12539
  return `${cssLength(page.width)} ${cssLength(page.height)}`;