gutterpress 0.11.20-alpha.1 → 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-cmf4nbpm.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-7shp2kmj.js → cli-7z29z4wz.js} +1 -1
- package/dist/{cli-mmm22cza.js → cli-gh0ym80r.js} +1 -1
- package/dist/{cli-rtpm04dv.js → cli-j802s31j.js} +5 -5
- package/dist/{cli-x1nfhaf1.js → cli-zacevwcy.js} +284 -180
- package/dist/cli.js +12 -12
- package/dist/{doctor-0d6esj02.js → doctor-fe7tcs4v.js} +2 -2
- package/dist/{engine-1v8kmv3a.js → engine-9xb1ky8d.js} +1 -1
- package/dist/{engine-adxptzf0.js → engine-yax79k9m.js} +2 -2
- package/dist/{ext-65nfh7st.js → ext-ahwxt6gv.js} +4 -4
- package/dist/{gutterpress-viewer-0r4rt4de.js → gutterpress-viewer-retj6ym2.js} +343 -98
- package/dist/{index-pnvzgftg.js → index-3nr5rzzp.js} +5 -5
- package/dist/{index-cngw658w.js → index-6xwtb7cw.js} +1 -1
- package/dist/{index-1y885h1j.js → index-razpty47.js} +282 -178
- package/dist/index.js +3 -3
- package/dist/lib/presets.d.ts +3 -1
- package/dist/{manifest.schema-nrykd2c6.json → manifest.schema-74s1ykyv.json} +8 -2
- package/dist/{new-y4a2xz3g.js → new-ftd4fec4.js} +4 -4
- package/dist/{preflight-kt7wfgbd.js → preflight-8sqarcsr.js} +4 -4
- package/dist/preview/file-watcher.d.ts +7 -1
- package/dist/{preview-bridge-evecsh4w.js → preview-bridge-8wjcjec3.js} +3 -2
- package/dist/{preview-interface-k2mj5kvq.js → preview-interface-nnmrxyqm.js} +37 -41
- package/dist/{preview-5ze728kp.js → preview-q76xn95h.js} +4 -4
- package/dist/{preview-shell-7jfe6vsc.js → preview-shell-wk9k3gcd.js} +18 -6
- package/dist/{publish-5tpcyysh.js → publish-7gdy6gbf.js} +4 -4
- package/dist/schema/manifest.types.d.ts +27 -6
- package/dist/{validate-p0hkqkfe.js → validate-e3ryegce.js} +4 -4
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
getAssetsDir,
|
|
10
10
|
lastPageMode,
|
|
11
11
|
parseSize
|
|
12
|
-
} from "./index-
|
|
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: {
|
|
@@ -159,7 +164,8 @@ var DTRPG_PRESET = {
|
|
|
159
164
|
height: 810,
|
|
160
165
|
tolerance: 0.5,
|
|
161
166
|
bleed: 9,
|
|
162
|
-
safe:
|
|
167
|
+
safe: 36,
|
|
168
|
+
artSafe: 18
|
|
163
169
|
},
|
|
164
170
|
ink: {
|
|
165
171
|
maxTac: 240,
|
|
@@ -188,6 +194,10 @@ var DTRPG_PRESET = {
|
|
|
188
194
|
"pdf.print.embedded-fonts": {
|
|
189
195
|
enabled: true,
|
|
190
196
|
severity: "error"
|
|
197
|
+
},
|
|
198
|
+
"pdf.print.safe-margins": {
|
|
199
|
+
enabled: true,
|
|
200
|
+
severity: "error"
|
|
191
201
|
}
|
|
192
202
|
},
|
|
193
203
|
source: {
|
|
@@ -220,6 +230,7 @@ var DTRPG_PRESET = {
|
|
|
220
230
|
}
|
|
221
231
|
};
|
|
222
232
|
var BOOK_PRESET = {
|
|
233
|
+
id: "book",
|
|
223
234
|
defaultTargets: [],
|
|
224
235
|
extensions: [],
|
|
225
236
|
source: {
|
|
@@ -238,7 +249,8 @@ var BOOK_PRESET = {
|
|
|
238
249
|
height: 648,
|
|
239
250
|
tolerance: 0.5,
|
|
240
251
|
bleed: undefined,
|
|
241
|
-
safe: 18
|
|
252
|
+
safe: 18,
|
|
253
|
+
artSafe: undefined
|
|
242
254
|
},
|
|
243
255
|
ink: {
|
|
244
256
|
maxTac: 400,
|
|
@@ -260,6 +272,10 @@ var BOOK_PRESET = {
|
|
|
260
272
|
enabled: true,
|
|
261
273
|
severity: "error"
|
|
262
274
|
},
|
|
275
|
+
"pdf.print.safe-margins": {
|
|
276
|
+
enabled: true,
|
|
277
|
+
severity: "warning"
|
|
278
|
+
},
|
|
263
279
|
"pdf.print.pdfx-markers": false,
|
|
264
280
|
"pdf.print.pdfx-metadata": false
|
|
265
281
|
},
|
|
@@ -294,6 +310,7 @@ var BOOK_PRESET = {
|
|
|
294
310
|
};
|
|
295
311
|
var CUSTOM_PRESET = {
|
|
296
312
|
...BOOK_PRESET,
|
|
313
|
+
id: "custom",
|
|
297
314
|
page: null,
|
|
298
315
|
defaultTargets: []
|
|
299
316
|
};
|
|
@@ -339,7 +356,8 @@ var DTRPG_TARGET = {
|
|
|
339
356
|
"pdf.structure.qpdf": { enabled: true, severity: "error" },
|
|
340
357
|
"pdf.print.pdfx-markers": { enabled: true, severity: "error" },
|
|
341
358
|
"pdf.print.pdfx-metadata": { enabled: true, severity: "error" },
|
|
342
|
-
"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" }
|
|
343
361
|
},
|
|
344
362
|
assets: {
|
|
345
363
|
allowedColorSpaces: [...DTRPG_PRESET.validate.assets.allowedColorSpaces]
|
|
@@ -354,7 +372,8 @@ var DTRPG_TARGET = {
|
|
|
354
372
|
"pdf.structure.qpdf",
|
|
355
373
|
"pdf.print.pdfx-markers",
|
|
356
374
|
"pdf.print.pdfx-metadata",
|
|
357
|
-
"pdf.print.embedded-fonts"
|
|
375
|
+
"pdf.print.embedded-fonts",
|
|
376
|
+
"pdf.print.safe-margins"
|
|
358
377
|
],
|
|
359
378
|
requiredTools: ["qpdf", "gs"]
|
|
360
379
|
};
|
|
@@ -369,6 +388,7 @@ var ITCH_TARGET = {
|
|
|
369
388
|
"pdf.print.pdfx-markers": false,
|
|
370
389
|
"pdf.print.pdfx-metadata": false,
|
|
371
390
|
"pdf.print.ink-coverage": false,
|
|
391
|
+
"pdf.print.safe-margins": false,
|
|
372
392
|
"asset.image.tac-raster": false
|
|
373
393
|
},
|
|
374
394
|
assets: {
|
|
@@ -593,7 +613,8 @@ function resolveCustomPage(c, m) {
|
|
|
593
613
|
height,
|
|
594
614
|
tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5,
|
|
595
615
|
bleed: c.page?.bleed ?? m.page?.bleed,
|
|
596
|
-
safe: c.page?.safe ?? m.page?.safe
|
|
616
|
+
safe: c.page?.safe ?? m.page?.safe,
|
|
617
|
+
artSafe: c.page?.artSafe ?? m.page?.artSafe
|
|
597
618
|
};
|
|
598
619
|
}
|
|
599
620
|
function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
@@ -638,6 +659,8 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
|
638
659
|
return {
|
|
639
660
|
title: c.title ?? m.title ?? "Document",
|
|
640
661
|
authors: c.authors ?? m.authors ?? [],
|
|
662
|
+
preset: preset.id,
|
|
663
|
+
spec: preset.spec,
|
|
641
664
|
styles: c.styles ?? m.styles,
|
|
642
665
|
extensions,
|
|
643
666
|
targets: resolveTargets(c.targets ?? m.targets, preset.defaultTargets),
|
|
@@ -5994,7 +6017,7 @@ import git from "isomorphic-git";
|
|
|
5994
6017
|
// package.json
|
|
5995
6018
|
var package_default = {
|
|
5996
6019
|
name: "gutterpress",
|
|
5997
|
-
version: "0.11.20-alpha.
|
|
6020
|
+
version: "0.11.20-alpha.2",
|
|
5998
6021
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
5999
6022
|
author: "itlackey",
|
|
6000
6023
|
license: "MPL-2.0",
|
|
@@ -6987,6 +7010,79 @@ var check3 = {
|
|
|
6987
7010
|
};
|
|
6988
7011
|
registerCheck(check3);
|
|
6989
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
|
+
|
|
6990
7086
|
// src/checks/pdf/pdfx-structure.ts
|
|
6991
7087
|
function isObject(value) {
|
|
6992
7088
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -7095,7 +7191,7 @@ function getPdfxMetadataIssues(objects, flavor) {
|
|
|
7095
7191
|
}
|
|
7096
7192
|
|
|
7097
7193
|
// src/checks/pdf/pdfx-markers.ts
|
|
7098
|
-
var
|
|
7194
|
+
var check5 = {
|
|
7099
7195
|
id: "pdf.print.pdfx-markers",
|
|
7100
7196
|
name: "PDF/X Markers",
|
|
7101
7197
|
description: "Checks PDF/X OutputIntent structure via qpdf JSON",
|
|
@@ -7114,22 +7210,22 @@ var check4 = {
|
|
|
7114
7210
|
const objects = parseQpdfObjectsJson(stdout);
|
|
7115
7211
|
if (!objects) {
|
|
7116
7212
|
return [
|
|
7117
|
-
inspectionFailed(
|
|
7213
|
+
inspectionFailed(check5.id, "Unable to parse qpdf JSON output for PDF/X checks.", { file: ctx.pdfPath })
|
|
7118
7214
|
];
|
|
7119
7215
|
}
|
|
7120
|
-
return getPdfxOutputIntentIssues(objects).map((message) => finding(
|
|
7216
|
+
return getPdfxOutputIntentIssues(objects).map((message) => finding(check5.id, {
|
|
7121
7217
|
severity: "error",
|
|
7122
7218
|
message,
|
|
7123
7219
|
file: ctx.pdfPath
|
|
7124
7220
|
}));
|
|
7125
7221
|
} catch {
|
|
7126
7222
|
return [
|
|
7127
|
-
inspectionFailed(
|
|
7223
|
+
inspectionFailed(check5.id, "Failed to inspect PDF objects with qpdf for PDF/X markers.", { file: ctx.pdfPath })
|
|
7128
7224
|
];
|
|
7129
7225
|
}
|
|
7130
7226
|
}
|
|
7131
7227
|
};
|
|
7132
|
-
registerCheck(
|
|
7228
|
+
registerCheck(check5);
|
|
7133
7229
|
|
|
7134
7230
|
// src/lib/pdf-parse.ts
|
|
7135
7231
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
@@ -7182,7 +7278,7 @@ async function getPerPageInkCoverage(pdfPath) {
|
|
|
7182
7278
|
}
|
|
7183
7279
|
|
|
7184
7280
|
// src/checks/pdf/color-spaces.ts
|
|
7185
|
-
var
|
|
7281
|
+
var check6 = {
|
|
7186
7282
|
id: "pdf.print.color-spaces",
|
|
7187
7283
|
name: "Color Spaces",
|
|
7188
7284
|
description: "Checks for forbidden color spaces (DeviceRGB, Lab, Separation, DeviceN)",
|
|
@@ -7195,7 +7291,7 @@ var check5 = {
|
|
|
7195
7291
|
const results = [];
|
|
7196
7292
|
if (/\/DeviceRGB/.test(bytes)) {
|
|
7197
7293
|
results.push({
|
|
7198
|
-
checkId:
|
|
7294
|
+
checkId: check6.id,
|
|
7199
7295
|
severity: "error",
|
|
7200
7296
|
message: "DeviceRGB found (interior must be CMYK or grayscale only).",
|
|
7201
7297
|
file: ctx.pdfPath
|
|
@@ -7203,7 +7299,7 @@ var check5 = {
|
|
|
7203
7299
|
}
|
|
7204
7300
|
if (/\/Lab\b/.test(bytes)) {
|
|
7205
7301
|
results.push({
|
|
7206
|
-
checkId:
|
|
7302
|
+
checkId: check6.id,
|
|
7207
7303
|
severity: "error",
|
|
7208
7304
|
message: "Lab color space found (not allowed).",
|
|
7209
7305
|
file: ctx.pdfPath
|
|
@@ -7211,7 +7307,7 @@ var check5 = {
|
|
|
7211
7307
|
}
|
|
7212
7308
|
if (/\/Separation/.test(bytes)) {
|
|
7213
7309
|
results.push({
|
|
7214
|
-
checkId:
|
|
7310
|
+
checkId: check6.id,
|
|
7215
7311
|
severity: "error",
|
|
7216
7312
|
message: "Spot color (Separation) found (not allowed).",
|
|
7217
7313
|
file: ctx.pdfPath
|
|
@@ -7219,7 +7315,7 @@ var check5 = {
|
|
|
7219
7315
|
}
|
|
7220
7316
|
if (/\/DeviceN/.test(bytes)) {
|
|
7221
7317
|
results.push({
|
|
7222
|
-
checkId:
|
|
7318
|
+
checkId: check6.id,
|
|
7223
7319
|
severity: "error",
|
|
7224
7320
|
message: "Spot color (DeviceN) found (not allowed).",
|
|
7225
7321
|
file: ctx.pdfPath
|
|
@@ -7228,10 +7324,10 @@ var check5 = {
|
|
|
7228
7324
|
return results;
|
|
7229
7325
|
}
|
|
7230
7326
|
};
|
|
7231
|
-
registerCheck(
|
|
7327
|
+
registerCheck(check6);
|
|
7232
7328
|
|
|
7233
7329
|
// src/checks/pdf/embedded-fonts.ts
|
|
7234
|
-
var
|
|
7330
|
+
var check7 = {
|
|
7235
7331
|
id: "pdf.print.embedded-fonts",
|
|
7236
7332
|
name: "Embedded Fonts",
|
|
7237
7333
|
description: "Verifies all fonts in the PDF are embedded",
|
|
@@ -7247,7 +7343,7 @@ var check6 = {
|
|
|
7247
7343
|
if (fonts.length === 0) {
|
|
7248
7344
|
return [
|
|
7249
7345
|
{
|
|
7250
|
-
checkId:
|
|
7346
|
+
checkId: check7.id,
|
|
7251
7347
|
severity: "warning",
|
|
7252
7348
|
message: "No fonts detected (unexpected).",
|
|
7253
7349
|
file: ctx.pdfPath,
|
|
@@ -7260,7 +7356,7 @@ var check6 = {
|
|
|
7260
7356
|
const names = notEmbedded.map((f) => f.name);
|
|
7261
7357
|
return [
|
|
7262
7358
|
{
|
|
7263
|
-
checkId:
|
|
7359
|
+
checkId: check7.id,
|
|
7264
7360
|
severity: "error",
|
|
7265
7361
|
message: `Not all fonts are embedded (${names.join(", ")}). Check @font-face and Chromium output.`,
|
|
7266
7362
|
file: ctx.pdfPath,
|
|
@@ -7272,10 +7368,10 @@ var check6 = {
|
|
|
7272
7368
|
return [];
|
|
7273
7369
|
}
|
|
7274
7370
|
};
|
|
7275
|
-
registerCheck(
|
|
7371
|
+
registerCheck(check7);
|
|
7276
7372
|
|
|
7277
7373
|
// src/checks/pdf/ink-coverage.ts
|
|
7278
|
-
var
|
|
7374
|
+
var check8 = {
|
|
7279
7375
|
id: "pdf.print.ink-coverage",
|
|
7280
7376
|
name: "Ink Coverage (TAC)",
|
|
7281
7377
|
description: "Checks total area coverage (TAC) against maximum ink limits",
|
|
@@ -7289,7 +7385,7 @@ var check7 = {
|
|
|
7289
7385
|
if (!inkResult.ok) {
|
|
7290
7386
|
return [
|
|
7291
7387
|
{
|
|
7292
|
-
checkId:
|
|
7388
|
+
checkId: check8.id,
|
|
7293
7389
|
severity: "warning",
|
|
7294
7390
|
message: "Ink coverage could not be measured",
|
|
7295
7391
|
file: ctx.pdfPath,
|
|
@@ -7314,7 +7410,7 @@ var check7 = {
|
|
|
7314
7410
|
}
|
|
7315
7411
|
return [
|
|
7316
7412
|
{
|
|
7317
|
-
checkId:
|
|
7413
|
+
checkId: check8.id,
|
|
7318
7414
|
severity: "warning",
|
|
7319
7415
|
message: `Total ink coverage too high on ${offending.length} page(s) (max ${maxTac.toFixed(1)}%, recommended <=${ctx.config.ink.maxTac}%)`,
|
|
7320
7416
|
file: ctx.pdfPath,
|
|
@@ -7338,10 +7434,10 @@ var check7 = {
|
|
|
7338
7434
|
];
|
|
7339
7435
|
}
|
|
7340
7436
|
};
|
|
7341
|
-
registerCheck(
|
|
7437
|
+
registerCheck(check8);
|
|
7342
7438
|
|
|
7343
7439
|
// src/checks/pdf/rasterized-pages.ts
|
|
7344
|
-
var
|
|
7440
|
+
var check9 = {
|
|
7345
7441
|
id: "pdf.print.rasterized-pages",
|
|
7346
7442
|
name: "Rasterized Pages",
|
|
7347
7443
|
description: "Detects pages that appear to be fully rasterized (CSS filters, blend modes, transparency)",
|
|
@@ -7381,7 +7477,7 @@ var check8 = {
|
|
|
7381
7477
|
rasterizedPages.sort((a, b) => a - b);
|
|
7382
7478
|
return [
|
|
7383
7479
|
{
|
|
7384
|
-
checkId:
|
|
7480
|
+
checkId: check9.id,
|
|
7385
7481
|
severity: "warning",
|
|
7386
7482
|
message: `Possible rasterized pages detected: ${rasterizedPages.join(", ")}`,
|
|
7387
7483
|
file: ctx.pdfPath,
|
|
@@ -7392,11 +7488,11 @@ var check8 = {
|
|
|
7392
7488
|
];
|
|
7393
7489
|
}
|
|
7394
7490
|
};
|
|
7395
|
-
registerCheck(
|
|
7491
|
+
registerCheck(check9);
|
|
7396
7492
|
|
|
7397
7493
|
// src/checks/pdf/black-text.ts
|
|
7398
7494
|
import { readFile as readFile15 } from "node:fs/promises";
|
|
7399
|
-
var
|
|
7495
|
+
var check10 = {
|
|
7400
7496
|
id: "pdf.print.black-text",
|
|
7401
7497
|
name: "Black Text",
|
|
7402
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)",
|
|
@@ -7412,7 +7508,7 @@ var check9 = {
|
|
|
7412
7508
|
if (found.runs === 0)
|
|
7413
7509
|
return [];
|
|
7414
7510
|
return [
|
|
7415
|
-
finding(
|
|
7511
|
+
finding(check10.id, {
|
|
7416
7512
|
severity: "warning",
|
|
7417
7513
|
message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
|
|
7418
7514
|
file: ctx.pdfPath,
|
|
@@ -7423,17 +7519,17 @@ var check9 = {
|
|
|
7423
7519
|
];
|
|
7424
7520
|
} catch {
|
|
7425
7521
|
return [
|
|
7426
|
-
inspectionFailed(
|
|
7522
|
+
inspectionFailed(check10.id, "Could not inspect the PDF's text colours.", {
|
|
7427
7523
|
file: ctx.pdfPath
|
|
7428
7524
|
})
|
|
7429
7525
|
];
|
|
7430
7526
|
}
|
|
7431
7527
|
}
|
|
7432
7528
|
};
|
|
7433
|
-
registerCheck(
|
|
7529
|
+
registerCheck(check10);
|
|
7434
7530
|
|
|
7435
7531
|
// src/checks/pdf/signature.ts
|
|
7436
|
-
var
|
|
7532
|
+
var check11 = {
|
|
7437
7533
|
id: "pdf.print.signature",
|
|
7438
7534
|
name: "Signature",
|
|
7439
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)",
|
|
@@ -7445,7 +7541,7 @@ var check10 = {
|
|
|
7445
7541
|
return [];
|
|
7446
7542
|
const doc = await loadPdf(ctx.pdfPath);
|
|
7447
7543
|
if (!doc) {
|
|
7448
|
-
return [inspectionFailed(
|
|
7544
|
+
return [inspectionFailed(check11.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
|
|
7449
7545
|
}
|
|
7450
7546
|
const size = ctx.config.print.signature;
|
|
7451
7547
|
const pages = doc.numPages;
|
|
@@ -7454,7 +7550,7 @@ var check10 = {
|
|
|
7454
7550
|
const data = { pages, signature: size };
|
|
7455
7551
|
if (pages % size !== 0) {
|
|
7456
7552
|
return [
|
|
7457
|
-
finding(
|
|
7553
|
+
finding(check11.id, {
|
|
7458
7554
|
severity: "error",
|
|
7459
7555
|
message: `Page count ${pages} does not fit a signature of ${size}: it must be a multiple of ${size}, or one short of one.`,
|
|
7460
7556
|
file: ctx.pdfPath,
|
|
@@ -7470,10 +7566,10 @@ var check10 = {
|
|
|
7470
7566
|
if (!await pageHasMarks(doc, pages))
|
|
7471
7567
|
return [];
|
|
7472
7568
|
} catch {
|
|
7473
|
-
return [inspectionFailed(
|
|
7569
|
+
return [inspectionFailed(check11.id, "Could not read the last page.", { file: ctx.pdfPath })];
|
|
7474
7570
|
}
|
|
7475
7571
|
return [
|
|
7476
|
-
finding(
|
|
7572
|
+
finding(check11.id, {
|
|
7477
7573
|
severity: "error",
|
|
7478
7574
|
message: `The last page (${pages}) has content, but the printer needs it blank for its own information.`,
|
|
7479
7575
|
file: ctx.pdfPath,
|
|
@@ -7484,10 +7580,10 @@ var check10 = {
|
|
|
7484
7580
|
];
|
|
7485
7581
|
}
|
|
7486
7582
|
};
|
|
7487
|
-
registerCheck(
|
|
7583
|
+
registerCheck(check11);
|
|
7488
7584
|
|
|
7489
7585
|
// src/checks/pdf/bookmarks.ts
|
|
7490
|
-
var
|
|
7586
|
+
var check12 = {
|
|
7491
7587
|
id: "pdf.nav.bookmarks",
|
|
7492
7588
|
name: "Bookmarks",
|
|
7493
7589
|
description: "Checks for PDF outline (bookmarks) tree",
|
|
@@ -7501,14 +7597,14 @@ var check11 = {
|
|
|
7501
7597
|
const doc = await loadPdf(ctx.pdfPath);
|
|
7502
7598
|
if (!doc) {
|
|
7503
7599
|
return [
|
|
7504
|
-
inspectionFailed(
|
|
7600
|
+
inspectionFailed(check12.id, "Could not inspect PDF for bookmarks.", {
|
|
7505
7601
|
file: ctx.pdfPath
|
|
7506
7602
|
})
|
|
7507
7603
|
];
|
|
7508
7604
|
}
|
|
7509
7605
|
if (await getOutlineCount(doc) === 0) {
|
|
7510
7606
|
return [
|
|
7511
|
-
finding(
|
|
7607
|
+
finding(check12.id, {
|
|
7512
7608
|
severity: "warning",
|
|
7513
7609
|
message: "PDF does not contain bookmarks (outline tree).",
|
|
7514
7610
|
file: ctx.pdfPath
|
|
@@ -7518,10 +7614,10 @@ var check11 = {
|
|
|
7518
7614
|
return [];
|
|
7519
7615
|
}
|
|
7520
7616
|
};
|
|
7521
|
-
registerCheck(
|
|
7617
|
+
registerCheck(check12);
|
|
7522
7618
|
|
|
7523
7619
|
// src/checks/pdf/toc-links.ts
|
|
7524
|
-
var
|
|
7620
|
+
var check13 = {
|
|
7525
7621
|
id: "pdf.nav.toc-links",
|
|
7526
7622
|
name: "TOC Links",
|
|
7527
7623
|
description: "Detects link annotations on TOC pages",
|
|
@@ -7536,7 +7632,7 @@ var check12 = {
|
|
|
7536
7632
|
if (!doc) {
|
|
7537
7633
|
return [
|
|
7538
7634
|
{
|
|
7539
|
-
checkId:
|
|
7635
|
+
checkId: check13.id,
|
|
7540
7636
|
severity: "warning",
|
|
7541
7637
|
message: "Could not inspect PDF for TOC links.",
|
|
7542
7638
|
file: ctx.pdfPath
|
|
@@ -7547,7 +7643,7 @@ var check12 = {
|
|
|
7547
7643
|
if (!hasLinks) {
|
|
7548
7644
|
return [
|
|
7549
7645
|
{
|
|
7550
|
-
checkId:
|
|
7646
|
+
checkId: check13.id,
|
|
7551
7647
|
severity: "warning",
|
|
7552
7648
|
message: "No link annotations found in PDF (expected for TOC).",
|
|
7553
7649
|
file: ctx.pdfPath
|
|
@@ -7557,10 +7653,10 @@ var check12 = {
|
|
|
7557
7653
|
return [];
|
|
7558
7654
|
}
|
|
7559
7655
|
};
|
|
7560
|
-
registerCheck(
|
|
7656
|
+
registerCheck(check13);
|
|
7561
7657
|
|
|
7562
7658
|
// src/checks/pdf/cross-refs.ts
|
|
7563
|
-
var
|
|
7659
|
+
var check14 = {
|
|
7564
7660
|
id: "pdf.nav.cross-refs",
|
|
7565
7661
|
name: "Cross References",
|
|
7566
7662
|
description: "Counts and verifies internal link annotations",
|
|
@@ -7573,7 +7669,7 @@ var check13 = {
|
|
|
7573
7669
|
if (!doc) {
|
|
7574
7670
|
return [
|
|
7575
7671
|
{
|
|
7576
|
-
checkId:
|
|
7672
|
+
checkId: check14.id,
|
|
7577
7673
|
severity: "info",
|
|
7578
7674
|
message: "Could not inspect PDF for cross-references.",
|
|
7579
7675
|
file: ctx.pdfPath
|
|
@@ -7583,7 +7679,7 @@ var check13 = {
|
|
|
7583
7679
|
const linkCount = await countLinkAnnotations(doc);
|
|
7584
7680
|
return [
|
|
7585
7681
|
{
|
|
7586
|
-
checkId:
|
|
7682
|
+
checkId: check14.id,
|
|
7587
7683
|
severity: "info",
|
|
7588
7684
|
message: `PDF contains ${linkCount} internal link annotation${linkCount !== 1 ? "s" : ""}.`,
|
|
7589
7685
|
file: ctx.pdfPath
|
|
@@ -7591,10 +7687,10 @@ var check13 = {
|
|
|
7591
7687
|
];
|
|
7592
7688
|
}
|
|
7593
7689
|
};
|
|
7594
|
-
registerCheck(
|
|
7690
|
+
registerCheck(check14);
|
|
7595
7691
|
|
|
7596
7692
|
// src/checks/pdf/page-labels.ts
|
|
7597
|
-
var
|
|
7693
|
+
var check15 = {
|
|
7598
7694
|
id: "pdf.nav.page-labels",
|
|
7599
7695
|
name: "Page Labels",
|
|
7600
7696
|
description: "Verifies PDF page labels / folio numbering",
|
|
@@ -7609,7 +7705,7 @@ var check14 = {
|
|
|
7609
7705
|
const labels = await getPageLabels(doc);
|
|
7610
7706
|
return [
|
|
7611
7707
|
{
|
|
7612
|
-
checkId:
|
|
7708
|
+
checkId: check15.id,
|
|
7613
7709
|
severity: "info",
|
|
7614
7710
|
message: labels && labels.length > 0 ? "PDF contains page labels." : "PDF does not contain explicit page labels.",
|
|
7615
7711
|
file: ctx.pdfPath
|
|
@@ -7617,10 +7713,10 @@ var check14 = {
|
|
|
7617
7713
|
];
|
|
7618
7714
|
}
|
|
7619
7715
|
};
|
|
7620
|
-
registerCheck(
|
|
7716
|
+
registerCheck(check15);
|
|
7621
7717
|
|
|
7622
7718
|
// src/checks/pdf/image-resolution.ts
|
|
7623
|
-
var
|
|
7719
|
+
var check16 = {
|
|
7624
7720
|
id: "pdf.print.image-resolution",
|
|
7625
7721
|
name: "Image Resolution",
|
|
7626
7722
|
description: "Checks DPI of images embedded in the PDF",
|
|
@@ -7647,7 +7743,7 @@ var check15 = {
|
|
|
7647
7743
|
lowResPages.sort((a, b) => a - b);
|
|
7648
7744
|
return [
|
|
7649
7745
|
{
|
|
7650
|
-
checkId:
|
|
7746
|
+
checkId: check16.id,
|
|
7651
7747
|
severity: "warning",
|
|
7652
7748
|
message: `Low-resolution images (below ${minDpi} DPI) found on pages: ${lowResPages.join(", ")}`,
|
|
7653
7749
|
file: ctx.pdfPath
|
|
@@ -7657,10 +7753,10 @@ var check15 = {
|
|
|
7657
7753
|
return [];
|
|
7658
7754
|
}
|
|
7659
7755
|
};
|
|
7660
|
-
registerCheck(
|
|
7756
|
+
registerCheck(check16);
|
|
7661
7757
|
|
|
7662
7758
|
// src/checks/pdf/transparency.ts
|
|
7663
|
-
var
|
|
7759
|
+
var check17 = {
|
|
7664
7760
|
id: "pdf.print.transparency",
|
|
7665
7761
|
name: "Transparency",
|
|
7666
7762
|
description: "Checks for transparency markers (/Transparency, /SMask, /BM) in the PDF",
|
|
@@ -7682,7 +7778,7 @@ var check16 = {
|
|
|
7682
7778
|
if (found.length > 0) {
|
|
7683
7779
|
return [
|
|
7684
7780
|
{
|
|
7685
|
-
checkId:
|
|
7781
|
+
checkId: check17.id,
|
|
7686
7782
|
severity: "warning",
|
|
7687
7783
|
message: `Transparency detected in PDF: ${found.join(", ")}. This may cause issues with some printers.`,
|
|
7688
7784
|
file: ctx.pdfPath
|
|
@@ -7692,7 +7788,7 @@ var check16 = {
|
|
|
7692
7788
|
return [];
|
|
7693
7789
|
}
|
|
7694
7790
|
};
|
|
7695
|
-
registerCheck(
|
|
7791
|
+
registerCheck(check17);
|
|
7696
7792
|
|
|
7697
7793
|
// src/checks/pdf/bleed.ts
|
|
7698
7794
|
function findBox(bytes, box) {
|
|
@@ -7702,7 +7798,7 @@ function findBox(bytes, box) {
|
|
|
7702
7798
|
return null;
|
|
7703
7799
|
return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
|
|
7704
7800
|
}
|
|
7705
|
-
var
|
|
7801
|
+
var check18 = {
|
|
7706
7802
|
id: "pdf.print.bleed",
|
|
7707
7803
|
name: "Bleed Box",
|
|
7708
7804
|
description: "Compares MediaBox vs TrimBox/BleedBox to verify bleed area",
|
|
@@ -7716,7 +7812,7 @@ var check17 = {
|
|
|
7716
7812
|
const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
|
|
7717
7813
|
if (!bytes) {
|
|
7718
7814
|
return [
|
|
7719
|
-
inspectionFailed(
|
|
7815
|
+
inspectionFailed(check18.id, "Could not inspect PDF for bleed boxes.", {
|
|
7720
7816
|
file: ctx.pdfPath
|
|
7721
7817
|
})
|
|
7722
7818
|
];
|
|
@@ -7726,14 +7822,14 @@ var check17 = {
|
|
|
7726
7822
|
const bleed = findBox(bytes, "BleedBox");
|
|
7727
7823
|
if (!media) {
|
|
7728
7824
|
return [
|
|
7729
|
-
inspectionFailed(
|
|
7825
|
+
inspectionFailed(check18.id, "Could not parse MediaBox from PDF.", {
|
|
7730
7826
|
file: ctx.pdfPath
|
|
7731
7827
|
})
|
|
7732
7828
|
];
|
|
7733
7829
|
}
|
|
7734
7830
|
if (!trim && !bleed) {
|
|
7735
7831
|
return [
|
|
7736
|
-
finding(
|
|
7832
|
+
finding(check18.id, {
|
|
7737
7833
|
severity: "warning",
|
|
7738
7834
|
message: "No TrimBox or BleedBox found. Bleed area cannot be verified.",
|
|
7739
7835
|
file: ctx.pdfPath
|
|
@@ -7746,7 +7842,7 @@ var check17 = {
|
|
|
7746
7842
|
const bleedSize = ctx.config.validate.pdf.bleedSize;
|
|
7747
7843
|
if (mediaW - trimW < bleedSize * 2 * 0.9) {
|
|
7748
7844
|
return [
|
|
7749
|
-
finding(
|
|
7845
|
+
finding(check18.id, {
|
|
7750
7846
|
severity: "warning",
|
|
7751
7847
|
message: `Bleed area appears insufficient. Expected at least ${bleedSize}pt on each side.`,
|
|
7752
7848
|
file: ctx.pdfPath
|
|
@@ -7757,10 +7853,10 @@ var check17 = {
|
|
|
7757
7853
|
return [];
|
|
7758
7854
|
}
|
|
7759
7855
|
};
|
|
7760
|
-
registerCheck(
|
|
7856
|
+
registerCheck(check18);
|
|
7761
7857
|
|
|
7762
7858
|
// src/checks/pdf/pdfx-metadata.ts
|
|
7763
|
-
var
|
|
7859
|
+
var check19 = {
|
|
7764
7860
|
id: "pdf.print.pdfx-metadata",
|
|
7765
7861
|
name: "PDF/X Metadata",
|
|
7766
7862
|
description: "Verifies PDF/X DOCINFO metadata via qpdf JSON",
|
|
@@ -7779,22 +7875,22 @@ var check18 = {
|
|
|
7779
7875
|
const objects = parseQpdfObjectsJson(stdout);
|
|
7780
7876
|
if (!objects) {
|
|
7781
7877
|
return [
|
|
7782
|
-
inspectionFailed(
|
|
7878
|
+
inspectionFailed(check19.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
|
|
7783
7879
|
];
|
|
7784
7880
|
}
|
|
7785
|
-
return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(
|
|
7881
|
+
return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check19.id, {
|
|
7786
7882
|
severity: "error",
|
|
7787
7883
|
message,
|
|
7788
7884
|
file: ctx.pdfPath
|
|
7789
7885
|
}));
|
|
7790
7886
|
} catch {
|
|
7791
7887
|
return [
|
|
7792
|
-
inspectionFailed(
|
|
7888
|
+
inspectionFailed(check19.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
|
|
7793
7889
|
];
|
|
7794
7890
|
}
|
|
7795
7891
|
}
|
|
7796
7892
|
};
|
|
7797
|
-
registerCheck(
|
|
7893
|
+
registerCheck(check19);
|
|
7798
7894
|
|
|
7799
7895
|
// src/checks/source/htmlhint.ts
|
|
7800
7896
|
import { existsSync as existsSync9 } from "node:fs";
|
|
@@ -7802,7 +7898,7 @@ import { readFile as readFile16 } from "node:fs/promises";
|
|
|
7802
7898
|
import htmlhint from "htmlhint";
|
|
7803
7899
|
var { HTMLHint } = htmlhint;
|
|
7804
7900
|
var CONFIG_NAMES2 = [".htmlhintrc"];
|
|
7805
|
-
var
|
|
7901
|
+
var check20 = {
|
|
7806
7902
|
id: "source.htmlhint",
|
|
7807
7903
|
name: "HTMLHint",
|
|
7808
7904
|
description: "Runs htmlhint with project config to validate generated HTML",
|
|
@@ -7822,7 +7918,7 @@ var check19 = {
|
|
|
7822
7918
|
const html = await readFile16(ctx.htmlPath, "utf8");
|
|
7823
7919
|
const messages = HTMLHint.verify(html, ruleset);
|
|
7824
7920
|
return messages.map((m) => ({
|
|
7825
|
-
checkId:
|
|
7921
|
+
checkId: check20.id,
|
|
7826
7922
|
severity: m.type === "error" ? "error" : "warning",
|
|
7827
7923
|
message: `${m.rule.id}: ${m.message}`,
|
|
7828
7924
|
file: ctx.htmlPath,
|
|
@@ -7831,7 +7927,7 @@ var check19 = {
|
|
|
7831
7927
|
}));
|
|
7832
7928
|
}
|
|
7833
7929
|
};
|
|
7834
|
-
registerCheck(
|
|
7930
|
+
registerCheck(check20);
|
|
7835
7931
|
|
|
7836
7932
|
// src/checks/source/stylelint.ts
|
|
7837
7933
|
import { readFile as readFile17 } from "node:fs/promises";
|
|
@@ -8224,7 +8320,7 @@ function checkCssRaw(css, from) {
|
|
|
8224
8320
|
}
|
|
8225
8321
|
|
|
8226
8322
|
// src/checks/source/stylelint.ts
|
|
8227
|
-
var
|
|
8323
|
+
var check21 = {
|
|
8228
8324
|
id: "source.stylelint",
|
|
8229
8325
|
name: "Print-safety CSS",
|
|
8230
8326
|
description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
|
|
@@ -8244,7 +8340,7 @@ var check20 = {
|
|
|
8244
8340
|
css = await readFile17(file, "utf8");
|
|
8245
8341
|
} catch (err) {
|
|
8246
8342
|
results.push({
|
|
8247
|
-
checkId:
|
|
8343
|
+
checkId: check21.id,
|
|
8248
8344
|
severity: "error",
|
|
8249
8345
|
message: `Cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`,
|
|
8250
8346
|
file
|
|
@@ -8253,7 +8349,7 @@ var check20 = {
|
|
|
8253
8349
|
}
|
|
8254
8350
|
for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
|
|
8255
8351
|
results.push({
|
|
8256
|
-
checkId:
|
|
8352
|
+
checkId: check21.id,
|
|
8257
8353
|
severity: w.severity,
|
|
8258
8354
|
message: `${w.rule}: ${w.message}`,
|
|
8259
8355
|
file,
|
|
@@ -8265,7 +8361,7 @@ var check20 = {
|
|
|
8265
8361
|
return results;
|
|
8266
8362
|
}
|
|
8267
8363
|
};
|
|
8268
|
-
registerCheck(
|
|
8364
|
+
registerCheck(check21);
|
|
8269
8365
|
|
|
8270
8366
|
// src/checks/source/css-ownership.ts
|
|
8271
8367
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
@@ -8386,7 +8482,7 @@ function checkOwnership(root, file, maps) {
|
|
|
8386
8482
|
});
|
|
8387
8483
|
return out;
|
|
8388
8484
|
}
|
|
8389
|
-
var
|
|
8485
|
+
var check22 = {
|
|
8390
8486
|
id: "source.css-ownership",
|
|
8391
8487
|
name: "CSS ownership contract",
|
|
8392
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.",
|
|
@@ -8409,7 +8505,7 @@ var check21 = {
|
|
|
8409
8505
|
} catch (err) {
|
|
8410
8506
|
return [
|
|
8411
8507
|
{
|
|
8412
|
-
checkId:
|
|
8508
|
+
checkId: check22.id,
|
|
8413
8509
|
severity: "error",
|
|
8414
8510
|
message: `Failed to parse CSS ownership contract "${configPath}": ${err instanceof Error ? err.message : String(err)}`,
|
|
8415
8511
|
file: configPath
|
|
@@ -8451,7 +8547,7 @@ var check21 = {
|
|
|
8451
8547
|
}
|
|
8452
8548
|
for (const v of violations) {
|
|
8453
8549
|
results.push({
|
|
8454
|
-
checkId:
|
|
8550
|
+
checkId: check22.id,
|
|
8455
8551
|
severity: "warning",
|
|
8456
8552
|
message: v.message,
|
|
8457
8553
|
file: absFile,
|
|
@@ -8463,7 +8559,7 @@ var check21 = {
|
|
|
8463
8559
|
return results;
|
|
8464
8560
|
}
|
|
8465
8561
|
};
|
|
8466
|
-
registerCheck(
|
|
8562
|
+
registerCheck(check22);
|
|
8467
8563
|
|
|
8468
8564
|
// src/checks/source/local-refs.ts
|
|
8469
8565
|
import { existsSync as existsSync10 } from "node:fs";
|
|
@@ -8550,7 +8646,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
|
|
|
8550
8646
|
}
|
|
8551
8647
|
|
|
8552
8648
|
// src/checks/source/local-refs.ts
|
|
8553
|
-
var
|
|
8649
|
+
var check23 = {
|
|
8554
8650
|
id: "source.links.local-refs",
|
|
8555
8651
|
name: "Local Markdown References",
|
|
8556
8652
|
description: "Checks local markdown links and image refs resolve on disk",
|
|
@@ -8558,12 +8654,12 @@ var check22 = {
|
|
|
8558
8654
|
phase: "pre-build",
|
|
8559
8655
|
async run(ctx) {
|
|
8560
8656
|
const results = [];
|
|
8561
|
-
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx,
|
|
8657
|
+
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
|
|
8562
8658
|
if (isNonFilesystemRef(ref))
|
|
8563
8659
|
continue;
|
|
8564
8660
|
const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
|
|
8565
8661
|
if (escape) {
|
|
8566
|
-
results.push(finding(
|
|
8662
|
+
results.push(finding(check23.id, {
|
|
8567
8663
|
severity: "error",
|
|
8568
8664
|
message: escape,
|
|
8569
8665
|
file,
|
|
@@ -8573,7 +8669,7 @@ var check22 = {
|
|
|
8573
8669
|
}
|
|
8574
8670
|
if (localRefExists(ref, kind, file, ctx.inputDir))
|
|
8575
8671
|
continue;
|
|
8576
|
-
results.push(finding(
|
|
8672
|
+
results.push(finding(check23.id, {
|
|
8577
8673
|
severity: kind === "image" ? "warning" : "error",
|
|
8578
8674
|
code: kind === "image" ? "missing-image-placeholder" : undefined,
|
|
8579
8675
|
message: kind === "image" ? `Local image not found; build will substitute a magenta placeholder: ${ref}` : `Local reference not found: ${ref}`,
|
|
@@ -8598,7 +8694,7 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
|
|
|
8598
8694
|
return existsSync10(fileRelative);
|
|
8599
8695
|
}
|
|
8600
8696
|
}
|
|
8601
|
-
registerCheck(
|
|
8697
|
+
registerCheck(check23);
|
|
8602
8698
|
|
|
8603
8699
|
// src/lib/build-staging.ts
|
|
8604
8700
|
import path11 from "node:path";
|
|
@@ -8864,7 +8960,7 @@ async function createStageRoot() {
|
|
|
8864
8960
|
}
|
|
8865
8961
|
|
|
8866
8962
|
// src/checks/source/dangling-links.ts
|
|
8867
|
-
var
|
|
8963
|
+
var check24 = {
|
|
8868
8964
|
id: "source.links.dangling",
|
|
8869
8965
|
name: "Dangling Links",
|
|
8870
8966
|
description: "Flags relative links the printed book cannot open (their href is dropped at print time)",
|
|
@@ -8872,10 +8968,10 @@ var check23 = {
|
|
|
8872
8968
|
phase: "pre-build",
|
|
8873
8969
|
async run(ctx) {
|
|
8874
8970
|
const results = [];
|
|
8875
|
-
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx,
|
|
8971
|
+
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check24.id, results)) {
|
|
8876
8972
|
if (kind !== "link" || isPrintResolvableHref(ref))
|
|
8877
8973
|
continue;
|
|
8878
|
-
results.push(finding(
|
|
8974
|
+
results.push(finding(check24.id, {
|
|
8879
8975
|
severity: "warning",
|
|
8880
8976
|
code: "dangling-link",
|
|
8881
8977
|
data: { ref },
|
|
@@ -8887,11 +8983,11 @@ var check23 = {
|
|
|
8887
8983
|
return results;
|
|
8888
8984
|
}
|
|
8889
8985
|
};
|
|
8890
|
-
registerCheck(
|
|
8986
|
+
registerCheck(check24);
|
|
8891
8987
|
|
|
8892
8988
|
// src/checks/source/accessibility-alt-text.ts
|
|
8893
8989
|
import { readFile as readFile20 } from "node:fs/promises";
|
|
8894
|
-
var
|
|
8990
|
+
var check25 = {
|
|
8895
8991
|
id: "source.accessibility.alt-text",
|
|
8896
8992
|
name: "Image Alt Text",
|
|
8897
8993
|
description: "Checks markdown images include non-empty alt text",
|
|
@@ -8920,7 +9016,7 @@ var check24 = {
|
|
|
8920
9016
|
if (alt.trim())
|
|
8921
9017
|
continue;
|
|
8922
9018
|
results.push({
|
|
8923
|
-
checkId:
|
|
9019
|
+
checkId: check25.id,
|
|
8924
9020
|
severity: "warning",
|
|
8925
9021
|
message: "Image is missing alt text",
|
|
8926
9022
|
file,
|
|
@@ -8945,11 +9041,11 @@ function extractImageAlts(line) {
|
|
|
8945
9041
|
}
|
|
8946
9042
|
return alts;
|
|
8947
9043
|
}
|
|
8948
|
-
registerCheck(
|
|
9044
|
+
registerCheck(check25);
|
|
8949
9045
|
|
|
8950
9046
|
// src/checks/source/accessibility-heading-order.ts
|
|
8951
9047
|
import { readFile as readFile21 } from "node:fs/promises";
|
|
8952
|
-
var
|
|
9048
|
+
var check26 = {
|
|
8953
9049
|
id: "source.accessibility.heading-order",
|
|
8954
9050
|
name: "Heading Order",
|
|
8955
9051
|
description: "Checks markdown heading levels do not jump by more than one",
|
|
@@ -8977,7 +9073,7 @@ var check25 = {
|
|
|
8977
9073
|
if (inFence)
|
|
8978
9074
|
continue;
|
|
8979
9075
|
const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
|
|
8980
|
-
if (comment && parseDisableDirective(comment[1])?.includes(
|
|
9076
|
+
if (comment && parseDisableDirective(comment[1])?.includes(check26.id)) {
|
|
8981
9077
|
silencedLine = i + 1;
|
|
8982
9078
|
continue;
|
|
8983
9079
|
}
|
|
@@ -8987,7 +9083,7 @@ var check25 = {
|
|
|
8987
9083
|
const level = match[1].length;
|
|
8988
9084
|
if (prevLevel != null && level > prevLevel + 1 && i !== silencedLine) {
|
|
8989
9085
|
results.push({
|
|
8990
|
-
checkId:
|
|
9086
|
+
checkId: check26.id,
|
|
8991
9087
|
severity: "warning",
|
|
8992
9088
|
message: `Heading level jump from h${prevLevel} to h${level}`,
|
|
8993
9089
|
file,
|
|
@@ -9001,7 +9097,7 @@ var check25 = {
|
|
|
9001
9097
|
return results;
|
|
9002
9098
|
}
|
|
9003
9099
|
};
|
|
9004
|
-
registerCheck(
|
|
9100
|
+
registerCheck(check26);
|
|
9005
9101
|
|
|
9006
9102
|
// src/checks/source/layout-markers.ts
|
|
9007
9103
|
import { readFile as readFile22 } from "node:fs/promises";
|
|
@@ -9112,7 +9208,7 @@ async function collectKnownClasses(ctx, plugins) {
|
|
|
9112
9208
|
}
|
|
9113
9209
|
return names;
|
|
9114
9210
|
}
|
|
9115
|
-
var
|
|
9211
|
+
var check27 = {
|
|
9116
9212
|
id: "source.markdown.layout-markers",
|
|
9117
9213
|
name: "Layout Markers",
|
|
9118
9214
|
description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class and any other class no stylesheet mentions",
|
|
@@ -9124,7 +9220,7 @@ var check26 = {
|
|
|
9124
9220
|
return [];
|
|
9125
9221
|
const results = [];
|
|
9126
9222
|
const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
|
|
9127
|
-
results.push(inspectionFailed(
|
|
9223
|
+
results.push(inspectionFailed(check27.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
|
|
9128
9224
|
});
|
|
9129
9225
|
const md = createMarkdownRenderer(plugins);
|
|
9130
9226
|
let known;
|
|
@@ -9143,7 +9239,7 @@ var check26 = {
|
|
|
9143
9239
|
if (names.has(cls) || warnedLines.has(use.line))
|
|
9144
9240
|
continue;
|
|
9145
9241
|
const guess = nearestKnownClass(cls, names);
|
|
9146
|
-
results.push(finding(
|
|
9242
|
+
results.push(finding(check27.id, {
|
|
9147
9243
|
severity: "warning",
|
|
9148
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.` : ""),
|
|
9149
9245
|
file,
|
|
@@ -9153,7 +9249,7 @@ var check26 = {
|
|
|
9153
9249
|
}
|
|
9154
9250
|
}
|
|
9155
9251
|
for (const w of env.layoutWarnings ?? []) {
|
|
9156
|
-
results.push(finding(
|
|
9252
|
+
results.push(finding(check27.id, {
|
|
9157
9253
|
severity: w.severity ?? "warning",
|
|
9158
9254
|
message: w.message,
|
|
9159
9255
|
file,
|
|
@@ -9162,13 +9258,13 @@ var check26 = {
|
|
|
9162
9258
|
}));
|
|
9163
9259
|
}
|
|
9164
9260
|
} catch (error2) {
|
|
9165
|
-
results.push(inspectionFailed(
|
|
9261
|
+
results.push(inspectionFailed(check27.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
|
|
9166
9262
|
}
|
|
9167
9263
|
}
|
|
9168
9264
|
return results;
|
|
9169
9265
|
}
|
|
9170
9266
|
};
|
|
9171
|
-
registerCheck(
|
|
9267
|
+
registerCheck(check27);
|
|
9172
9268
|
|
|
9173
9269
|
// src/checks/source/merge-markers.ts
|
|
9174
9270
|
import { readFile as readFile23 } from "node:fs/promises";
|
|
@@ -9201,7 +9297,7 @@ var CLOSE_SENTINEL = ">>>>>>> online version";
|
|
|
9201
9297
|
function withoutOnlineTag(relPath) {
|
|
9202
9298
|
return relPath.replace(/\.online(?=\.[^./]*$|$)/, "");
|
|
9203
9299
|
}
|
|
9204
|
-
var
|
|
9300
|
+
var check28 = {
|
|
9205
9301
|
id: "source.sync.merge-markers",
|
|
9206
9302
|
name: "Combined Versions",
|
|
9207
9303
|
description: "Finds passages and files still holding two versions (yours and the online copy) after a sync",
|
|
@@ -9215,7 +9311,7 @@ var check27 = {
|
|
|
9215
9311
|
try {
|
|
9216
9312
|
content = await readFile23(file, "utf8");
|
|
9217
9313
|
} catch {
|
|
9218
|
-
results.push(inspectionFailed(
|
|
9314
|
+
results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
|
|
9219
9315
|
continue;
|
|
9220
9316
|
}
|
|
9221
9317
|
if (!content.includes(OPEN_SENTINEL) && !content.includes(CLOSE_SENTINEL))
|
|
@@ -9227,7 +9323,7 @@ var check27 = {
|
|
|
9227
9323
|
const line = lines[i].endsWith("\r") ? lines[i].slice(0, -1) : lines[i];
|
|
9228
9324
|
if (line === OPEN_SENTINEL) {
|
|
9229
9325
|
inBlock = true;
|
|
9230
|
-
results.push(finding(
|
|
9326
|
+
results.push(finding(check28.id, {
|
|
9231
9327
|
severity: "error",
|
|
9232
9328
|
code: "two-versions-passage",
|
|
9233
9329
|
message: "This passage has two versions (yours and the online copy) — keep what you want, then delete the marker lines.",
|
|
@@ -9236,7 +9332,7 @@ var check27 = {
|
|
|
9236
9332
|
}));
|
|
9237
9333
|
} else if (line === CLOSE_SENTINEL) {
|
|
9238
9334
|
if (!inBlock) {
|
|
9239
|
-
results.push(finding(
|
|
9335
|
+
results.push(finding(check28.id, {
|
|
9240
9336
|
severity: "error",
|
|
9241
9337
|
code: "leftover-version-marker",
|
|
9242
9338
|
message: "This marker line is left over from combining two versions — delete it.",
|
|
@@ -9257,7 +9353,7 @@ var check27 = {
|
|
|
9257
9353
|
});
|
|
9258
9354
|
for (const sibling of siblings.sort()) {
|
|
9259
9355
|
const rel = path12.relative(ctx.inputDir, sibling).split(path12.sep).join("/");
|
|
9260
|
-
results.push(finding(
|
|
9356
|
+
results.push(finding(check28.id, {
|
|
9261
9357
|
severity: "warning",
|
|
9262
9358
|
code: "kept-both-versions",
|
|
9263
9359
|
message: `Two versions of ${withoutOnlineTag(rel)} are in your book — keep the one you want, then delete the other.`,
|
|
@@ -9265,12 +9361,12 @@ var check27 = {
|
|
|
9265
9361
|
}));
|
|
9266
9362
|
}
|
|
9267
9363
|
} catch (error2) {
|
|
9268
|
-
results.push(inspectionFailed(
|
|
9364
|
+
results.push(inspectionFailed(check28.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
|
|
9269
9365
|
}
|
|
9270
9366
|
return results;
|
|
9271
9367
|
}
|
|
9272
9368
|
};
|
|
9273
|
-
registerCheck(
|
|
9369
|
+
registerCheck(check28);
|
|
9274
9370
|
|
|
9275
9371
|
// src/checks/source/characters.ts
|
|
9276
9372
|
import { readFile as readFile24 } from "node:fs/promises";
|
|
@@ -9385,7 +9481,7 @@ function codePointLabel(char) {
|
|
|
9385
9481
|
|
|
9386
9482
|
// src/checks/source/characters.ts
|
|
9387
9483
|
var PROBE = new RegExp(CHARACTER_PATTERN.source, "u");
|
|
9388
|
-
var
|
|
9484
|
+
var check29 = {
|
|
9389
9485
|
id: "source.characters",
|
|
9390
9486
|
name: "Unusual Characters",
|
|
9391
9487
|
description: "Finds invisible and control characters, odd spaces, and pasted typography (em dashes, curly quotes, emoji) in markdown",
|
|
@@ -9398,7 +9494,7 @@ var check28 = {
|
|
|
9398
9494
|
try {
|
|
9399
9495
|
content = await readFile24(file, "utf8");
|
|
9400
9496
|
} catch {
|
|
9401
|
-
results.push(inspectionFailed(
|
|
9497
|
+
results.push(inspectionFailed(check29.id, `Could not read source file: ${file}`, { file }));
|
|
9402
9498
|
continue;
|
|
9403
9499
|
}
|
|
9404
9500
|
if (!PROBE.test(content))
|
|
@@ -9409,7 +9505,7 @@ var check28 = {
|
|
|
9409
9505
|
for (let i = 0;i < lines.length; i++) {
|
|
9410
9506
|
const line = lines[i];
|
|
9411
9507
|
const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
|
|
9412
|
-
if (comment && parseDisableDirective(comment[1])?.includes(
|
|
9508
|
+
if (comment && parseDisableDirective(comment[1])?.includes(check29.id)) {
|
|
9413
9509
|
silencedLine = i + 1;
|
|
9414
9510
|
continue;
|
|
9415
9511
|
}
|
|
@@ -9418,7 +9514,7 @@ var check28 = {
|
|
|
9418
9514
|
for (const match of line.matchAll(CHARACTER_PATTERN)) {
|
|
9419
9515
|
const char = match[0];
|
|
9420
9516
|
const { name, code, fix } = describeCharacter(char);
|
|
9421
|
-
results.push(finding(
|
|
9517
|
+
results.push(finding(check29.id, {
|
|
9422
9518
|
severity: code === "typographer" ? "info" : "warning",
|
|
9423
9519
|
code,
|
|
9424
9520
|
message: `${name} (${codePointLabel(char)}). ${fix}`,
|
|
@@ -9433,7 +9529,7 @@ var check28 = {
|
|
|
9433
9529
|
return results;
|
|
9434
9530
|
}
|
|
9435
9531
|
};
|
|
9436
|
-
registerCheck(
|
|
9532
|
+
registerCheck(check29);
|
|
9437
9533
|
|
|
9438
9534
|
// src/checks/asset/image-file-size.ts
|
|
9439
9535
|
import { stat as stat4 } from "node:fs/promises";
|
|
@@ -9659,7 +9755,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
|
|
|
9659
9755
|
}
|
|
9660
9756
|
|
|
9661
9757
|
// src/checks/asset/image-file-size.ts
|
|
9662
|
-
var
|
|
9758
|
+
var check30 = {
|
|
9663
9759
|
id: "asset.image.file-size",
|
|
9664
9760
|
name: "Image File Size",
|
|
9665
9761
|
description: "Checks that image files do not exceed the maximum size limit",
|
|
@@ -9680,14 +9776,14 @@ var check29 = {
|
|
|
9680
9776
|
if (info3.size > maxSize) {
|
|
9681
9777
|
const sizeMb = (info3.size / 1e6).toFixed(1);
|
|
9682
9778
|
const maxMb = (maxSize / 1e6).toFixed(1);
|
|
9683
|
-
results.push(finding(
|
|
9779
|
+
results.push(finding(check30.id, {
|
|
9684
9780
|
severity: "warning",
|
|
9685
9781
|
message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
|
|
9686
9782
|
file
|
|
9687
9783
|
}));
|
|
9688
9784
|
}
|
|
9689
9785
|
} catch {
|
|
9690
|
-
results.push(inspectionFailed(
|
|
9786
|
+
results.push(inspectionFailed(check30.id, `Could not stat image file: ${file}`, {
|
|
9691
9787
|
file
|
|
9692
9788
|
}));
|
|
9693
9789
|
}
|
|
@@ -9695,10 +9791,10 @@ var check29 = {
|
|
|
9695
9791
|
return results;
|
|
9696
9792
|
}
|
|
9697
9793
|
};
|
|
9698
|
-
registerCheck(
|
|
9794
|
+
registerCheck(check30);
|
|
9699
9795
|
|
|
9700
9796
|
// src/checks/asset/image-resolution.ts
|
|
9701
|
-
var
|
|
9797
|
+
var check31 = {
|
|
9702
9798
|
id: "asset.image.resolution",
|
|
9703
9799
|
name: "Image Resolution",
|
|
9704
9800
|
description: "Checks source image DPI from embedded density metadata",
|
|
@@ -9720,7 +9816,7 @@ var check30 = {
|
|
|
9720
9816
|
const { xDpi, yDpi } = info3;
|
|
9721
9817
|
if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
|
|
9722
9818
|
results.push({
|
|
9723
|
-
checkId:
|
|
9819
|
+
checkId: check31.id,
|
|
9724
9820
|
severity: "warning",
|
|
9725
9821
|
message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
|
|
9726
9822
|
file
|
|
@@ -9730,7 +9826,7 @@ var check30 = {
|
|
|
9730
9826
|
return results;
|
|
9731
9827
|
}
|
|
9732
9828
|
};
|
|
9733
|
-
registerCheck(
|
|
9829
|
+
registerCheck(check31);
|
|
9734
9830
|
|
|
9735
9831
|
// src/checks/asset/image-color-space.ts
|
|
9736
9832
|
var LABEL = {
|
|
@@ -9738,7 +9834,7 @@ var LABEL = {
|
|
|
9738
9834
|
gray: "Gray",
|
|
9739
9835
|
cmyk: "CMYK"
|
|
9740
9836
|
};
|
|
9741
|
-
var
|
|
9837
|
+
var check32 = {
|
|
9742
9838
|
id: "asset.image.color-space",
|
|
9743
9839
|
name: "Image Color Space",
|
|
9744
9840
|
description: "Validates image color spaces against allowed list",
|
|
@@ -9759,7 +9855,7 @@ var check31 = {
|
|
|
9759
9855
|
const cs = info3?.colorSpace;
|
|
9760
9856
|
if (cs && !allowedLower.has(cs)) {
|
|
9761
9857
|
results.push({
|
|
9762
|
-
checkId:
|
|
9858
|
+
checkId: check32.id,
|
|
9763
9859
|
severity: "warning",
|
|
9764
9860
|
message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
|
|
9765
9861
|
file
|
|
@@ -9769,10 +9865,10 @@ var check31 = {
|
|
|
9769
9865
|
return results;
|
|
9770
9866
|
}
|
|
9771
9867
|
};
|
|
9772
|
-
registerCheck(
|
|
9868
|
+
registerCheck(check32);
|
|
9773
9869
|
|
|
9774
9870
|
// src/checks/asset/image-alpha.ts
|
|
9775
|
-
var
|
|
9871
|
+
var check33 = {
|
|
9776
9872
|
id: "asset.image.alpha-channel",
|
|
9777
9873
|
name: "Image Alpha Channel",
|
|
9778
9874
|
description: "Checks for alpha channels in PNG/TIFF images",
|
|
@@ -9790,7 +9886,7 @@ var check32 = {
|
|
|
9790
9886
|
const info3 = await inspectImage(file);
|
|
9791
9887
|
if (info3?.hasAlpha) {
|
|
9792
9888
|
results.push({
|
|
9793
|
-
checkId:
|
|
9889
|
+
checkId: check33.id,
|
|
9794
9890
|
severity: "warning",
|
|
9795
9891
|
message: "Image contains alpha channel, which may cause print issues",
|
|
9796
9892
|
file
|
|
@@ -9800,10 +9896,10 @@ var check32 = {
|
|
|
9800
9896
|
return results;
|
|
9801
9897
|
}
|
|
9802
9898
|
};
|
|
9803
|
-
registerCheck(
|
|
9899
|
+
registerCheck(check33);
|
|
9804
9900
|
|
|
9805
9901
|
// src/checks/asset/image-tac.ts
|
|
9806
|
-
var
|
|
9902
|
+
var check34 = {
|
|
9807
9903
|
id: "asset.image.tac-raster",
|
|
9808
9904
|
name: "Image TAC (Raster)",
|
|
9809
9905
|
description: "Rasterizes and checks TAC per image using Ghostscript",
|
|
@@ -9822,7 +9918,7 @@ var check33 = {
|
|
|
9822
9918
|
const ghostscript = await resolveGhostscript();
|
|
9823
9919
|
if (!ghostscript) {
|
|
9824
9920
|
return [
|
|
9825
|
-
inspectionFailed(
|
|
9921
|
+
inspectionFailed(check34.id, "Could not inspect image ink coverage: Ghostscript executable not found")
|
|
9826
9922
|
];
|
|
9827
9923
|
}
|
|
9828
9924
|
for (const file of files) {
|
|
@@ -9840,7 +9936,7 @@ var check33 = {
|
|
|
9840
9936
|
if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
|
|
9841
9937
|
const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
|
|
9842
9938
|
if (tac > maxTac) {
|
|
9843
|
-
results.push(finding(
|
|
9939
|
+
results.push(finding(check34.id, {
|
|
9844
9940
|
severity: "warning",
|
|
9845
9941
|
message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
|
|
9846
9942
|
file,
|
|
@@ -9852,16 +9948,16 @@ var check33 = {
|
|
|
9852
9948
|
}
|
|
9853
9949
|
}
|
|
9854
9950
|
} catch {
|
|
9855
|
-
results.push(inspectionFailed(
|
|
9951
|
+
results.push(inspectionFailed(check34.id, `Could not inspect image ink coverage: ${file}`, { file }));
|
|
9856
9952
|
}
|
|
9857
9953
|
}
|
|
9858
9954
|
return results;
|
|
9859
9955
|
}
|
|
9860
9956
|
};
|
|
9861
|
-
registerCheck(
|
|
9957
|
+
registerCheck(check34);
|
|
9862
9958
|
|
|
9863
9959
|
// src/checks/asset/approved-fonts.ts
|
|
9864
|
-
var
|
|
9960
|
+
var check35 = {
|
|
9865
9961
|
id: "asset.font.approved-files",
|
|
9866
9962
|
name: "Approved Font Files",
|
|
9867
9963
|
description: "Checks font files against the approved file patterns",
|
|
@@ -9900,7 +9996,7 @@ var check34 = {
|
|
|
9900
9996
|
for (const font of allFonts) {
|
|
9901
9997
|
if (!approvedFonts.has(font)) {
|
|
9902
9998
|
results.push({
|
|
9903
|
-
checkId:
|
|
9999
|
+
checkId: check35.id,
|
|
9904
10000
|
severity: "warning",
|
|
9905
10001
|
message: "Font file not in approved list",
|
|
9906
10002
|
file: font
|
|
@@ -9910,7 +10006,7 @@ var check34 = {
|
|
|
9910
10006
|
return results;
|
|
9911
10007
|
}
|
|
9912
10008
|
};
|
|
9913
|
-
registerCheck(
|
|
10009
|
+
registerCheck(check35);
|
|
9914
10010
|
|
|
9915
10011
|
// src/checks/asset/font-license.ts
|
|
9916
10012
|
import { existsSync as existsSync11 } from "node:fs";
|
|
@@ -9925,7 +10021,7 @@ var LICENSE_NAMES = [
|
|
|
9925
10021
|
"OFL-1.1.txt",
|
|
9926
10022
|
"COPYING"
|
|
9927
10023
|
];
|
|
9928
|
-
var
|
|
10024
|
+
var check36 = {
|
|
9929
10025
|
id: "asset.font.license",
|
|
9930
10026
|
name: "Font License",
|
|
9931
10027
|
description: "Checks for license files in font directories",
|
|
@@ -9952,7 +10048,7 @@ var check35 = {
|
|
|
9952
10048
|
const hasLicense = LICENSE_NAMES.some((name) => existsSync11(resolve7(fontDir, name)));
|
|
9953
10049
|
if (!hasLicense) {
|
|
9954
10050
|
results.push({
|
|
9955
|
-
checkId:
|
|
10051
|
+
checkId: check36.id,
|
|
9956
10052
|
severity: "warning",
|
|
9957
10053
|
message: `No font license file found in directory`,
|
|
9958
10054
|
file: fontDir
|
|
@@ -9962,10 +10058,10 @@ var check35 = {
|
|
|
9962
10058
|
return results;
|
|
9963
10059
|
}
|
|
9964
10060
|
};
|
|
9965
|
-
registerCheck(
|
|
10061
|
+
registerCheck(check36);
|
|
9966
10062
|
|
|
9967
10063
|
// src/checks/heuristic/text-density.ts
|
|
9968
|
-
var
|
|
10064
|
+
var check37 = {
|
|
9969
10065
|
id: "heuristic.whitespace.text-density",
|
|
9970
10066
|
name: "Text Density",
|
|
9971
10067
|
description: "Checks characters-per-page ratio",
|
|
@@ -9994,7 +10090,7 @@ var check36 = {
|
|
|
9994
10090
|
});
|
|
9995
10091
|
if (lowPages.length > 0) {
|
|
9996
10092
|
results.push({
|
|
9997
|
-
checkId:
|
|
10093
|
+
checkId: check37.id,
|
|
9998
10094
|
severity: "info",
|
|
9999
10095
|
message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
|
|
10000
10096
|
file: ctx.pdfPath
|
|
@@ -10002,7 +10098,7 @@ var check36 = {
|
|
|
10002
10098
|
}
|
|
10003
10099
|
if (highPages.length > 0) {
|
|
10004
10100
|
results.push({
|
|
10005
|
-
checkId:
|
|
10101
|
+
checkId: check37.id,
|
|
10006
10102
|
severity: "info",
|
|
10007
10103
|
message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
|
|
10008
10104
|
file: ctx.pdfPath
|
|
@@ -10011,11 +10107,11 @@ var check36 = {
|
|
|
10011
10107
|
return results;
|
|
10012
10108
|
}
|
|
10013
10109
|
};
|
|
10014
|
-
registerCheck(
|
|
10110
|
+
registerCheck(check37);
|
|
10015
10111
|
|
|
10016
10112
|
// src/checks/heuristic/section-density.ts
|
|
10017
10113
|
import { readFile as readFile26 } from "node:fs/promises";
|
|
10018
|
-
var
|
|
10114
|
+
var check38 = {
|
|
10019
10115
|
id: "heuristic.chunking.section-density",
|
|
10020
10116
|
name: "Section Density",
|
|
10021
10117
|
description: "Checks heading/paragraph/callout density from source Markdown",
|
|
@@ -10041,7 +10137,7 @@ var check37 = {
|
|
|
10041
10137
|
const line = lines[i];
|
|
10042
10138
|
if (/^#{1,6}\s/.test(line)) {
|
|
10043
10139
|
if (paragraphCount > maxParas) {
|
|
10044
|
-
results.push(finding(
|
|
10140
|
+
results.push(finding(check38.id, {
|
|
10045
10141
|
severity: "info",
|
|
10046
10142
|
message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
|
|
10047
10143
|
file,
|
|
@@ -10061,7 +10157,7 @@ var check37 = {
|
|
|
10061
10157
|
}
|
|
10062
10158
|
}
|
|
10063
10159
|
if (paragraphCount > maxParas) {
|
|
10064
|
-
results.push(finding(
|
|
10160
|
+
results.push(finding(check38.id, {
|
|
10065
10161
|
severity: "info",
|
|
10066
10162
|
message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
|
|
10067
10163
|
file,
|
|
@@ -10069,7 +10165,7 @@ var check37 = {
|
|
|
10069
10165
|
}));
|
|
10070
10166
|
}
|
|
10071
10167
|
} catch {
|
|
10072
|
-
results.push(inspectionFailed(
|
|
10168
|
+
results.push(inspectionFailed(check38.id, `Could not read source file: ${file}`, {
|
|
10073
10169
|
file
|
|
10074
10170
|
}));
|
|
10075
10171
|
}
|
|
@@ -10077,10 +10173,10 @@ var check37 = {
|
|
|
10077
10173
|
return results;
|
|
10078
10174
|
}
|
|
10079
10175
|
};
|
|
10080
|
-
registerCheck(
|
|
10176
|
+
registerCheck(check38);
|
|
10081
10177
|
|
|
10082
10178
|
// src/checks/heuristic/layer-count.ts
|
|
10083
|
-
var
|
|
10179
|
+
var check39 = {
|
|
10084
10180
|
id: "heuristic.decoration.layer-count",
|
|
10085
10181
|
name: "Layer Count",
|
|
10086
10182
|
description: "Counts image objects per page",
|
|
@@ -10105,7 +10201,7 @@ var check38 = {
|
|
|
10105
10201
|
heavyPages.sort((a, b) => a - b);
|
|
10106
10202
|
return [
|
|
10107
10203
|
{
|
|
10108
|
-
checkId:
|
|
10204
|
+
checkId: check39.id,
|
|
10109
10205
|
severity: "info",
|
|
10110
10206
|
message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
|
|
10111
10207
|
file: ctx.pdfPath
|
|
@@ -10115,10 +10211,10 @@ var check38 = {
|
|
|
10115
10211
|
return [];
|
|
10116
10212
|
}
|
|
10117
10213
|
};
|
|
10118
|
-
registerCheck(
|
|
10214
|
+
registerCheck(check39);
|
|
10119
10215
|
|
|
10120
10216
|
// src/checks/heuristic/placement-variance.ts
|
|
10121
|
-
var
|
|
10217
|
+
var check40 = {
|
|
10122
10218
|
id: "heuristic.layout.placement-variance",
|
|
10123
10219
|
name: "Placement Variance",
|
|
10124
10220
|
description: "Analyzes text baseline coordinates for layout consistency",
|
|
@@ -10136,7 +10232,7 @@ var check39 = {
|
|
|
10136
10232
|
const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
|
|
10137
10233
|
return [
|
|
10138
10234
|
{
|
|
10139
|
-
checkId:
|
|
10235
|
+
checkId: check40.id,
|
|
10140
10236
|
severity: "info",
|
|
10141
10237
|
message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
|
|
10142
10238
|
file: ctx.pdfPath
|
|
@@ -10144,7 +10240,7 @@ var check39 = {
|
|
|
10144
10240
|
];
|
|
10145
10241
|
}
|
|
10146
10242
|
};
|
|
10147
|
-
registerCheck(
|
|
10243
|
+
registerCheck(check40);
|
|
10148
10244
|
|
|
10149
10245
|
// src/checks/runner.ts
|
|
10150
10246
|
function getCheckSeverityOverride(checkId, config) {
|
|
@@ -10174,16 +10270,16 @@ async function runChecks(ctx, opts = {}) {
|
|
|
10174
10270
|
}
|
|
10175
10271
|
const release = retainPdfCache();
|
|
10176
10272
|
try {
|
|
10177
|
-
for (const
|
|
10273
|
+
for (const check41 of checks2) {
|
|
10178
10274
|
try {
|
|
10179
|
-
let results = await
|
|
10180
|
-
const severityOverride = getCheckSeverityOverride(
|
|
10275
|
+
let results = await check41.run(ctx);
|
|
10276
|
+
const severityOverride = getCheckSeverityOverride(check41.id, ctx.config);
|
|
10181
10277
|
if (severityOverride) {
|
|
10182
10278
|
for (const r of results) {
|
|
10183
10279
|
r.severity = severityOverride;
|
|
10184
10280
|
}
|
|
10185
10281
|
}
|
|
10186
|
-
const lintLevel = ruleLevel(ctx.config.lint.rules,
|
|
10282
|
+
const lintLevel = ruleLevel(ctx.config.lint.rules, check41.id);
|
|
10187
10283
|
if (lintLevel === "off")
|
|
10188
10284
|
results = [];
|
|
10189
10285
|
else if (lintLevel) {
|
|
@@ -10191,15 +10287,15 @@ async function runChecks(ctx, opts = {}) {
|
|
|
10191
10287
|
r.severity = lintLevel === "warn" ? "warning" : "error";
|
|
10192
10288
|
}
|
|
10193
10289
|
if (results.length === 0) {
|
|
10194
|
-
passed.push(
|
|
10290
|
+
passed.push(check41.id);
|
|
10195
10291
|
} else {
|
|
10196
10292
|
allResults.push(...results);
|
|
10197
10293
|
}
|
|
10198
10294
|
} catch (err) {
|
|
10199
10295
|
allResults.push({
|
|
10200
|
-
checkId:
|
|
10296
|
+
checkId: check41.id,
|
|
10201
10297
|
severity: "error",
|
|
10202
|
-
message: `Check "${
|
|
10298
|
+
message: `Check "${check41.id}" threw: ${err instanceof Error ? err.message : String(err)}`
|
|
10203
10299
|
});
|
|
10204
10300
|
}
|
|
10205
10301
|
}
|
|
@@ -10239,15 +10335,15 @@ function emptyReport() {
|
|
|
10239
10335
|
async function checkToolAvailability(config, opts = {}) {
|
|
10240
10336
|
const { checks: checks2 } = selectChecks(opts, config);
|
|
10241
10337
|
const toolToChecks = new Map;
|
|
10242
|
-
for (const
|
|
10243
|
-
if (!
|
|
10338
|
+
for (const check41 of checks2) {
|
|
10339
|
+
if (!check41.requiredTools?.length)
|
|
10244
10340
|
continue;
|
|
10245
|
-
for (const tool of
|
|
10341
|
+
for (const tool of check41.requiredTools) {
|
|
10246
10342
|
const existing = toolToChecks.get(tool);
|
|
10247
10343
|
if (existing) {
|
|
10248
|
-
existing.push(
|
|
10344
|
+
existing.push(check41.id);
|
|
10249
10345
|
} else {
|
|
10250
|
-
toolToChecks.set(tool, [
|
|
10346
|
+
toolToChecks.set(tool, [check41.id]);
|
|
10251
10347
|
}
|
|
10252
10348
|
}
|
|
10253
10349
|
}
|
|
@@ -10263,11 +10359,11 @@ async function checkToolAvailability(config, opts = {}) {
|
|
|
10263
10359
|
const missing = results.filter((r) => !r.found).map((r) => r.tool);
|
|
10264
10360
|
const missingSet = new Set(missing);
|
|
10265
10361
|
const skippedChecks = [];
|
|
10266
|
-
for (const
|
|
10267
|
-
if (!
|
|
10362
|
+
for (const check41 of checks2) {
|
|
10363
|
+
if (!check41.requiredTools?.length)
|
|
10268
10364
|
continue;
|
|
10269
|
-
if (
|
|
10270
|
-
skippedChecks.push(
|
|
10365
|
+
if (check41.requiredTools.some((t) => missingSet.has(t))) {
|
|
10366
|
+
skippedChecks.push(check41.id);
|
|
10271
10367
|
}
|
|
10272
10368
|
}
|
|
10273
10369
|
return { available, missing, skippedChecks, toolToChecks };
|
|
@@ -10540,7 +10636,7 @@ async function executeValidation(args) {
|
|
|
10540
10636
|
};
|
|
10541
10637
|
if (args.fix) {
|
|
10542
10638
|
const selected = selectChecks(runnerOptions, config).checks;
|
|
10543
|
-
if (selected.some((
|
|
10639
|
+
if (selected.some((check41) => check41.id === MARKDOWNLINT_CHECK_ID)) {
|
|
10544
10640
|
await applyMarkdownlintFixes(context);
|
|
10545
10641
|
} else {
|
|
10546
10642
|
log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
|
|
@@ -10913,7 +11009,7 @@ class PdfOutput {
|
|
|
10913
11009
|
const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
|
|
10914
11010
|
await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
|
|
10915
11011
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
10916
|
-
const { buildNativePdf } = await import("./engine-
|
|
11012
|
+
const { buildNativePdf } = await import("./engine-yax79k9m.js");
|
|
10917
11013
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
10918
11014
|
title: config.title,
|
|
10919
11015
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
@@ -11142,8 +11238,16 @@ async function renderPreviewBook(inputPath, config, opts) {
|
|
|
11142
11238
|
});
|
|
11143
11239
|
}
|
|
11144
11240
|
function injectPreviewScripts(html, page) {
|
|
11145
|
-
const area = page ? JSON.stringify({
|
|
11146
|
-
|
|
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, "&").replace(/'/g, "'")}'>
|
|
11147
11251
|
` : "";
|
|
11148
11252
|
const scripts = meta + ` <script src="/engine/gutterpress-viewer.js"></script>
|
|
11149
11253
|
` + `<script src="/preview/scripts/preview-interface.js"></script>
|
|
@@ -11179,7 +11283,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
|
|
|
11179
11283
|
for (const ref of nextImageRefs)
|
|
11180
11284
|
imageRefs.add(ref);
|
|
11181
11285
|
}
|
|
11182
|
-
await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, config.page), "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");
|
|
11183
11287
|
}
|
|
11184
11288
|
function isDotPathUnderRoot(candidatePath, root) {
|
|
11185
11289
|
const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
@@ -12428,8 +12532,8 @@ function pageSizeForChoice(choice) {
|
|
|
12428
12532
|
}
|
|
12429
12533
|
var round3 = (n) => Math.round(n * 1000) / 1000;
|
|
12430
12534
|
function cssLength(pt) {
|
|
12431
|
-
const
|
|
12432
|
-
return Math.abs(
|
|
12535
|
+
const inches2 = round3(pt / 72);
|
|
12536
|
+
return Math.abs(inches2 * 72 - pt) < 0.000001 ? `${inches2}in` : `${round3(pt)}pt`;
|
|
12433
12537
|
}
|
|
12434
12538
|
function formatPageSize(page) {
|
|
12435
12539
|
return `${cssLength(page.width)} ${cssLength(page.height)}`;
|