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