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