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.
- package/dist/api/index.js +2 -2
- package/dist/{build-czbaj4qf.js → build-qmjg9r2d.js} +4 -4
- package/dist/checks/pdf/index.d.ts +1 -0
- package/dist/checks/pdf/safe-margins.d.ts +3 -0
- package/dist/{cli-cgbzdrwz.js → cli-7z29z4wz.js} +1 -1
- package/dist/{cli-dzw28t3s.js → cli-gh0ym80r.js} +2 -1
- package/dist/{cli-xx94a1k6.js → cli-j802s31j.js} +5 -5
- package/dist/{cli-aw614ztv.js → cli-zacevwcy.js} +299 -184
- package/dist/cli.js +12 -12
- package/dist/{doctor-6e9perj7.js → doctor-fe7tcs4v.js} +2 -2
- package/dist/{engine-fe0n7v9p.js → engine-9xb1ky8d.js} +1 -1
- package/dist/{engine-zehw6tkv.js → engine-yax79k9m.js} +2 -2
- package/dist/{ext-kavfk1zn.js → ext-ahwxt6gv.js} +4 -4
- package/dist/{gutterpress-viewer-k4mpqjh0.js → gutterpress-viewer-retj6ym2.js} +515 -281
- package/dist/{index-jt552wtf.js → index-3nr5rzzp.js} +5 -5
- package/dist/{index-b866g2ph.js → index-6xwtb7cw.js} +1 -1
- package/dist/{index-g7vszjqf.js → index-razpty47.js} +298 -182
- package/dist/index.js +3 -3
- package/dist/lib/ghostscript.d.ts +7 -0
- package/dist/lib/presets.d.ts +3 -1
- package/dist/{manifest.schema-5pzhscqh.json → manifest.schema-74s1ykyv.json} +20 -1
- package/dist/{new-ckpas1ya.js → new-ftd4fec4.js} +4 -4
- package/dist/{preflight-yh57q9nn.js → preflight-8sqarcsr.js} +4 -4
- package/dist/preview/file-watcher.d.ts +16 -2
- package/dist/{preview-bridge-evecsh4w.js → preview-bridge-8wjcjec3.js} +3 -2
- package/dist/{preview-interface-2n1d9pbf.js → preview-interface-nnmrxyqm.js} +37 -41
- package/dist/{preview-a4x2ezjp.js → preview-q76xn95h.js} +4 -4
- package/dist/{preview-shell-hyp7g158.js → preview-shell-wk9k3gcd.js} +18 -6
- package/dist/{publish-sfn5hmnx.js → publish-7gdy6gbf.js} +4 -4
- package/dist/schema/manifest.types.d.ts +38 -3
- package/dist/{validate-281rsdsv.js → validate-e3ryegce.js} +4 -4
- package/package.json +1 -1
|
@@ -14,12 +14,12 @@ import {
|
|
|
14
14
|
stampCreator,
|
|
15
15
|
stripAnnotations,
|
|
16
16
|
warn
|
|
17
|
-
} from "./cli-
|
|
17
|
+
} from "./cli-gh0ym80r.js";
|
|
18
18
|
import {
|
|
19
19
|
DEBOUNCE,
|
|
20
20
|
UsageError,
|
|
21
21
|
resolvePort
|
|
22
|
-
} from "./cli-
|
|
22
|
+
} from "./cli-7z29z4wz.js";
|
|
23
23
|
import {
|
|
24
24
|
blackTextToGray
|
|
25
25
|
} from "./cli-ht1bcqdd.js";
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
getAssetPath,
|
|
29
29
|
getAssetsDir,
|
|
30
30
|
lastPageMode
|
|
31
|
-
} from "./cli-
|
|
31
|
+
} from "./cli-j802s31j.js";
|
|
32
32
|
import {
|
|
33
33
|
BuildError,
|
|
34
34
|
EXIT_CODES
|
|
@@ -106,6 +106,11 @@ function pinnedNpmSpecifier(name, version) {
|
|
|
106
106
|
// src/lib/presets.ts
|
|
107
107
|
var PRESET_IDS = ["dtrpg", "book", "custom"];
|
|
108
108
|
var DTRPG_PRESET = {
|
|
109
|
+
id: "dtrpg",
|
|
110
|
+
spec: {
|
|
111
|
+
label: "DriveThruRPG — Quick Specifications for Print Books",
|
|
112
|
+
url: "https://help.drivethrupartners.com/hc/en-us/articles/12780800178583-Quick-Specifications-for-Print-Books"
|
|
113
|
+
},
|
|
109
114
|
defaultTargets: ["dtrpg"],
|
|
110
115
|
extensions: [],
|
|
111
116
|
source: {
|
|
@@ -116,13 +121,16 @@ var DTRPG_PRESET = {
|
|
|
116
121
|
flavor: "x1a",
|
|
117
122
|
icc: "profiles/CGATS21_CRPC1.icc",
|
|
118
123
|
stripAnnotations: true,
|
|
119
|
-
blackText: "k-only"
|
|
124
|
+
blackText: "k-only",
|
|
125
|
+
rasterDpi: undefined
|
|
120
126
|
},
|
|
121
127
|
page: {
|
|
122
128
|
width: 621,
|
|
123
129
|
height: 810,
|
|
124
130
|
tolerance: 0.5,
|
|
125
|
-
bleed: 9
|
|
131
|
+
bleed: 9,
|
|
132
|
+
safe: 36,
|
|
133
|
+
artSafe: 18
|
|
126
134
|
},
|
|
127
135
|
ink: {
|
|
128
136
|
maxTac: 240,
|
|
@@ -151,6 +159,10 @@ var DTRPG_PRESET = {
|
|
|
151
159
|
"pdf.print.embedded-fonts": {
|
|
152
160
|
enabled: true,
|
|
153
161
|
severity: "error"
|
|
162
|
+
},
|
|
163
|
+
"pdf.print.safe-margins": {
|
|
164
|
+
enabled: true,
|
|
165
|
+
severity: "error"
|
|
154
166
|
}
|
|
155
167
|
},
|
|
156
168
|
source: {
|
|
@@ -183,6 +195,7 @@ var DTRPG_PRESET = {
|
|
|
183
195
|
}
|
|
184
196
|
};
|
|
185
197
|
var BOOK_PRESET = {
|
|
198
|
+
id: "book",
|
|
186
199
|
defaultTargets: [],
|
|
187
200
|
extensions: [],
|
|
188
201
|
source: {
|
|
@@ -193,13 +206,16 @@ var BOOK_PRESET = {
|
|
|
193
206
|
flavor: "x1a",
|
|
194
207
|
icc: "profiles/CGATS21_CRPC1.icc",
|
|
195
208
|
stripAnnotations: true,
|
|
196
|
-
blackText: "icc"
|
|
209
|
+
blackText: "icc",
|
|
210
|
+
rasterDpi: undefined
|
|
197
211
|
},
|
|
198
212
|
page: {
|
|
199
213
|
width: 432,
|
|
200
214
|
height: 648,
|
|
201
215
|
tolerance: 0.5,
|
|
202
|
-
bleed: undefined
|
|
216
|
+
bleed: undefined,
|
|
217
|
+
safe: 18,
|
|
218
|
+
artSafe: undefined
|
|
203
219
|
},
|
|
204
220
|
ink: {
|
|
205
221
|
maxTac: 400,
|
|
@@ -221,6 +237,10 @@ var BOOK_PRESET = {
|
|
|
221
237
|
enabled: true,
|
|
222
238
|
severity: "error"
|
|
223
239
|
},
|
|
240
|
+
"pdf.print.safe-margins": {
|
|
241
|
+
enabled: true,
|
|
242
|
+
severity: "warning"
|
|
243
|
+
},
|
|
224
244
|
"pdf.print.pdfx-markers": false,
|
|
225
245
|
"pdf.print.pdfx-metadata": false
|
|
226
246
|
},
|
|
@@ -255,6 +275,7 @@ var BOOK_PRESET = {
|
|
|
255
275
|
};
|
|
256
276
|
var CUSTOM_PRESET = {
|
|
257
277
|
...BOOK_PRESET,
|
|
278
|
+
id: "custom",
|
|
258
279
|
page: null,
|
|
259
280
|
defaultTargets: []
|
|
260
281
|
};
|
|
@@ -300,7 +321,8 @@ var DTRPG_TARGET = {
|
|
|
300
321
|
"pdf.structure.qpdf": { enabled: true, severity: "error" },
|
|
301
322
|
"pdf.print.pdfx-markers": { enabled: true, severity: "error" },
|
|
302
323
|
"pdf.print.pdfx-metadata": { enabled: true, severity: "error" },
|
|
303
|
-
"pdf.print.embedded-fonts": { enabled: true, severity: "error" }
|
|
324
|
+
"pdf.print.embedded-fonts": { enabled: true, severity: "error" },
|
|
325
|
+
"pdf.print.safe-margins": { enabled: true, severity: "error" }
|
|
304
326
|
},
|
|
305
327
|
assets: {
|
|
306
328
|
allowedColorSpaces: [...DTRPG_PRESET.validate.assets.allowedColorSpaces]
|
|
@@ -315,7 +337,8 @@ var DTRPG_TARGET = {
|
|
|
315
337
|
"pdf.structure.qpdf",
|
|
316
338
|
"pdf.print.pdfx-markers",
|
|
317
339
|
"pdf.print.pdfx-metadata",
|
|
318
|
-
"pdf.print.embedded-fonts"
|
|
340
|
+
"pdf.print.embedded-fonts",
|
|
341
|
+
"pdf.print.safe-margins"
|
|
319
342
|
],
|
|
320
343
|
requiredTools: ["qpdf", "gs"]
|
|
321
344
|
};
|
|
@@ -330,6 +353,7 @@ var ITCH_TARGET = {
|
|
|
330
353
|
"pdf.print.pdfx-markers": false,
|
|
331
354
|
"pdf.print.pdfx-metadata": false,
|
|
332
355
|
"pdf.print.ink-coverage": false,
|
|
356
|
+
"pdf.print.safe-margins": false,
|
|
333
357
|
"asset.image.tac-raster": false
|
|
334
358
|
},
|
|
335
359
|
assets: {
|
|
@@ -547,7 +571,9 @@ function resolveCustomPage(c, m) {
|
|
|
547
571
|
width,
|
|
548
572
|
height,
|
|
549
573
|
tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5,
|
|
550
|
-
bleed: c.page?.bleed ?? m.page?.bleed
|
|
574
|
+
bleed: c.page?.bleed ?? m.page?.bleed,
|
|
575
|
+
safe: c.page?.safe ?? m.page?.safe,
|
|
576
|
+
artSafe: c.page?.artSafe ?? m.page?.artSafe
|
|
551
577
|
};
|
|
552
578
|
}
|
|
553
579
|
function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
@@ -580,6 +606,10 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
|
580
606
|
if (lastPage !== undefined && lastPage !== "blank" && lastPage !== "short-by-one") {
|
|
581
607
|
throw new UsageError(`Manifest field \`print.lastPage\` must be "blank" or "short-by-one", got ${JSON.stringify(lastPage)}.`);
|
|
582
608
|
}
|
|
609
|
+
const rasterDpi = c.pdfx?.rasterDpi ?? m.pdfx?.rasterDpi;
|
|
610
|
+
if (rasterDpi !== undefined && !(Number.isInteger(rasterDpi) && rasterDpi >= 72 && rasterDpi <= 1200)) {
|
|
611
|
+
throw new UsageError(`Manifest field \`pdfx.rasterDpi\` must be a whole number from 72 to 1200, got ${JSON.stringify(rasterDpi)}.`);
|
|
612
|
+
}
|
|
583
613
|
const rawExtensions = c.extensions ?? m.extensions ?? [];
|
|
584
614
|
if (!Array.isArray(rawExtensions)) {
|
|
585
615
|
throw new UsageError("Manifest field `extensions` must be a list of specifiers.");
|
|
@@ -588,6 +618,8 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
|
588
618
|
return {
|
|
589
619
|
title: c.title ?? m.title ?? "Document",
|
|
590
620
|
authors: c.authors ?? m.authors ?? [],
|
|
621
|
+
preset: preset.id,
|
|
622
|
+
spec: preset.spec,
|
|
591
623
|
styles: c.styles ?? m.styles,
|
|
592
624
|
extensions,
|
|
593
625
|
targets: resolveTargets(c.targets ?? m.targets, preset.defaultTargets),
|
|
@@ -5263,6 +5295,79 @@ var check2 = {
|
|
|
5263
5295
|
};
|
|
5264
5296
|
registerCheck(check2);
|
|
5265
5297
|
|
|
5298
|
+
// src/checks/pdf/safe-margins.ts
|
|
5299
|
+
var TOLERANCE_PT = 0.5;
|
|
5300
|
+
var ABOVE_BASELINE = 0.8;
|
|
5301
|
+
var BELOW_BASELINE = 0.2;
|
|
5302
|
+
var inches = (pt) => `${+(pt / 72).toFixed(2)}in`;
|
|
5303
|
+
var check3 = {
|
|
5304
|
+
id: "pdf.print.safe-margins",
|
|
5305
|
+
name: "Safe margins",
|
|
5306
|
+
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",
|
|
5307
|
+
category: "pdf",
|
|
5308
|
+
phase: "post-build",
|
|
5309
|
+
async run(ctx) {
|
|
5310
|
+
const { width, height, bleed = 0, safe = 0 } = ctx.config.page;
|
|
5311
|
+
if (!ctx.pdfPath || !safe)
|
|
5312
|
+
return [];
|
|
5313
|
+
const doc = await loadPdf(ctx.pdfPath);
|
|
5314
|
+
if (!doc) {
|
|
5315
|
+
return [inspectionFailed(check3.id, "Could not parse the PDF's text positions.", { file: ctx.pdfPath })];
|
|
5316
|
+
}
|
|
5317
|
+
const rule = ctx.config.spec?.label ?? "page.safe";
|
|
5318
|
+
const results = [];
|
|
5319
|
+
try {
|
|
5320
|
+
for (let pageNum = 1;pageNum <= doc.numPages; pageNum++) {
|
|
5321
|
+
const recto = pageNum % 2 === 1;
|
|
5322
|
+
const trim = {
|
|
5323
|
+
left: recto ? 0 : bleed,
|
|
5324
|
+
right: width - (recto ? bleed : 0),
|
|
5325
|
+
bottom: bleed,
|
|
5326
|
+
top: height - bleed
|
|
5327
|
+
};
|
|
5328
|
+
const { items } = await (await doc.getPage(pageNum)).getTextContent();
|
|
5329
|
+
for (const item of items) {
|
|
5330
|
+
if (!("str" in item) || !item.str.trim())
|
|
5331
|
+
continue;
|
|
5332
|
+
const [a, b, c, d, e, f] = item.transform;
|
|
5333
|
+
const along = item.width / Math.hypot(a, b);
|
|
5334
|
+
const xs = [];
|
|
5335
|
+
const ys = [];
|
|
5336
|
+
for (const s of [0, 1]) {
|
|
5337
|
+
for (const u of [-BELOW_BASELINE, ABOVE_BASELINE]) {
|
|
5338
|
+
xs.push(e + s * along * a + u * c);
|
|
5339
|
+
ys.push(f + s * along * b + u * d);
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
5342
|
+
const gaps = [
|
|
5343
|
+
[recto ? "binding" : "outside", Math.min(...xs) - trim.left],
|
|
5344
|
+
[recto ? "outside" : "binding", trim.right - Math.max(...xs)],
|
|
5345
|
+
["bottom", Math.min(...ys) - trim.bottom],
|
|
5346
|
+
["top", trim.top - Math.max(...ys)]
|
|
5347
|
+
];
|
|
5348
|
+
const [edge, gap] = gaps.reduce((worst, g) => g[1] < worst[1] ? g : worst);
|
|
5349
|
+
const shortfall = safe - gap;
|
|
5350
|
+
if (shortfall <= TOLERANCE_PT)
|
|
5351
|
+
continue;
|
|
5352
|
+
const text = item.str.trim();
|
|
5353
|
+
results.push(finding(check3.id, {
|
|
5354
|
+
severity: "error",
|
|
5355
|
+
message: `Page ${pageNum}: "${text.slice(0, 40)}${text.length > 40 ? "…" : ""}" is ${inches(shortfall)} too close to the ${edge} edge.`,
|
|
5356
|
+
file: ctx.pdfPath,
|
|
5357
|
+
detail: `${rule}: text must stay at least ${inches(safe)} inside the trim on every edge.`,
|
|
5358
|
+
code: "text-outside-safe-margin",
|
|
5359
|
+
data: { page: pageNum, edge, shortfallPt: +shortfall.toFixed(2) }
|
|
5360
|
+
}));
|
|
5361
|
+
}
|
|
5362
|
+
}
|
|
5363
|
+
} catch {
|
|
5364
|
+
return [inspectionFailed(check3.id, "Could not read the PDF's text positions.", { file: ctx.pdfPath })];
|
|
5365
|
+
}
|
|
5366
|
+
return results;
|
|
5367
|
+
}
|
|
5368
|
+
};
|
|
5369
|
+
registerCheck(check3);
|
|
5370
|
+
|
|
5266
5371
|
// src/checks/pdf/pdfx-structure.ts
|
|
5267
5372
|
function isObject(value) {
|
|
5268
5373
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -5371,7 +5476,7 @@ function getPdfxMetadataIssues(objects, flavor) {
|
|
|
5371
5476
|
}
|
|
5372
5477
|
|
|
5373
5478
|
// src/checks/pdf/pdfx-markers.ts
|
|
5374
|
-
var
|
|
5479
|
+
var check4 = {
|
|
5375
5480
|
id: "pdf.print.pdfx-markers",
|
|
5376
5481
|
name: "PDF/X Markers",
|
|
5377
5482
|
description: "Checks PDF/X OutputIntent structure via qpdf JSON",
|
|
@@ -5390,22 +5495,22 @@ var check3 = {
|
|
|
5390
5495
|
const objects = parseQpdfObjectsJson(stdout);
|
|
5391
5496
|
if (!objects) {
|
|
5392
5497
|
return [
|
|
5393
|
-
inspectionFailed(
|
|
5498
|
+
inspectionFailed(check4.id, "Unable to parse qpdf JSON output for PDF/X checks.", { file: ctx.pdfPath })
|
|
5394
5499
|
];
|
|
5395
5500
|
}
|
|
5396
|
-
return getPdfxOutputIntentIssues(objects).map((message) => finding(
|
|
5501
|
+
return getPdfxOutputIntentIssues(objects).map((message) => finding(check4.id, {
|
|
5397
5502
|
severity: "error",
|
|
5398
5503
|
message,
|
|
5399
5504
|
file: ctx.pdfPath
|
|
5400
5505
|
}));
|
|
5401
5506
|
} catch {
|
|
5402
5507
|
return [
|
|
5403
|
-
inspectionFailed(
|
|
5508
|
+
inspectionFailed(check4.id, "Failed to inspect PDF objects with qpdf for PDF/X markers.", { file: ctx.pdfPath })
|
|
5404
5509
|
];
|
|
5405
5510
|
}
|
|
5406
5511
|
}
|
|
5407
5512
|
};
|
|
5408
|
-
registerCheck(
|
|
5513
|
+
registerCheck(check4);
|
|
5409
5514
|
|
|
5410
5515
|
// src/lib/pdf-parse.ts
|
|
5411
5516
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
@@ -5458,7 +5563,7 @@ async function getPerPageInkCoverage(pdfPath) {
|
|
|
5458
5563
|
}
|
|
5459
5564
|
|
|
5460
5565
|
// src/checks/pdf/color-spaces.ts
|
|
5461
|
-
var
|
|
5566
|
+
var check5 = {
|
|
5462
5567
|
id: "pdf.print.color-spaces",
|
|
5463
5568
|
name: "Color Spaces",
|
|
5464
5569
|
description: "Checks for forbidden color spaces (DeviceRGB, Lab, Separation, DeviceN)",
|
|
@@ -5471,7 +5576,7 @@ var check4 = {
|
|
|
5471
5576
|
const results = [];
|
|
5472
5577
|
if (/\/DeviceRGB/.test(bytes)) {
|
|
5473
5578
|
results.push({
|
|
5474
|
-
checkId:
|
|
5579
|
+
checkId: check5.id,
|
|
5475
5580
|
severity: "error",
|
|
5476
5581
|
message: "DeviceRGB found (interior must be CMYK or grayscale only).",
|
|
5477
5582
|
file: ctx.pdfPath
|
|
@@ -5479,7 +5584,7 @@ var check4 = {
|
|
|
5479
5584
|
}
|
|
5480
5585
|
if (/\/Lab\b/.test(bytes)) {
|
|
5481
5586
|
results.push({
|
|
5482
|
-
checkId:
|
|
5587
|
+
checkId: check5.id,
|
|
5483
5588
|
severity: "error",
|
|
5484
5589
|
message: "Lab color space found (not allowed).",
|
|
5485
5590
|
file: ctx.pdfPath
|
|
@@ -5487,7 +5592,7 @@ var check4 = {
|
|
|
5487
5592
|
}
|
|
5488
5593
|
if (/\/Separation/.test(bytes)) {
|
|
5489
5594
|
results.push({
|
|
5490
|
-
checkId:
|
|
5595
|
+
checkId: check5.id,
|
|
5491
5596
|
severity: "error",
|
|
5492
5597
|
message: "Spot color (Separation) found (not allowed).",
|
|
5493
5598
|
file: ctx.pdfPath
|
|
@@ -5495,7 +5600,7 @@ var check4 = {
|
|
|
5495
5600
|
}
|
|
5496
5601
|
if (/\/DeviceN/.test(bytes)) {
|
|
5497
5602
|
results.push({
|
|
5498
|
-
checkId:
|
|
5603
|
+
checkId: check5.id,
|
|
5499
5604
|
severity: "error",
|
|
5500
5605
|
message: "Spot color (DeviceN) found (not allowed).",
|
|
5501
5606
|
file: ctx.pdfPath
|
|
@@ -5504,10 +5609,10 @@ var check4 = {
|
|
|
5504
5609
|
return results;
|
|
5505
5610
|
}
|
|
5506
5611
|
};
|
|
5507
|
-
registerCheck(
|
|
5612
|
+
registerCheck(check5);
|
|
5508
5613
|
|
|
5509
5614
|
// src/checks/pdf/embedded-fonts.ts
|
|
5510
|
-
var
|
|
5615
|
+
var check6 = {
|
|
5511
5616
|
id: "pdf.print.embedded-fonts",
|
|
5512
5617
|
name: "Embedded Fonts",
|
|
5513
5618
|
description: "Verifies all fonts in the PDF are embedded",
|
|
@@ -5523,7 +5628,7 @@ var check5 = {
|
|
|
5523
5628
|
if (fonts.length === 0) {
|
|
5524
5629
|
return [
|
|
5525
5630
|
{
|
|
5526
|
-
checkId:
|
|
5631
|
+
checkId: check6.id,
|
|
5527
5632
|
severity: "warning",
|
|
5528
5633
|
message: "No fonts detected (unexpected).",
|
|
5529
5634
|
file: ctx.pdfPath,
|
|
@@ -5536,7 +5641,7 @@ var check5 = {
|
|
|
5536
5641
|
const names = notEmbedded.map((f) => f.name);
|
|
5537
5642
|
return [
|
|
5538
5643
|
{
|
|
5539
|
-
checkId:
|
|
5644
|
+
checkId: check6.id,
|
|
5540
5645
|
severity: "error",
|
|
5541
5646
|
message: `Not all fonts are embedded (${names.join(", ")}). Check @font-face and Chromium output.`,
|
|
5542
5647
|
file: ctx.pdfPath,
|
|
@@ -5548,10 +5653,10 @@ var check5 = {
|
|
|
5548
5653
|
return [];
|
|
5549
5654
|
}
|
|
5550
5655
|
};
|
|
5551
|
-
registerCheck(
|
|
5656
|
+
registerCheck(check6);
|
|
5552
5657
|
|
|
5553
5658
|
// src/checks/pdf/ink-coverage.ts
|
|
5554
|
-
var
|
|
5659
|
+
var check7 = {
|
|
5555
5660
|
id: "pdf.print.ink-coverage",
|
|
5556
5661
|
name: "Ink Coverage (TAC)",
|
|
5557
5662
|
description: "Checks total area coverage (TAC) against maximum ink limits",
|
|
@@ -5565,7 +5670,7 @@ var check6 = {
|
|
|
5565
5670
|
if (!inkResult.ok) {
|
|
5566
5671
|
return [
|
|
5567
5672
|
{
|
|
5568
|
-
checkId:
|
|
5673
|
+
checkId: check7.id,
|
|
5569
5674
|
severity: "warning",
|
|
5570
5675
|
message: "Ink coverage could not be measured",
|
|
5571
5676
|
file: ctx.pdfPath,
|
|
@@ -5590,7 +5695,7 @@ var check6 = {
|
|
|
5590
5695
|
}
|
|
5591
5696
|
return [
|
|
5592
5697
|
{
|
|
5593
|
-
checkId:
|
|
5698
|
+
checkId: check7.id,
|
|
5594
5699
|
severity: "warning",
|
|
5595
5700
|
message: `Total ink coverage too high on ${offending.length} page(s) (max ${maxTac.toFixed(1)}%, recommended <=${ctx.config.ink.maxTac}%)`,
|
|
5596
5701
|
file: ctx.pdfPath,
|
|
@@ -5614,10 +5719,10 @@ var check6 = {
|
|
|
5614
5719
|
];
|
|
5615
5720
|
}
|
|
5616
5721
|
};
|
|
5617
|
-
registerCheck(
|
|
5722
|
+
registerCheck(check7);
|
|
5618
5723
|
|
|
5619
5724
|
// src/checks/pdf/rasterized-pages.ts
|
|
5620
|
-
var
|
|
5725
|
+
var check8 = {
|
|
5621
5726
|
id: "pdf.print.rasterized-pages",
|
|
5622
5727
|
name: "Rasterized Pages",
|
|
5623
5728
|
description: "Detects pages that appear to be fully rasterized (CSS filters, blend modes, transparency)",
|
|
@@ -5657,7 +5762,7 @@ var check7 = {
|
|
|
5657
5762
|
rasterizedPages.sort((a, b) => a - b);
|
|
5658
5763
|
return [
|
|
5659
5764
|
{
|
|
5660
|
-
checkId:
|
|
5765
|
+
checkId: check8.id,
|
|
5661
5766
|
severity: "warning",
|
|
5662
5767
|
message: `Possible rasterized pages detected: ${rasterizedPages.join(", ")}`,
|
|
5663
5768
|
file: ctx.pdfPath,
|
|
@@ -5668,11 +5773,11 @@ var check7 = {
|
|
|
5668
5773
|
];
|
|
5669
5774
|
}
|
|
5670
5775
|
};
|
|
5671
|
-
registerCheck(
|
|
5776
|
+
registerCheck(check8);
|
|
5672
5777
|
|
|
5673
5778
|
// src/checks/pdf/black-text.ts
|
|
5674
5779
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
5675
|
-
var
|
|
5780
|
+
var check9 = {
|
|
5676
5781
|
id: "pdf.print.black-text",
|
|
5677
5782
|
name: "Black Text",
|
|
5678
5783
|
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)",
|
|
@@ -5688,7 +5793,7 @@ var check8 = {
|
|
|
5688
5793
|
if (found.runs === 0)
|
|
5689
5794
|
return [];
|
|
5690
5795
|
return [
|
|
5691
|
-
finding(
|
|
5796
|
+
finding(check9.id, {
|
|
5692
5797
|
severity: "warning",
|
|
5693
5798
|
message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
|
|
5694
5799
|
file: ctx.pdfPath,
|
|
@@ -5699,17 +5804,17 @@ var check8 = {
|
|
|
5699
5804
|
];
|
|
5700
5805
|
} catch {
|
|
5701
5806
|
return [
|
|
5702
|
-
inspectionFailed(
|
|
5807
|
+
inspectionFailed(check9.id, "Could not inspect the PDF's text colours.", {
|
|
5703
5808
|
file: ctx.pdfPath
|
|
5704
5809
|
})
|
|
5705
5810
|
];
|
|
5706
5811
|
}
|
|
5707
5812
|
}
|
|
5708
5813
|
};
|
|
5709
|
-
registerCheck(
|
|
5814
|
+
registerCheck(check9);
|
|
5710
5815
|
|
|
5711
5816
|
// src/checks/pdf/signature.ts
|
|
5712
|
-
var
|
|
5817
|
+
var check10 = {
|
|
5713
5818
|
id: "pdf.print.signature",
|
|
5714
5819
|
name: "Signature",
|
|
5715
5820
|
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)",
|
|
@@ -5721,7 +5826,7 @@ var check9 = {
|
|
|
5721
5826
|
return [];
|
|
5722
5827
|
const doc = await loadPdf(ctx.pdfPath);
|
|
5723
5828
|
if (!doc) {
|
|
5724
|
-
return [inspectionFailed(
|
|
5829
|
+
return [inspectionFailed(check10.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
|
|
5725
5830
|
}
|
|
5726
5831
|
const size = ctx.config.print.signature;
|
|
5727
5832
|
const pages = doc.numPages;
|
|
@@ -5730,7 +5835,7 @@ var check9 = {
|
|
|
5730
5835
|
const data = { pages, signature: size };
|
|
5731
5836
|
if (pages % size !== 0) {
|
|
5732
5837
|
return [
|
|
5733
|
-
finding(
|
|
5838
|
+
finding(check10.id, {
|
|
5734
5839
|
severity: "error",
|
|
5735
5840
|
message: `Page count ${pages} does not fit a signature of ${size}: it must be a multiple of ${size}, or one short of one.`,
|
|
5736
5841
|
file: ctx.pdfPath,
|
|
@@ -5746,10 +5851,10 @@ var check9 = {
|
|
|
5746
5851
|
if (!await pageHasMarks(doc, pages))
|
|
5747
5852
|
return [];
|
|
5748
5853
|
} catch {
|
|
5749
|
-
return [inspectionFailed(
|
|
5854
|
+
return [inspectionFailed(check10.id, "Could not read the last page.", { file: ctx.pdfPath })];
|
|
5750
5855
|
}
|
|
5751
5856
|
return [
|
|
5752
|
-
finding(
|
|
5857
|
+
finding(check10.id, {
|
|
5753
5858
|
severity: "error",
|
|
5754
5859
|
message: `The last page (${pages}) has content, but the printer needs it blank for its own information.`,
|
|
5755
5860
|
file: ctx.pdfPath,
|
|
@@ -5760,10 +5865,10 @@ var check9 = {
|
|
|
5760
5865
|
];
|
|
5761
5866
|
}
|
|
5762
5867
|
};
|
|
5763
|
-
registerCheck(
|
|
5868
|
+
registerCheck(check10);
|
|
5764
5869
|
|
|
5765
5870
|
// src/checks/pdf/bookmarks.ts
|
|
5766
|
-
var
|
|
5871
|
+
var check11 = {
|
|
5767
5872
|
id: "pdf.nav.bookmarks",
|
|
5768
5873
|
name: "Bookmarks",
|
|
5769
5874
|
description: "Checks for PDF outline (bookmarks) tree",
|
|
@@ -5777,14 +5882,14 @@ var check10 = {
|
|
|
5777
5882
|
const doc = await loadPdf(ctx.pdfPath);
|
|
5778
5883
|
if (!doc) {
|
|
5779
5884
|
return [
|
|
5780
|
-
inspectionFailed(
|
|
5885
|
+
inspectionFailed(check11.id, "Could not inspect PDF for bookmarks.", {
|
|
5781
5886
|
file: ctx.pdfPath
|
|
5782
5887
|
})
|
|
5783
5888
|
];
|
|
5784
5889
|
}
|
|
5785
5890
|
if (await getOutlineCount(doc) === 0) {
|
|
5786
5891
|
return [
|
|
5787
|
-
finding(
|
|
5892
|
+
finding(check11.id, {
|
|
5788
5893
|
severity: "warning",
|
|
5789
5894
|
message: "PDF does not contain bookmarks (outline tree).",
|
|
5790
5895
|
file: ctx.pdfPath
|
|
@@ -5794,10 +5899,10 @@ var check10 = {
|
|
|
5794
5899
|
return [];
|
|
5795
5900
|
}
|
|
5796
5901
|
};
|
|
5797
|
-
registerCheck(
|
|
5902
|
+
registerCheck(check11);
|
|
5798
5903
|
|
|
5799
5904
|
// src/checks/pdf/toc-links.ts
|
|
5800
|
-
var
|
|
5905
|
+
var check12 = {
|
|
5801
5906
|
id: "pdf.nav.toc-links",
|
|
5802
5907
|
name: "TOC Links",
|
|
5803
5908
|
description: "Detects link annotations on TOC pages",
|
|
@@ -5812,7 +5917,7 @@ var check11 = {
|
|
|
5812
5917
|
if (!doc) {
|
|
5813
5918
|
return [
|
|
5814
5919
|
{
|
|
5815
|
-
checkId:
|
|
5920
|
+
checkId: check12.id,
|
|
5816
5921
|
severity: "warning",
|
|
5817
5922
|
message: "Could not inspect PDF for TOC links.",
|
|
5818
5923
|
file: ctx.pdfPath
|
|
@@ -5823,7 +5928,7 @@ var check11 = {
|
|
|
5823
5928
|
if (!hasLinks) {
|
|
5824
5929
|
return [
|
|
5825
5930
|
{
|
|
5826
|
-
checkId:
|
|
5931
|
+
checkId: check12.id,
|
|
5827
5932
|
severity: "warning",
|
|
5828
5933
|
message: "No link annotations found in PDF (expected for TOC).",
|
|
5829
5934
|
file: ctx.pdfPath
|
|
@@ -5833,10 +5938,10 @@ var check11 = {
|
|
|
5833
5938
|
return [];
|
|
5834
5939
|
}
|
|
5835
5940
|
};
|
|
5836
|
-
registerCheck(
|
|
5941
|
+
registerCheck(check12);
|
|
5837
5942
|
|
|
5838
5943
|
// src/checks/pdf/cross-refs.ts
|
|
5839
|
-
var
|
|
5944
|
+
var check13 = {
|
|
5840
5945
|
id: "pdf.nav.cross-refs",
|
|
5841
5946
|
name: "Cross References",
|
|
5842
5947
|
description: "Counts and verifies internal link annotations",
|
|
@@ -5849,7 +5954,7 @@ var check12 = {
|
|
|
5849
5954
|
if (!doc) {
|
|
5850
5955
|
return [
|
|
5851
5956
|
{
|
|
5852
|
-
checkId:
|
|
5957
|
+
checkId: check13.id,
|
|
5853
5958
|
severity: "info",
|
|
5854
5959
|
message: "Could not inspect PDF for cross-references.",
|
|
5855
5960
|
file: ctx.pdfPath
|
|
@@ -5859,7 +5964,7 @@ var check12 = {
|
|
|
5859
5964
|
const linkCount = await countLinkAnnotations(doc);
|
|
5860
5965
|
return [
|
|
5861
5966
|
{
|
|
5862
|
-
checkId:
|
|
5967
|
+
checkId: check13.id,
|
|
5863
5968
|
severity: "info",
|
|
5864
5969
|
message: `PDF contains ${linkCount} internal link annotation${linkCount !== 1 ? "s" : ""}.`,
|
|
5865
5970
|
file: ctx.pdfPath
|
|
@@ -5867,10 +5972,10 @@ var check12 = {
|
|
|
5867
5972
|
];
|
|
5868
5973
|
}
|
|
5869
5974
|
};
|
|
5870
|
-
registerCheck(
|
|
5975
|
+
registerCheck(check13);
|
|
5871
5976
|
|
|
5872
5977
|
// src/checks/pdf/page-labels.ts
|
|
5873
|
-
var
|
|
5978
|
+
var check14 = {
|
|
5874
5979
|
id: "pdf.nav.page-labels",
|
|
5875
5980
|
name: "Page Labels",
|
|
5876
5981
|
description: "Verifies PDF page labels / folio numbering",
|
|
@@ -5885,7 +5990,7 @@ var check13 = {
|
|
|
5885
5990
|
const labels = await getPageLabels(doc);
|
|
5886
5991
|
return [
|
|
5887
5992
|
{
|
|
5888
|
-
checkId:
|
|
5993
|
+
checkId: check14.id,
|
|
5889
5994
|
severity: "info",
|
|
5890
5995
|
message: labels && labels.length > 0 ? "PDF contains page labels." : "PDF does not contain explicit page labels.",
|
|
5891
5996
|
file: ctx.pdfPath
|
|
@@ -5893,10 +5998,10 @@ var check13 = {
|
|
|
5893
5998
|
];
|
|
5894
5999
|
}
|
|
5895
6000
|
};
|
|
5896
|
-
registerCheck(
|
|
6001
|
+
registerCheck(check14);
|
|
5897
6002
|
|
|
5898
6003
|
// src/checks/pdf/image-resolution.ts
|
|
5899
|
-
var
|
|
6004
|
+
var check15 = {
|
|
5900
6005
|
id: "pdf.print.image-resolution",
|
|
5901
6006
|
name: "Image Resolution",
|
|
5902
6007
|
description: "Checks DPI of images embedded in the PDF",
|
|
@@ -5923,7 +6028,7 @@ var check14 = {
|
|
|
5923
6028
|
lowResPages.sort((a, b) => a - b);
|
|
5924
6029
|
return [
|
|
5925
6030
|
{
|
|
5926
|
-
checkId:
|
|
6031
|
+
checkId: check15.id,
|
|
5927
6032
|
severity: "warning",
|
|
5928
6033
|
message: `Low-resolution images (below ${minDpi} DPI) found on pages: ${lowResPages.join(", ")}`,
|
|
5929
6034
|
file: ctx.pdfPath
|
|
@@ -5933,10 +6038,10 @@ var check14 = {
|
|
|
5933
6038
|
return [];
|
|
5934
6039
|
}
|
|
5935
6040
|
};
|
|
5936
|
-
registerCheck(
|
|
6041
|
+
registerCheck(check15);
|
|
5937
6042
|
|
|
5938
6043
|
// src/checks/pdf/transparency.ts
|
|
5939
|
-
var
|
|
6044
|
+
var check16 = {
|
|
5940
6045
|
id: "pdf.print.transparency",
|
|
5941
6046
|
name: "Transparency",
|
|
5942
6047
|
description: "Checks for transparency markers (/Transparency, /SMask, /BM) in the PDF",
|
|
@@ -5958,7 +6063,7 @@ var check15 = {
|
|
|
5958
6063
|
if (found.length > 0) {
|
|
5959
6064
|
return [
|
|
5960
6065
|
{
|
|
5961
|
-
checkId:
|
|
6066
|
+
checkId: check16.id,
|
|
5962
6067
|
severity: "warning",
|
|
5963
6068
|
message: `Transparency detected in PDF: ${found.join(", ")}. This may cause issues with some printers.`,
|
|
5964
6069
|
file: ctx.pdfPath
|
|
@@ -5968,7 +6073,7 @@ var check15 = {
|
|
|
5968
6073
|
return [];
|
|
5969
6074
|
}
|
|
5970
6075
|
};
|
|
5971
|
-
registerCheck(
|
|
6076
|
+
registerCheck(check16);
|
|
5972
6077
|
|
|
5973
6078
|
// src/checks/pdf/bleed.ts
|
|
5974
6079
|
function findBox(bytes, box) {
|
|
@@ -5978,7 +6083,7 @@ function findBox(bytes, box) {
|
|
|
5978
6083
|
return null;
|
|
5979
6084
|
return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
|
|
5980
6085
|
}
|
|
5981
|
-
var
|
|
6086
|
+
var check17 = {
|
|
5982
6087
|
id: "pdf.print.bleed",
|
|
5983
6088
|
name: "Bleed Box",
|
|
5984
6089
|
description: "Compares MediaBox vs TrimBox/BleedBox to verify bleed area",
|
|
@@ -5992,7 +6097,7 @@ var check16 = {
|
|
|
5992
6097
|
const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
|
|
5993
6098
|
if (!bytes) {
|
|
5994
6099
|
return [
|
|
5995
|
-
inspectionFailed(
|
|
6100
|
+
inspectionFailed(check17.id, "Could not inspect PDF for bleed boxes.", {
|
|
5996
6101
|
file: ctx.pdfPath
|
|
5997
6102
|
})
|
|
5998
6103
|
];
|
|
@@ -6002,14 +6107,14 @@ var check16 = {
|
|
|
6002
6107
|
const bleed = findBox(bytes, "BleedBox");
|
|
6003
6108
|
if (!media) {
|
|
6004
6109
|
return [
|
|
6005
|
-
inspectionFailed(
|
|
6110
|
+
inspectionFailed(check17.id, "Could not parse MediaBox from PDF.", {
|
|
6006
6111
|
file: ctx.pdfPath
|
|
6007
6112
|
})
|
|
6008
6113
|
];
|
|
6009
6114
|
}
|
|
6010
6115
|
if (!trim && !bleed) {
|
|
6011
6116
|
return [
|
|
6012
|
-
finding(
|
|
6117
|
+
finding(check17.id, {
|
|
6013
6118
|
severity: "warning",
|
|
6014
6119
|
message: "No TrimBox or BleedBox found. Bleed area cannot be verified.",
|
|
6015
6120
|
file: ctx.pdfPath
|
|
@@ -6022,7 +6127,7 @@ var check16 = {
|
|
|
6022
6127
|
const bleedSize = ctx.config.validate.pdf.bleedSize;
|
|
6023
6128
|
if (mediaW - trimW < bleedSize * 2 * 0.9) {
|
|
6024
6129
|
return [
|
|
6025
|
-
finding(
|
|
6130
|
+
finding(check17.id, {
|
|
6026
6131
|
severity: "warning",
|
|
6027
6132
|
message: `Bleed area appears insufficient. Expected at least ${bleedSize}pt on each side.`,
|
|
6028
6133
|
file: ctx.pdfPath
|
|
@@ -6033,10 +6138,10 @@ var check16 = {
|
|
|
6033
6138
|
return [];
|
|
6034
6139
|
}
|
|
6035
6140
|
};
|
|
6036
|
-
registerCheck(
|
|
6141
|
+
registerCheck(check17);
|
|
6037
6142
|
|
|
6038
6143
|
// src/checks/pdf/pdfx-metadata.ts
|
|
6039
|
-
var
|
|
6144
|
+
var check18 = {
|
|
6040
6145
|
id: "pdf.print.pdfx-metadata",
|
|
6041
6146
|
name: "PDF/X Metadata",
|
|
6042
6147
|
description: "Verifies PDF/X DOCINFO metadata via qpdf JSON",
|
|
@@ -6055,22 +6160,22 @@ var check17 = {
|
|
|
6055
6160
|
const objects = parseQpdfObjectsJson(stdout);
|
|
6056
6161
|
if (!objects) {
|
|
6057
6162
|
return [
|
|
6058
|
-
inspectionFailed(
|
|
6163
|
+
inspectionFailed(check18.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
|
|
6059
6164
|
];
|
|
6060
6165
|
}
|
|
6061
|
-
return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(
|
|
6166
|
+
return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check18.id, {
|
|
6062
6167
|
severity: "error",
|
|
6063
6168
|
message,
|
|
6064
6169
|
file: ctx.pdfPath
|
|
6065
6170
|
}));
|
|
6066
6171
|
} catch {
|
|
6067
6172
|
return [
|
|
6068
|
-
inspectionFailed(
|
|
6173
|
+
inspectionFailed(check18.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
|
|
6069
6174
|
];
|
|
6070
6175
|
}
|
|
6071
6176
|
}
|
|
6072
6177
|
};
|
|
6073
|
-
registerCheck(
|
|
6178
|
+
registerCheck(check18);
|
|
6074
6179
|
|
|
6075
6180
|
// src/checks/source/markdownlint.ts
|
|
6076
6181
|
import { readFile as readFile10 } from "node:fs/promises";
|
|
@@ -6125,7 +6230,7 @@ async function loadConfig(configPath) {
|
|
|
6125
6230
|
return {};
|
|
6126
6231
|
}
|
|
6127
6232
|
var MARKDOWNLINT_CHECK_ID = "source.markdownlint";
|
|
6128
|
-
var
|
|
6233
|
+
var check19 = {
|
|
6129
6234
|
id: MARKDOWNLINT_CHECK_ID,
|
|
6130
6235
|
name: "Markdownlint",
|
|
6131
6236
|
description: "Runs markdownlint with project config to validate Markdown files",
|
|
@@ -6150,7 +6255,7 @@ var check18 = {
|
|
|
6150
6255
|
continue;
|
|
6151
6256
|
for (const v of violations) {
|
|
6152
6257
|
out.push({
|
|
6153
|
-
checkId:
|
|
6258
|
+
checkId: check19.id,
|
|
6154
6259
|
severity: "warning",
|
|
6155
6260
|
message: `${v.ruleDescription} (${v.ruleNames.join("/")})`,
|
|
6156
6261
|
file,
|
|
@@ -6163,7 +6268,7 @@ var check18 = {
|
|
|
6163
6268
|
return out;
|
|
6164
6269
|
}
|
|
6165
6270
|
};
|
|
6166
|
-
registerCheck(
|
|
6271
|
+
registerCheck(check19);
|
|
6167
6272
|
|
|
6168
6273
|
// src/checks/source/htmlhint.ts
|
|
6169
6274
|
import { existsSync as existsSync7 } from "node:fs";
|
|
@@ -6171,7 +6276,7 @@ import { readFile as readFile11 } from "node:fs/promises";
|
|
|
6171
6276
|
import htmlhint from "htmlhint";
|
|
6172
6277
|
var { HTMLHint } = htmlhint;
|
|
6173
6278
|
var CONFIG_NAMES2 = [".htmlhintrc"];
|
|
6174
|
-
var
|
|
6279
|
+
var check20 = {
|
|
6175
6280
|
id: "source.htmlhint",
|
|
6176
6281
|
name: "HTMLHint",
|
|
6177
6282
|
description: "Runs htmlhint with project config to validate generated HTML",
|
|
@@ -6191,7 +6296,7 @@ var check19 = {
|
|
|
6191
6296
|
const html = await readFile11(ctx.htmlPath, "utf8");
|
|
6192
6297
|
const messages = HTMLHint.verify(html, ruleset);
|
|
6193
6298
|
return messages.map((m) => ({
|
|
6194
|
-
checkId:
|
|
6299
|
+
checkId: check20.id,
|
|
6195
6300
|
severity: m.type === "error" ? "error" : "warning",
|
|
6196
6301
|
message: `${m.rule.id}: ${m.message}`,
|
|
6197
6302
|
file: ctx.htmlPath,
|
|
@@ -6200,7 +6305,7 @@ var check19 = {
|
|
|
6200
6305
|
}));
|
|
6201
6306
|
}
|
|
6202
6307
|
};
|
|
6203
|
-
registerCheck(
|
|
6308
|
+
registerCheck(check20);
|
|
6204
6309
|
|
|
6205
6310
|
// src/checks/source/stylelint.ts
|
|
6206
6311
|
import { readFile as readFile12 } from "node:fs/promises";
|
|
@@ -6593,7 +6698,7 @@ function checkCssRaw(css, from) {
|
|
|
6593
6698
|
}
|
|
6594
6699
|
|
|
6595
6700
|
// src/checks/source/stylelint.ts
|
|
6596
|
-
var
|
|
6701
|
+
var check21 = {
|
|
6597
6702
|
id: "source.stylelint",
|
|
6598
6703
|
name: "Print-safety CSS",
|
|
6599
6704
|
description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
|
|
@@ -6613,7 +6718,7 @@ var check20 = {
|
|
|
6613
6718
|
css = await readFile12(file, "utf8");
|
|
6614
6719
|
} catch (err) {
|
|
6615
6720
|
results.push({
|
|
6616
|
-
checkId:
|
|
6721
|
+
checkId: check21.id,
|
|
6617
6722
|
severity: "error",
|
|
6618
6723
|
message: `Cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`,
|
|
6619
6724
|
file
|
|
@@ -6622,7 +6727,7 @@ var check20 = {
|
|
|
6622
6727
|
}
|
|
6623
6728
|
for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
|
|
6624
6729
|
results.push({
|
|
6625
|
-
checkId:
|
|
6730
|
+
checkId: check21.id,
|
|
6626
6731
|
severity: w.severity,
|
|
6627
6732
|
message: `${w.rule}: ${w.message}`,
|
|
6628
6733
|
file,
|
|
@@ -6634,7 +6739,7 @@ var check20 = {
|
|
|
6634
6739
|
return results;
|
|
6635
6740
|
}
|
|
6636
6741
|
};
|
|
6637
|
-
registerCheck(
|
|
6742
|
+
registerCheck(check21);
|
|
6638
6743
|
|
|
6639
6744
|
// src/checks/source/css-ownership.ts
|
|
6640
6745
|
import { readFile as readFile13 } from "node:fs/promises";
|
|
@@ -6818,7 +6923,7 @@ function checkOwnership(root, file, maps) {
|
|
|
6818
6923
|
});
|
|
6819
6924
|
return out;
|
|
6820
6925
|
}
|
|
6821
|
-
var
|
|
6926
|
+
var check22 = {
|
|
6822
6927
|
id: "source.css-ownership",
|
|
6823
6928
|
name: "CSS ownership contract",
|
|
6824
6929
|
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.",
|
|
@@ -6841,7 +6946,7 @@ var check21 = {
|
|
|
6841
6946
|
} catch (err) {
|
|
6842
6947
|
return [
|
|
6843
6948
|
{
|
|
6844
|
-
checkId:
|
|
6949
|
+
checkId: check22.id,
|
|
6845
6950
|
severity: "error",
|
|
6846
6951
|
message: `Failed to parse CSS ownership contract "${configPath}": ${err instanceof Error ? err.message : String(err)}`,
|
|
6847
6952
|
file: configPath
|
|
@@ -6883,7 +6988,7 @@ var check21 = {
|
|
|
6883
6988
|
}
|
|
6884
6989
|
for (const v of violations) {
|
|
6885
6990
|
results.push({
|
|
6886
|
-
checkId:
|
|
6991
|
+
checkId: check22.id,
|
|
6887
6992
|
severity: "warning",
|
|
6888
6993
|
message: v.message,
|
|
6889
6994
|
file: absFile,
|
|
@@ -6895,7 +7000,7 @@ var check21 = {
|
|
|
6895
7000
|
return results;
|
|
6896
7001
|
}
|
|
6897
7002
|
};
|
|
6898
|
-
registerCheck(
|
|
7003
|
+
registerCheck(check22);
|
|
6899
7004
|
|
|
6900
7005
|
// src/checks/source/local-refs.ts
|
|
6901
7006
|
import { existsSync as existsSync9 } from "node:fs";
|
|
@@ -7286,7 +7391,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
|
|
|
7286
7391
|
}
|
|
7287
7392
|
|
|
7288
7393
|
// src/checks/source/local-refs.ts
|
|
7289
|
-
var
|
|
7394
|
+
var check23 = {
|
|
7290
7395
|
id: "source.links.local-refs",
|
|
7291
7396
|
name: "Local Markdown References",
|
|
7292
7397
|
description: "Checks local markdown links and image refs resolve on disk",
|
|
@@ -7294,12 +7399,12 @@ var check22 = {
|
|
|
7294
7399
|
phase: "pre-build",
|
|
7295
7400
|
async run(ctx) {
|
|
7296
7401
|
const results = [];
|
|
7297
|
-
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx,
|
|
7402
|
+
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
|
|
7298
7403
|
if (isNonFilesystemRef(ref))
|
|
7299
7404
|
continue;
|
|
7300
7405
|
const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
|
|
7301
7406
|
if (escape) {
|
|
7302
|
-
results.push(finding(
|
|
7407
|
+
results.push(finding(check23.id, {
|
|
7303
7408
|
severity: "error",
|
|
7304
7409
|
message: escape,
|
|
7305
7410
|
file,
|
|
@@ -7309,7 +7414,7 @@ var check22 = {
|
|
|
7309
7414
|
}
|
|
7310
7415
|
if (localRefExists(ref, kind, file, ctx.inputDir))
|
|
7311
7416
|
continue;
|
|
7312
|
-
results.push(finding(
|
|
7417
|
+
results.push(finding(check23.id, {
|
|
7313
7418
|
severity: kind === "image" ? "warning" : "error",
|
|
7314
7419
|
code: kind === "image" ? "missing-image-placeholder" : undefined,
|
|
7315
7420
|
message: kind === "image" ? `Local image not found; build will substitute a magenta placeholder: ${ref}` : `Local reference not found: ${ref}`,
|
|
@@ -7334,7 +7439,7 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
|
|
|
7334
7439
|
return existsSync9(fileRelative);
|
|
7335
7440
|
}
|
|
7336
7441
|
}
|
|
7337
|
-
registerCheck(
|
|
7442
|
+
registerCheck(check23);
|
|
7338
7443
|
|
|
7339
7444
|
// src/lib/build-staging.ts
|
|
7340
7445
|
import path8 from "node:path";
|
|
@@ -7600,7 +7705,7 @@ async function createStageRoot() {
|
|
|
7600
7705
|
}
|
|
7601
7706
|
|
|
7602
7707
|
// src/checks/source/dangling-links.ts
|
|
7603
|
-
var
|
|
7708
|
+
var check24 = {
|
|
7604
7709
|
id: "source.links.dangling",
|
|
7605
7710
|
name: "Dangling Links",
|
|
7606
7711
|
description: "Flags relative links the printed book cannot open (their href is dropped at print time)",
|
|
@@ -7608,10 +7713,10 @@ var check23 = {
|
|
|
7608
7713
|
phase: "pre-build",
|
|
7609
7714
|
async run(ctx) {
|
|
7610
7715
|
const results = [];
|
|
7611
|
-
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx,
|
|
7716
|
+
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check24.id, results)) {
|
|
7612
7717
|
if (kind !== "link" || isPrintResolvableHref(ref))
|
|
7613
7718
|
continue;
|
|
7614
|
-
results.push(finding(
|
|
7719
|
+
results.push(finding(check24.id, {
|
|
7615
7720
|
severity: "warning",
|
|
7616
7721
|
code: "dangling-link",
|
|
7617
7722
|
data: { ref },
|
|
@@ -7623,11 +7728,11 @@ var check23 = {
|
|
|
7623
7728
|
return results;
|
|
7624
7729
|
}
|
|
7625
7730
|
};
|
|
7626
|
-
registerCheck(
|
|
7731
|
+
registerCheck(check24);
|
|
7627
7732
|
|
|
7628
7733
|
// src/checks/source/accessibility-alt-text.ts
|
|
7629
7734
|
import { readFile as readFile16 } from "node:fs/promises";
|
|
7630
|
-
var
|
|
7735
|
+
var check25 = {
|
|
7631
7736
|
id: "source.accessibility.alt-text",
|
|
7632
7737
|
name: "Image Alt Text",
|
|
7633
7738
|
description: "Checks markdown images include non-empty alt text",
|
|
@@ -7656,7 +7761,7 @@ var check24 = {
|
|
|
7656
7761
|
if (alt.trim())
|
|
7657
7762
|
continue;
|
|
7658
7763
|
results.push({
|
|
7659
|
-
checkId:
|
|
7764
|
+
checkId: check25.id,
|
|
7660
7765
|
severity: "warning",
|
|
7661
7766
|
message: "Image is missing alt text",
|
|
7662
7767
|
file,
|
|
@@ -7681,11 +7786,11 @@ function extractImageAlts(line) {
|
|
|
7681
7786
|
}
|
|
7682
7787
|
return alts;
|
|
7683
7788
|
}
|
|
7684
|
-
registerCheck(
|
|
7789
|
+
registerCheck(check25);
|
|
7685
7790
|
|
|
7686
7791
|
// src/checks/source/accessibility-heading-order.ts
|
|
7687
7792
|
import { readFile as readFile17 } from "node:fs/promises";
|
|
7688
|
-
var
|
|
7793
|
+
var check26 = {
|
|
7689
7794
|
id: "source.accessibility.heading-order",
|
|
7690
7795
|
name: "Heading Order",
|
|
7691
7796
|
description: "Checks markdown heading levels do not jump by more than one",
|
|
@@ -7713,7 +7818,7 @@ var check25 = {
|
|
|
7713
7818
|
if (inFence)
|
|
7714
7819
|
continue;
|
|
7715
7820
|
const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
|
|
7716
|
-
if (comment && parseDisableDirective(comment[1])?.includes(
|
|
7821
|
+
if (comment && parseDisableDirective(comment[1])?.includes(check26.id)) {
|
|
7717
7822
|
silencedLine = i + 1;
|
|
7718
7823
|
continue;
|
|
7719
7824
|
}
|
|
@@ -7723,7 +7828,7 @@ var check25 = {
|
|
|
7723
7828
|
const level = match[1].length;
|
|
7724
7829
|
if (prevLevel != null && level > prevLevel + 1 && i !== silencedLine) {
|
|
7725
7830
|
results.push({
|
|
7726
|
-
checkId:
|
|
7831
|
+
checkId: check26.id,
|
|
7727
7832
|
severity: "warning",
|
|
7728
7833
|
message: `Heading level jump from h${prevLevel} to h${level}`,
|
|
7729
7834
|
file,
|
|
@@ -7737,7 +7842,7 @@ var check25 = {
|
|
|
7737
7842
|
return results;
|
|
7738
7843
|
}
|
|
7739
7844
|
};
|
|
7740
|
-
registerCheck(
|
|
7845
|
+
registerCheck(check26);
|
|
7741
7846
|
|
|
7742
7847
|
// src/checks/source/layout-markers.ts
|
|
7743
7848
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
@@ -7848,7 +7953,7 @@ async function collectKnownClasses(ctx, plugins) {
|
|
|
7848
7953
|
}
|
|
7849
7954
|
return names;
|
|
7850
7955
|
}
|
|
7851
|
-
var
|
|
7956
|
+
var check27 = {
|
|
7852
7957
|
id: "source.markdown.layout-markers",
|
|
7853
7958
|
name: "Layout Markers",
|
|
7854
7959
|
description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class and any other class no stylesheet mentions",
|
|
@@ -7860,7 +7965,7 @@ var check26 = {
|
|
|
7860
7965
|
return [];
|
|
7861
7966
|
const results = [];
|
|
7862
7967
|
const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
|
|
7863
|
-
results.push(inspectionFailed(
|
|
7968
|
+
results.push(inspectionFailed(check27.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
|
|
7864
7969
|
});
|
|
7865
7970
|
const md = createMarkdownRenderer(plugins);
|
|
7866
7971
|
let known;
|
|
@@ -7879,7 +7984,7 @@ var check26 = {
|
|
|
7879
7984
|
if (names.has(cls) || warnedLines.has(use.line))
|
|
7880
7985
|
continue;
|
|
7881
7986
|
const guess = nearestKnownClass(cls, names);
|
|
7882
|
-
results.push(finding(
|
|
7987
|
+
results.push(finding(check27.id, {
|
|
7883
7988
|
severity: "warning",
|
|
7884
7989
|
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.` : ""),
|
|
7885
7990
|
file,
|
|
@@ -7889,7 +7994,7 @@ var check26 = {
|
|
|
7889
7994
|
}
|
|
7890
7995
|
}
|
|
7891
7996
|
for (const w of env.layoutWarnings ?? []) {
|
|
7892
|
-
results.push(finding(
|
|
7997
|
+
results.push(finding(check27.id, {
|
|
7893
7998
|
severity: w.severity ?? "warning",
|
|
7894
7999
|
message: w.message,
|
|
7895
8000
|
file,
|
|
@@ -7898,13 +8003,13 @@ var check26 = {
|
|
|
7898
8003
|
}));
|
|
7899
8004
|
}
|
|
7900
8005
|
} catch (error2) {
|
|
7901
|
-
results.push(inspectionFailed(
|
|
8006
|
+
results.push(inspectionFailed(check27.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
|
|
7902
8007
|
}
|
|
7903
8008
|
}
|
|
7904
8009
|
return results;
|
|
7905
8010
|
}
|
|
7906
8011
|
};
|
|
7907
|
-
registerCheck(
|
|
8012
|
+
registerCheck(check27);
|
|
7908
8013
|
|
|
7909
8014
|
// src/checks/source/merge-markers.ts
|
|
7910
8015
|
import { readFile as readFile19 } from "node:fs/promises";
|
|
@@ -7937,7 +8042,7 @@ var CLOSE_SENTINEL = ">>>>>>> online version";
|
|
|
7937
8042
|
function withoutOnlineTag(relPath) {
|
|
7938
8043
|
return relPath.replace(/\.online(?=\.[^./]*$|$)/, "");
|
|
7939
8044
|
}
|
|
7940
|
-
var
|
|
8045
|
+
var check28 = {
|
|
7941
8046
|
id: "source.sync.merge-markers",
|
|
7942
8047
|
name: "Combined Versions",
|
|
7943
8048
|
description: "Finds passages and files still holding two versions (yours and the online copy) after a sync",
|
|
@@ -7951,7 +8056,7 @@ var check27 = {
|
|
|
7951
8056
|
try {
|
|
7952
8057
|
content = await readFile19(file, "utf8");
|
|
7953
8058
|
} catch {
|
|
7954
|
-
results.push(inspectionFailed(
|
|
8059
|
+
results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
|
|
7955
8060
|
continue;
|
|
7956
8061
|
}
|
|
7957
8062
|
if (!content.includes(OPEN_SENTINEL) && !content.includes(CLOSE_SENTINEL))
|
|
@@ -7963,7 +8068,7 @@ var check27 = {
|
|
|
7963
8068
|
const line = lines[i].endsWith("\r") ? lines[i].slice(0, -1) : lines[i];
|
|
7964
8069
|
if (line === OPEN_SENTINEL) {
|
|
7965
8070
|
inBlock = true;
|
|
7966
|
-
results.push(finding(
|
|
8071
|
+
results.push(finding(check28.id, {
|
|
7967
8072
|
severity: "error",
|
|
7968
8073
|
code: "two-versions-passage",
|
|
7969
8074
|
message: "This passage has two versions (yours and the online copy) — keep what you want, then delete the marker lines.",
|
|
@@ -7972,7 +8077,7 @@ var check27 = {
|
|
|
7972
8077
|
}));
|
|
7973
8078
|
} else if (line === CLOSE_SENTINEL) {
|
|
7974
8079
|
if (!inBlock) {
|
|
7975
|
-
results.push(finding(
|
|
8080
|
+
results.push(finding(check28.id, {
|
|
7976
8081
|
severity: "error",
|
|
7977
8082
|
code: "leftover-version-marker",
|
|
7978
8083
|
message: "This marker line is left over from combining two versions — delete it.",
|
|
@@ -7993,7 +8098,7 @@ var check27 = {
|
|
|
7993
8098
|
});
|
|
7994
8099
|
for (const sibling of siblings.sort()) {
|
|
7995
8100
|
const rel = path9.relative(ctx.inputDir, sibling).split(path9.sep).join("/");
|
|
7996
|
-
results.push(finding(
|
|
8101
|
+
results.push(finding(check28.id, {
|
|
7997
8102
|
severity: "warning",
|
|
7998
8103
|
code: "kept-both-versions",
|
|
7999
8104
|
message: `Two versions of ${withoutOnlineTag(rel)} are in your book — keep the one you want, then delete the other.`,
|
|
@@ -8001,12 +8106,12 @@ var check27 = {
|
|
|
8001
8106
|
}));
|
|
8002
8107
|
}
|
|
8003
8108
|
} catch (error2) {
|
|
8004
|
-
results.push(inspectionFailed(
|
|
8109
|
+
results.push(inspectionFailed(check28.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
|
|
8005
8110
|
}
|
|
8006
8111
|
return results;
|
|
8007
8112
|
}
|
|
8008
8113
|
};
|
|
8009
|
-
registerCheck(
|
|
8114
|
+
registerCheck(check28);
|
|
8010
8115
|
|
|
8011
8116
|
// src/checks/source/characters.ts
|
|
8012
8117
|
import { readFile as readFile20 } from "node:fs/promises";
|
|
@@ -8121,7 +8226,7 @@ function codePointLabel(char) {
|
|
|
8121
8226
|
|
|
8122
8227
|
// src/checks/source/characters.ts
|
|
8123
8228
|
var PROBE = new RegExp(CHARACTER_PATTERN.source, "u");
|
|
8124
|
-
var
|
|
8229
|
+
var check29 = {
|
|
8125
8230
|
id: "source.characters",
|
|
8126
8231
|
name: "Unusual Characters",
|
|
8127
8232
|
description: "Finds invisible and control characters, odd spaces, and pasted typography (em dashes, curly quotes, emoji) in markdown",
|
|
@@ -8134,7 +8239,7 @@ var check28 = {
|
|
|
8134
8239
|
try {
|
|
8135
8240
|
content = await readFile20(file, "utf8");
|
|
8136
8241
|
} catch {
|
|
8137
|
-
results.push(inspectionFailed(
|
|
8242
|
+
results.push(inspectionFailed(check29.id, `Could not read source file: ${file}`, { file }));
|
|
8138
8243
|
continue;
|
|
8139
8244
|
}
|
|
8140
8245
|
if (!PROBE.test(content))
|
|
@@ -8145,7 +8250,7 @@ var check28 = {
|
|
|
8145
8250
|
for (let i = 0;i < lines.length; i++) {
|
|
8146
8251
|
const line = lines[i];
|
|
8147
8252
|
const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
|
|
8148
|
-
if (comment && parseDisableDirective(comment[1])?.includes(
|
|
8253
|
+
if (comment && parseDisableDirective(comment[1])?.includes(check29.id)) {
|
|
8149
8254
|
silencedLine = i + 1;
|
|
8150
8255
|
continue;
|
|
8151
8256
|
}
|
|
@@ -8154,7 +8259,7 @@ var check28 = {
|
|
|
8154
8259
|
for (const match of line.matchAll(CHARACTER_PATTERN)) {
|
|
8155
8260
|
const char = match[0];
|
|
8156
8261
|
const { name, code, fix } = describeCharacter(char);
|
|
8157
|
-
results.push(finding(
|
|
8262
|
+
results.push(finding(check29.id, {
|
|
8158
8263
|
severity: code === "typographer" ? "info" : "warning",
|
|
8159
8264
|
code,
|
|
8160
8265
|
message: `${name} (${codePointLabel(char)}). ${fix}`,
|
|
@@ -8169,7 +8274,7 @@ var check28 = {
|
|
|
8169
8274
|
return results;
|
|
8170
8275
|
}
|
|
8171
8276
|
};
|
|
8172
|
-
registerCheck(
|
|
8277
|
+
registerCheck(check29);
|
|
8173
8278
|
|
|
8174
8279
|
// src/checks/asset/image-file-size.ts
|
|
8175
8280
|
import { stat as stat4 } from "node:fs/promises";
|
|
@@ -8395,7 +8500,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
|
|
|
8395
8500
|
}
|
|
8396
8501
|
|
|
8397
8502
|
// src/checks/asset/image-file-size.ts
|
|
8398
|
-
var
|
|
8503
|
+
var check30 = {
|
|
8399
8504
|
id: "asset.image.file-size",
|
|
8400
8505
|
name: "Image File Size",
|
|
8401
8506
|
description: "Checks that image files do not exceed the maximum size limit",
|
|
@@ -8416,14 +8521,14 @@ var check29 = {
|
|
|
8416
8521
|
if (info3.size > maxSize) {
|
|
8417
8522
|
const sizeMb = (info3.size / 1e6).toFixed(1);
|
|
8418
8523
|
const maxMb = (maxSize / 1e6).toFixed(1);
|
|
8419
|
-
results.push(finding(
|
|
8524
|
+
results.push(finding(check30.id, {
|
|
8420
8525
|
severity: "warning",
|
|
8421
8526
|
message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
|
|
8422
8527
|
file
|
|
8423
8528
|
}));
|
|
8424
8529
|
}
|
|
8425
8530
|
} catch {
|
|
8426
|
-
results.push(inspectionFailed(
|
|
8531
|
+
results.push(inspectionFailed(check30.id, `Could not stat image file: ${file}`, {
|
|
8427
8532
|
file
|
|
8428
8533
|
}));
|
|
8429
8534
|
}
|
|
@@ -8431,10 +8536,10 @@ var check29 = {
|
|
|
8431
8536
|
return results;
|
|
8432
8537
|
}
|
|
8433
8538
|
};
|
|
8434
|
-
registerCheck(
|
|
8539
|
+
registerCheck(check30);
|
|
8435
8540
|
|
|
8436
8541
|
// src/checks/asset/image-resolution.ts
|
|
8437
|
-
var
|
|
8542
|
+
var check31 = {
|
|
8438
8543
|
id: "asset.image.resolution",
|
|
8439
8544
|
name: "Image Resolution",
|
|
8440
8545
|
description: "Checks source image DPI from embedded density metadata",
|
|
@@ -8456,7 +8561,7 @@ var check30 = {
|
|
|
8456
8561
|
const { xDpi, yDpi } = info3;
|
|
8457
8562
|
if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
|
|
8458
8563
|
results.push({
|
|
8459
|
-
checkId:
|
|
8564
|
+
checkId: check31.id,
|
|
8460
8565
|
severity: "warning",
|
|
8461
8566
|
message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
|
|
8462
8567
|
file
|
|
@@ -8466,7 +8571,7 @@ var check30 = {
|
|
|
8466
8571
|
return results;
|
|
8467
8572
|
}
|
|
8468
8573
|
};
|
|
8469
|
-
registerCheck(
|
|
8574
|
+
registerCheck(check31);
|
|
8470
8575
|
|
|
8471
8576
|
// src/checks/asset/image-color-space.ts
|
|
8472
8577
|
var LABEL = {
|
|
@@ -8474,7 +8579,7 @@ var LABEL = {
|
|
|
8474
8579
|
gray: "Gray",
|
|
8475
8580
|
cmyk: "CMYK"
|
|
8476
8581
|
};
|
|
8477
|
-
var
|
|
8582
|
+
var check32 = {
|
|
8478
8583
|
id: "asset.image.color-space",
|
|
8479
8584
|
name: "Image Color Space",
|
|
8480
8585
|
description: "Validates image color spaces against allowed list",
|
|
@@ -8495,7 +8600,7 @@ var check31 = {
|
|
|
8495
8600
|
const cs = info3?.colorSpace;
|
|
8496
8601
|
if (cs && !allowedLower.has(cs)) {
|
|
8497
8602
|
results.push({
|
|
8498
|
-
checkId:
|
|
8603
|
+
checkId: check32.id,
|
|
8499
8604
|
severity: "warning",
|
|
8500
8605
|
message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
|
|
8501
8606
|
file
|
|
@@ -8505,10 +8610,10 @@ var check31 = {
|
|
|
8505
8610
|
return results;
|
|
8506
8611
|
}
|
|
8507
8612
|
};
|
|
8508
|
-
registerCheck(
|
|
8613
|
+
registerCheck(check32);
|
|
8509
8614
|
|
|
8510
8615
|
// src/checks/asset/image-alpha.ts
|
|
8511
|
-
var
|
|
8616
|
+
var check33 = {
|
|
8512
8617
|
id: "asset.image.alpha-channel",
|
|
8513
8618
|
name: "Image Alpha Channel",
|
|
8514
8619
|
description: "Checks for alpha channels in PNG/TIFF images",
|
|
@@ -8526,7 +8631,7 @@ var check32 = {
|
|
|
8526
8631
|
const info3 = await inspectImage(file);
|
|
8527
8632
|
if (info3?.hasAlpha) {
|
|
8528
8633
|
results.push({
|
|
8529
|
-
checkId:
|
|
8634
|
+
checkId: check33.id,
|
|
8530
8635
|
severity: "warning",
|
|
8531
8636
|
message: "Image contains alpha channel, which may cause print issues",
|
|
8532
8637
|
file
|
|
@@ -8536,10 +8641,10 @@ var check32 = {
|
|
|
8536
8641
|
return results;
|
|
8537
8642
|
}
|
|
8538
8643
|
};
|
|
8539
|
-
registerCheck(
|
|
8644
|
+
registerCheck(check33);
|
|
8540
8645
|
|
|
8541
8646
|
// src/checks/asset/image-tac.ts
|
|
8542
|
-
var
|
|
8647
|
+
var check34 = {
|
|
8543
8648
|
id: "asset.image.tac-raster",
|
|
8544
8649
|
name: "Image TAC (Raster)",
|
|
8545
8650
|
description: "Rasterizes and checks TAC per image using Ghostscript",
|
|
@@ -8558,7 +8663,7 @@ var check33 = {
|
|
|
8558
8663
|
const ghostscript = await resolveGhostscript();
|
|
8559
8664
|
if (!ghostscript) {
|
|
8560
8665
|
return [
|
|
8561
|
-
inspectionFailed(
|
|
8666
|
+
inspectionFailed(check34.id, "Could not inspect image ink coverage: Ghostscript executable not found")
|
|
8562
8667
|
];
|
|
8563
8668
|
}
|
|
8564
8669
|
for (const file of files) {
|
|
@@ -8576,7 +8681,7 @@ var check33 = {
|
|
|
8576
8681
|
if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
|
|
8577
8682
|
const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
|
|
8578
8683
|
if (tac > maxTac) {
|
|
8579
|
-
results.push(finding(
|
|
8684
|
+
results.push(finding(check34.id, {
|
|
8580
8685
|
severity: "warning",
|
|
8581
8686
|
message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
|
|
8582
8687
|
file,
|
|
@@ -8588,16 +8693,16 @@ var check33 = {
|
|
|
8588
8693
|
}
|
|
8589
8694
|
}
|
|
8590
8695
|
} catch {
|
|
8591
|
-
results.push(inspectionFailed(
|
|
8696
|
+
results.push(inspectionFailed(check34.id, `Could not inspect image ink coverage: ${file}`, { file }));
|
|
8592
8697
|
}
|
|
8593
8698
|
}
|
|
8594
8699
|
return results;
|
|
8595
8700
|
}
|
|
8596
8701
|
};
|
|
8597
|
-
registerCheck(
|
|
8702
|
+
registerCheck(check34);
|
|
8598
8703
|
|
|
8599
8704
|
// src/checks/asset/approved-fonts.ts
|
|
8600
|
-
var
|
|
8705
|
+
var check35 = {
|
|
8601
8706
|
id: "asset.font.approved-files",
|
|
8602
8707
|
name: "Approved Font Files",
|
|
8603
8708
|
description: "Checks font files against the approved file patterns",
|
|
@@ -8636,7 +8741,7 @@ var check34 = {
|
|
|
8636
8741
|
for (const font of allFonts) {
|
|
8637
8742
|
if (!approvedFonts.has(font)) {
|
|
8638
8743
|
results.push({
|
|
8639
|
-
checkId:
|
|
8744
|
+
checkId: check35.id,
|
|
8640
8745
|
severity: "warning",
|
|
8641
8746
|
message: "Font file not in approved list",
|
|
8642
8747
|
file: font
|
|
@@ -8646,7 +8751,7 @@ var check34 = {
|
|
|
8646
8751
|
return results;
|
|
8647
8752
|
}
|
|
8648
8753
|
};
|
|
8649
|
-
registerCheck(
|
|
8754
|
+
registerCheck(check35);
|
|
8650
8755
|
|
|
8651
8756
|
// src/checks/asset/font-license.ts
|
|
8652
8757
|
import { existsSync as existsSync10 } from "node:fs";
|
|
@@ -8661,7 +8766,7 @@ var LICENSE_NAMES = [
|
|
|
8661
8766
|
"OFL-1.1.txt",
|
|
8662
8767
|
"COPYING"
|
|
8663
8768
|
];
|
|
8664
|
-
var
|
|
8769
|
+
var check36 = {
|
|
8665
8770
|
id: "asset.font.license",
|
|
8666
8771
|
name: "Font License",
|
|
8667
8772
|
description: "Checks for license files in font directories",
|
|
@@ -8688,7 +8793,7 @@ var check35 = {
|
|
|
8688
8793
|
const hasLicense = LICENSE_NAMES.some((name) => existsSync10(resolve6(fontDir, name)));
|
|
8689
8794
|
if (!hasLicense) {
|
|
8690
8795
|
results.push({
|
|
8691
|
-
checkId:
|
|
8796
|
+
checkId: check36.id,
|
|
8692
8797
|
severity: "warning",
|
|
8693
8798
|
message: `No font license file found in directory`,
|
|
8694
8799
|
file: fontDir
|
|
@@ -8698,10 +8803,10 @@ var check35 = {
|
|
|
8698
8803
|
return results;
|
|
8699
8804
|
}
|
|
8700
8805
|
};
|
|
8701
|
-
registerCheck(
|
|
8806
|
+
registerCheck(check36);
|
|
8702
8807
|
|
|
8703
8808
|
// src/checks/heuristic/text-density.ts
|
|
8704
|
-
var
|
|
8809
|
+
var check37 = {
|
|
8705
8810
|
id: "heuristic.whitespace.text-density",
|
|
8706
8811
|
name: "Text Density",
|
|
8707
8812
|
description: "Checks characters-per-page ratio",
|
|
@@ -8730,7 +8835,7 @@ var check36 = {
|
|
|
8730
8835
|
});
|
|
8731
8836
|
if (lowPages.length > 0) {
|
|
8732
8837
|
results.push({
|
|
8733
|
-
checkId:
|
|
8838
|
+
checkId: check37.id,
|
|
8734
8839
|
severity: "info",
|
|
8735
8840
|
message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
|
|
8736
8841
|
file: ctx.pdfPath
|
|
@@ -8738,7 +8843,7 @@ var check36 = {
|
|
|
8738
8843
|
}
|
|
8739
8844
|
if (highPages.length > 0) {
|
|
8740
8845
|
results.push({
|
|
8741
|
-
checkId:
|
|
8846
|
+
checkId: check37.id,
|
|
8742
8847
|
severity: "info",
|
|
8743
8848
|
message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
|
|
8744
8849
|
file: ctx.pdfPath
|
|
@@ -8747,11 +8852,11 @@ var check36 = {
|
|
|
8747
8852
|
return results;
|
|
8748
8853
|
}
|
|
8749
8854
|
};
|
|
8750
|
-
registerCheck(
|
|
8855
|
+
registerCheck(check37);
|
|
8751
8856
|
|
|
8752
8857
|
// src/checks/heuristic/section-density.ts
|
|
8753
8858
|
import { readFile as readFile22 } from "node:fs/promises";
|
|
8754
|
-
var
|
|
8859
|
+
var check38 = {
|
|
8755
8860
|
id: "heuristic.chunking.section-density",
|
|
8756
8861
|
name: "Section Density",
|
|
8757
8862
|
description: "Checks heading/paragraph/callout density from source Markdown",
|
|
@@ -8777,7 +8882,7 @@ var check37 = {
|
|
|
8777
8882
|
const line = lines[i];
|
|
8778
8883
|
if (/^#{1,6}\s/.test(line)) {
|
|
8779
8884
|
if (paragraphCount > maxParas) {
|
|
8780
|
-
results.push(finding(
|
|
8885
|
+
results.push(finding(check38.id, {
|
|
8781
8886
|
severity: "info",
|
|
8782
8887
|
message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
|
|
8783
8888
|
file,
|
|
@@ -8797,7 +8902,7 @@ var check37 = {
|
|
|
8797
8902
|
}
|
|
8798
8903
|
}
|
|
8799
8904
|
if (paragraphCount > maxParas) {
|
|
8800
|
-
results.push(finding(
|
|
8905
|
+
results.push(finding(check38.id, {
|
|
8801
8906
|
severity: "info",
|
|
8802
8907
|
message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
|
|
8803
8908
|
file,
|
|
@@ -8805,7 +8910,7 @@ var check37 = {
|
|
|
8805
8910
|
}));
|
|
8806
8911
|
}
|
|
8807
8912
|
} catch {
|
|
8808
|
-
results.push(inspectionFailed(
|
|
8913
|
+
results.push(inspectionFailed(check38.id, `Could not read source file: ${file}`, {
|
|
8809
8914
|
file
|
|
8810
8915
|
}));
|
|
8811
8916
|
}
|
|
@@ -8813,10 +8918,10 @@ var check37 = {
|
|
|
8813
8918
|
return results;
|
|
8814
8919
|
}
|
|
8815
8920
|
};
|
|
8816
|
-
registerCheck(
|
|
8921
|
+
registerCheck(check38);
|
|
8817
8922
|
|
|
8818
8923
|
// src/checks/heuristic/layer-count.ts
|
|
8819
|
-
var
|
|
8924
|
+
var check39 = {
|
|
8820
8925
|
id: "heuristic.decoration.layer-count",
|
|
8821
8926
|
name: "Layer Count",
|
|
8822
8927
|
description: "Counts image objects per page",
|
|
@@ -8841,7 +8946,7 @@ var check38 = {
|
|
|
8841
8946
|
heavyPages.sort((a, b) => a - b);
|
|
8842
8947
|
return [
|
|
8843
8948
|
{
|
|
8844
|
-
checkId:
|
|
8949
|
+
checkId: check39.id,
|
|
8845
8950
|
severity: "info",
|
|
8846
8951
|
message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
|
|
8847
8952
|
file: ctx.pdfPath
|
|
@@ -8851,10 +8956,10 @@ var check38 = {
|
|
|
8851
8956
|
return [];
|
|
8852
8957
|
}
|
|
8853
8958
|
};
|
|
8854
|
-
registerCheck(
|
|
8959
|
+
registerCheck(check39);
|
|
8855
8960
|
|
|
8856
8961
|
// src/checks/heuristic/placement-variance.ts
|
|
8857
|
-
var
|
|
8962
|
+
var check40 = {
|
|
8858
8963
|
id: "heuristic.layout.placement-variance",
|
|
8859
8964
|
name: "Placement Variance",
|
|
8860
8965
|
description: "Analyzes text baseline coordinates for layout consistency",
|
|
@@ -8872,7 +8977,7 @@ var check39 = {
|
|
|
8872
8977
|
const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
|
|
8873
8978
|
return [
|
|
8874
8979
|
{
|
|
8875
|
-
checkId:
|
|
8980
|
+
checkId: check40.id,
|
|
8876
8981
|
severity: "info",
|
|
8877
8982
|
message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
|
|
8878
8983
|
file: ctx.pdfPath
|
|
@@ -8880,21 +8985,21 @@ var check39 = {
|
|
|
8880
8985
|
];
|
|
8881
8986
|
}
|
|
8882
8987
|
};
|
|
8883
|
-
registerCheck(
|
|
8988
|
+
registerCheck(check40);
|
|
8884
8989
|
|
|
8885
8990
|
// src/checks/tool-check.ts
|
|
8886
8991
|
async function checkToolAvailability(config, opts = {}) {
|
|
8887
8992
|
const { checks: checks2 } = selectChecks(opts, config);
|
|
8888
8993
|
const toolToChecks = new Map;
|
|
8889
|
-
for (const
|
|
8890
|
-
if (!
|
|
8994
|
+
for (const check41 of checks2) {
|
|
8995
|
+
if (!check41.requiredTools?.length)
|
|
8891
8996
|
continue;
|
|
8892
|
-
for (const tool of
|
|
8997
|
+
for (const tool of check41.requiredTools) {
|
|
8893
8998
|
const existing = toolToChecks.get(tool);
|
|
8894
8999
|
if (existing) {
|
|
8895
|
-
existing.push(
|
|
9000
|
+
existing.push(check41.id);
|
|
8896
9001
|
} else {
|
|
8897
|
-
toolToChecks.set(tool, [
|
|
9002
|
+
toolToChecks.set(tool, [check41.id]);
|
|
8898
9003
|
}
|
|
8899
9004
|
}
|
|
8900
9005
|
}
|
|
@@ -8910,11 +9015,11 @@ async function checkToolAvailability(config, opts = {}) {
|
|
|
8910
9015
|
const missing = results.filter((r) => !r.found).map((r) => r.tool);
|
|
8911
9016
|
const missingSet = new Set(missing);
|
|
8912
9017
|
const skippedChecks = [];
|
|
8913
|
-
for (const
|
|
8914
|
-
if (!
|
|
9018
|
+
for (const check41 of checks2) {
|
|
9019
|
+
if (!check41.requiredTools?.length)
|
|
8915
9020
|
continue;
|
|
8916
|
-
if (
|
|
8917
|
-
skippedChecks.push(
|
|
9021
|
+
if (check41.requiredTools.some((t) => missingSet.has(t))) {
|
|
9022
|
+
skippedChecks.push(check41.id);
|
|
8918
9023
|
}
|
|
8919
9024
|
}
|
|
8920
9025
|
return { available, missing, skippedChecks, toolToChecks };
|
|
@@ -9394,16 +9499,16 @@ async function runChecks(ctx, opts = {}) {
|
|
|
9394
9499
|
}
|
|
9395
9500
|
const release = retainPdfCache();
|
|
9396
9501
|
try {
|
|
9397
|
-
for (const
|
|
9502
|
+
for (const check41 of checks2) {
|
|
9398
9503
|
try {
|
|
9399
|
-
let results = await
|
|
9400
|
-
const severityOverride = getCheckSeverityOverride(
|
|
9504
|
+
let results = await check41.run(ctx);
|
|
9505
|
+
const severityOverride = getCheckSeverityOverride(check41.id, ctx.config);
|
|
9401
9506
|
if (severityOverride) {
|
|
9402
9507
|
for (const r of results) {
|
|
9403
9508
|
r.severity = severityOverride;
|
|
9404
9509
|
}
|
|
9405
9510
|
}
|
|
9406
|
-
const lintLevel = ruleLevel(ctx.config.lint.rules,
|
|
9511
|
+
const lintLevel = ruleLevel(ctx.config.lint.rules, check41.id);
|
|
9407
9512
|
if (lintLevel === "off")
|
|
9408
9513
|
results = [];
|
|
9409
9514
|
else if (lintLevel) {
|
|
@@ -9411,15 +9516,15 @@ async function runChecks(ctx, opts = {}) {
|
|
|
9411
9516
|
r.severity = lintLevel === "warn" ? "warning" : "error";
|
|
9412
9517
|
}
|
|
9413
9518
|
if (results.length === 0) {
|
|
9414
|
-
passed.push(
|
|
9519
|
+
passed.push(check41.id);
|
|
9415
9520
|
} else {
|
|
9416
9521
|
allResults.push(...results);
|
|
9417
9522
|
}
|
|
9418
9523
|
} catch (err) {
|
|
9419
9524
|
allResults.push({
|
|
9420
|
-
checkId:
|
|
9525
|
+
checkId: check41.id,
|
|
9421
9526
|
severity: "error",
|
|
9422
|
-
message: `Check "${
|
|
9527
|
+
message: `Check "${check41.id}" threw: ${err instanceof Error ? err.message : String(err)}`
|
|
9423
9528
|
});
|
|
9424
9529
|
}
|
|
9425
9530
|
}
|
|
@@ -9711,7 +9816,7 @@ async function executeValidation(args) {
|
|
|
9711
9816
|
};
|
|
9712
9817
|
if (args.fix) {
|
|
9713
9818
|
const selected = selectChecks(runnerOptions, config).checks;
|
|
9714
|
-
if (selected.some((
|
|
9819
|
+
if (selected.some((check41) => check41.id === MARKDOWNLINT_CHECK_ID)) {
|
|
9715
9820
|
await applyMarkdownlintFixes(context);
|
|
9716
9821
|
} else {
|
|
9717
9822
|
log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
|
|
@@ -10256,7 +10361,7 @@ class PdfOutput {
|
|
|
10256
10361
|
const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
|
|
10257
10362
|
await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
|
|
10258
10363
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
10259
|
-
const { buildNativePdf } = await import("./engine-
|
|
10364
|
+
const { buildNativePdf } = await import("./engine-9xb1ky8d.js");
|
|
10260
10365
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
10261
10366
|
title: config.title,
|
|
10262
10367
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
@@ -10279,7 +10384,7 @@ class PdfOutput {
|
|
|
10279
10384
|
const icc = opts.iccPath ?? config.pdfx.icc;
|
|
10280
10385
|
effectiveIccPath = await resolveIccProfile(icc, manifestDir, opts.iccPath);
|
|
10281
10386
|
if (await hasLiveTransparency(await fsp2.readFile(rawPdf))) {
|
|
10282
|
-
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.");
|
|
10387
|
+
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).");
|
|
10283
10388
|
}
|
|
10284
10389
|
const blankGradientPages = await findPostScriptShadingPages(await fsp2.readFile(rawPdf));
|
|
10285
10390
|
if (blankGradientPages.length > 0) {
|
|
@@ -10301,7 +10406,8 @@ class PdfOutput {
|
|
|
10301
10406
|
pdfx: pdfxMode,
|
|
10302
10407
|
title: config.title,
|
|
10303
10408
|
maxTac: config.ink.maxTac,
|
|
10304
|
-
stagingDir: stage
|
|
10409
|
+
stagingDir: stage,
|
|
10410
|
+
rasterDpi: config.pdfx.rasterDpi
|
|
10305
10411
|
});
|
|
10306
10412
|
}
|
|
10307
10413
|
if (gates.postValidate) {
|
|
@@ -10512,8 +10618,17 @@ async function renderPreviewBook(inputPath, config, opts) {
|
|
|
10512
10618
|
}
|
|
10513
10619
|
});
|
|
10514
10620
|
}
|
|
10515
|
-
function injectPreviewScripts(html,
|
|
10516
|
-
const
|
|
10621
|
+
function injectPreviewScripts(html, page) {
|
|
10622
|
+
const area = page ? JSON.stringify({
|
|
10623
|
+
width: page.width,
|
|
10624
|
+
height: page.height,
|
|
10625
|
+
bleed: page.bleed ?? 0,
|
|
10626
|
+
safe: page.safe ?? 0,
|
|
10627
|
+
artSafe: page.artSafe,
|
|
10628
|
+
preset: page.preset,
|
|
10629
|
+
spec: page.spec
|
|
10630
|
+
}) : "";
|
|
10631
|
+
const meta = area ? ` <meta name="gp-print-area" content='${area.replace(/&/g, "&").replace(/'/g, "'")}'>
|
|
10517
10632
|
` : "";
|
|
10518
10633
|
const scripts = meta + ` <script src="/engine/gutterpress-viewer.js"></script>
|
|
10519
10634
|
` + `<script src="/preview/scripts/preview-interface.js"></script>
|
|
@@ -10549,7 +10664,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
|
|
|
10549
10664
|
for (const ref of nextImageRefs)
|
|
10550
10665
|
imageRefs.add(ref);
|
|
10551
10666
|
}
|
|
10552
|
-
await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, config.page
|
|
10667
|
+
await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, config.page && { ...config.page, preset: config.preset, spec: config.spec }), "utf-8");
|
|
10553
10668
|
}
|
|
10554
10669
|
function isDotPathUnderRoot(candidatePath, root) {
|
|
10555
10670
|
const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
|