gutterpress 0.11.17-alpha.1 → 0.11.17-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.d.ts +4 -0
- package/dist/api/index.js +10 -2
- package/dist/{build-jfjzam95.js → build-394c1ee1.js} +4 -4
- package/dist/checks/pdf/index.d.ts +1 -0
- package/dist/checks/pdf/signature.d.ts +3 -0
- package/dist/{cli-0754wskb.js → cli-3p1e95zc.js} +1 -1
- package/dist/{cli-ew3gc4nt.js → cli-9nba5ve6.js} +562 -211
- package/dist/{cli-zce1zcqc.js → cli-d57bsfpb.js} +1 -1
- package/dist/{cli-xx5bryh6.js → cli-jyy9pqf3.js} +16 -11
- package/dist/cli.js +12 -12
- package/dist/{doctor-h01f71ws.js → doctor-c459qv5j.js} +2 -2
- package/dist/engine/compiler/build.d.ts +3 -2
- package/dist/engine/compiler/postprocess.d.ts +6 -4
- package/dist/engine/shared/synthesis.d.ts +29 -5
- package/dist/{engine-aaam2nz3.js → engine-3xqgm82d.js} +2 -2
- package/dist/{engine-rhmwgk9k.js → engine-zdb41ay1.js} +1 -1
- package/dist/{ext-ja4hy1c2.js → ext-ha7gg8h9.js} +4 -4
- package/dist/{gutterpress-viewer-5a6h4evk.js → gutterpress-viewer-1n793gwd.js} +10 -4
- package/dist/{index-r9t4acx5.js → index-bscfmg5r.js} +1714 -1054
- package/dist/{index-3nk5mnn6.js → index-hrx0gfkj.js} +23 -684
- package/dist/index-q1xqxrh9.js +949 -0
- package/dist/index.js +11 -3
- package/dist/lib/black-text-gray.d.ts +38 -8
- package/dist/lib/build-fingerprint.d.ts +2 -0
- package/dist/lib/engine.d.ts +2 -1
- package/dist/lib/lint-rules.d.ts +9 -0
- package/dist/lib/markdown/assemble.d.ts +3 -5
- package/dist/lib/markdown/index.d.ts +3 -8
- package/dist/lib/markdown/renderer.d.ts +9 -1
- package/dist/lib/page-size.d.ts +66 -0
- package/dist/lib/pdf-inspect.d.ts +6 -0
- package/dist/lib/printsafe.d.ts +8 -1
- package/dist/lib/project-scaffold.d.ts +3 -9
- package/dist/lib/remote-auth/github-repos.d.ts +7 -0
- package/dist/lib/remote-auth/online-backup.d.ts +51 -0
- package/dist/lib/snippets.d.ts +3 -0
- package/dist/{manifest.schema-40sfq49z.json → manifest.schema-gh0jjve9.json} +14 -2
- package/dist/{new-yzswp0ge.js → new-kky3z7kv.js} +4 -4
- package/dist/{plugin.js-m4a7m3t4.tpl → plugin.js-cswq7wvd.tpl} +4 -1
- package/dist/{plugin.test.js-6n8ct5e3.tpl → plugin.test.js-xgxrd8hf.tpl} +4 -1
- package/dist/{preflight-0s1vw6y1.js → preflight-t8v6szq6.js} +4 -4
- package/dist/preview/file-watcher.d.ts +2 -4
- package/dist/{preview-tam7azsr.js → preview-cxab4y5x.js} +4 -4
- package/dist/{preview-interface-5wnzrvhf.js → preview-interface-c44qk17f.js} +7 -2
- package/dist/{publish-ra8kyf0z.js → publish-s2mtenb5.js} +4 -4
- package/dist/render.js +30 -5
- package/dist/schema/manifest.types.d.ts +17 -0
- package/dist/{validate-n9vae7p8.js → validate-xaajdw4x.js} +4 -4
- package/package.json +1 -1
- package/dist/index-dmm31ys3.js +0 -267
|
@@ -13,17 +13,18 @@ import {
|
|
|
13
13
|
stampCreator,
|
|
14
14
|
stripAnnotations,
|
|
15
15
|
warn
|
|
16
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-3p1e95zc.js";
|
|
17
17
|
import {
|
|
18
18
|
DEBOUNCE,
|
|
19
19
|
UsageError,
|
|
20
20
|
resolvePort
|
|
21
|
-
} from "./cli-
|
|
21
|
+
} from "./cli-d57bsfpb.js";
|
|
22
22
|
import {
|
|
23
23
|
MARGIN_BOX_IGNORED_PROPERTIES,
|
|
24
24
|
getAssetPath,
|
|
25
|
-
getAssetsDir
|
|
26
|
-
|
|
25
|
+
getAssetsDir,
|
|
26
|
+
lastPageMode
|
|
27
|
+
} from "./cli-jyy9pqf3.js";
|
|
27
28
|
import {
|
|
28
29
|
BuildError,
|
|
29
30
|
EXIT_CODES
|
|
@@ -106,7 +107,7 @@ var DTRPG_PRESET = {
|
|
|
106
107
|
source: {
|
|
107
108
|
files: null
|
|
108
109
|
},
|
|
109
|
-
print: { signature: 4, reserveLastPage: true },
|
|
110
|
+
print: { signature: 4, reserveLastPage: true, lastPage: undefined },
|
|
110
111
|
pdfx: {
|
|
111
112
|
flavor: "x1a",
|
|
112
113
|
icc: "profiles/CGATS21_CRPC1.icc",
|
|
@@ -124,7 +125,8 @@ var DTRPG_PRESET = {
|
|
|
124
125
|
},
|
|
125
126
|
lint: {
|
|
126
127
|
enabled: true,
|
|
127
|
-
configPath: null
|
|
128
|
+
configPath: null,
|
|
129
|
+
rules: {}
|
|
128
130
|
},
|
|
129
131
|
validate: {
|
|
130
132
|
enabled: true,
|
|
@@ -181,7 +183,7 @@ var BOOK_PRESET = {
|
|
|
181
183
|
source: {
|
|
182
184
|
files: null
|
|
183
185
|
},
|
|
184
|
-
print: { signature: 1, reserveLastPage: false },
|
|
186
|
+
print: { signature: 1, reserveLastPage: false, lastPage: undefined },
|
|
185
187
|
pdfx: {
|
|
186
188
|
flavor: "x1a",
|
|
187
189
|
icc: "profiles/CGATS21_CRPC1.icc",
|
|
@@ -199,7 +201,8 @@ var BOOK_PRESET = {
|
|
|
199
201
|
},
|
|
200
202
|
lint: {
|
|
201
203
|
enabled: true,
|
|
202
|
-
configPath: null
|
|
204
|
+
configPath: null,
|
|
205
|
+
rules: {}
|
|
203
206
|
},
|
|
204
207
|
validate: {
|
|
205
208
|
enabled: true,
|
|
@@ -566,6 +569,10 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
|
566
569
|
if (legacyPlugins !== undefined) {
|
|
567
570
|
throw new UsageError(pluginsRemovedMessage(legacyPlugins));
|
|
568
571
|
}
|
|
572
|
+
const lastPage = c.print?.lastPage ?? m.print?.lastPage;
|
|
573
|
+
if (lastPage !== undefined && lastPage !== "blank" && lastPage !== "short-by-one") {
|
|
574
|
+
throw new UsageError(`Manifest field \`print.lastPage\` must be "blank" or "short-by-one", got ${JSON.stringify(lastPage)}.`);
|
|
575
|
+
}
|
|
569
576
|
const rawExtensions = c.extensions ?? m.extensions ?? [];
|
|
570
577
|
if (!Array.isArray(rawExtensions)) {
|
|
571
578
|
throw new UsageError("Manifest field `extensions` must be a list of specifiers.");
|
|
@@ -582,7 +589,10 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
|
582
589
|
print: mergeShape(c.print, m.print, preset.print),
|
|
583
590
|
page: preset.page ? mergeShape(c.page, m.page, preset.page) : resolveCustomPage(c, m),
|
|
584
591
|
ink: mergeShape(c.ink, m.ink, preset.ink),
|
|
585
|
-
lint:
|
|
592
|
+
lint: {
|
|
593
|
+
...mergeShape(c.lint, m.lint, preset.lint),
|
|
594
|
+
rules: { ...preset.lint.rules, ...m.lint?.rules, ...c.lint?.rules }
|
|
595
|
+
},
|
|
586
596
|
validate: {
|
|
587
597
|
enabled: c.validate?.enabled ?? m.validate?.enabled ?? preset.validate.enabled,
|
|
588
598
|
checks: { ...preset.validate.checks, ...m.validate?.checks, ...c.validate?.checks },
|
|
@@ -1078,7 +1088,7 @@ function resolveContainerShape(name, pluginName, decl) {
|
|
|
1078
1088
|
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`section\` that is not a boolean.`);
|
|
1079
1089
|
}
|
|
1080
1090
|
if (decl.section) {
|
|
1081
|
-
for (const field of ["tag", "label", "autoCloseAt"]) {
|
|
1091
|
+
for (const field of ["tag", "label", "autoCloseAt", "selfClosing"]) {
|
|
1082
1092
|
if (decl[field] !== undefined) {
|
|
1083
1093
|
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" sets \`section: true\` and \`${field}\` — a section ` + `marker is a core @section, so \`${field}\` does not apply. Remove one of them.`);
|
|
1084
1094
|
}
|
|
@@ -1135,11 +1145,26 @@ function resolveContainerShape(name, pluginName, decl) {
|
|
|
1135
1145
|
}
|
|
1136
1146
|
autoCloseAtEof = decl.autoCloseAt.includes("eof");
|
|
1137
1147
|
}
|
|
1148
|
+
if (decl.selfClosing !== undefined && typeof decl.selfClosing !== "boolean") {
|
|
1149
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`selfClosing\` that is not a boolean.`);
|
|
1150
|
+
}
|
|
1151
|
+
if (decl.selfClosing && decl.autoCloseAt !== undefined) {
|
|
1152
|
+
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" sets \`selfClosing: true\` and \`autoCloseAt\` — a ` + `self-closing marker has no open container to auto-close. Remove one of them.`);
|
|
1153
|
+
}
|
|
1138
1154
|
if (decl.validate !== undefined && typeof decl.validate !== "function") {
|
|
1139
1155
|
throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`validate\` that is not a function.`);
|
|
1140
1156
|
}
|
|
1141
1157
|
const check = decl.validate ? { validate: decl.validate, validateOwner: pluginName } : {};
|
|
1142
|
-
return {
|
|
1158
|
+
return {
|
|
1159
|
+
tag,
|
|
1160
|
+
classBase,
|
|
1161
|
+
variants,
|
|
1162
|
+
label,
|
|
1163
|
+
autoCloseAtEof,
|
|
1164
|
+
...decl.section ? { section: true } : {},
|
|
1165
|
+
...decl.selfClosing ? { selfClosing: true } : {},
|
|
1166
|
+
...check
|
|
1167
|
+
};
|
|
1143
1168
|
}
|
|
1144
1169
|
function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
|
|
1145
1170
|
const pluginName = originOf.get(name);
|
|
@@ -1398,7 +1423,8 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1398
1423
|
addClasses(t, baseClass, meta.attrs && meta.attrs.class ? meta.attrs.class : "");
|
|
1399
1424
|
attachDataAttrs(t, decl.baseKind, variant, meta.attrs || {});
|
|
1400
1425
|
out.push(t);
|
|
1401
|
-
|
|
1426
|
+
if (!decl.selfClosing)
|
|
1427
|
+
declaredFrames.push({ kind: decl.baseKind, tag: decl.tag, decl, line: meta.__line });
|
|
1402
1428
|
if (decl.label) {
|
|
1403
1429
|
const value = meta.attrs && meta.attrs[decl.label.attr];
|
|
1404
1430
|
if (value) {
|
|
@@ -1409,6 +1435,8 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1409
1435
|
t.meta.labelled = true;
|
|
1410
1436
|
}
|
|
1411
1437
|
}
|
|
1438
|
+
if (decl.selfClosing)
|
|
1439
|
+
out.push(new state.Token("layout_component_close", decl.tag, -1));
|
|
1412
1440
|
}
|
|
1413
1441
|
const stack = {
|
|
1414
1442
|
frames: [],
|
|
@@ -1687,7 +1715,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1687
1715
|
continue;
|
|
1688
1716
|
}
|
|
1689
1717
|
if (!closeDeclaredFrame(closeDecl.baseKind)) {
|
|
1690
|
-
warn2(state.env, line, "declared_marker_close_without_open", `@${kind} used without an open @${closeDecl.baseKind}; ignoring marker.`, meta);
|
|
1718
|
+
warn2(state.env, line, "declared_marker_close_without_open", closeDecl.selfClosing ? `@${kind} is not a marker: @${closeDecl.baseKind} is self-closing and has nothing to close; ignoring marker.` : `@${kind} used without an open @${closeDecl.baseKind}; ignoring marker.`, meta);
|
|
1691
1719
|
}
|
|
1692
1720
|
continue;
|
|
1693
1721
|
}
|
|
@@ -4987,6 +5015,32 @@ function getImagePixelSize(page, name, timeoutMs = 2000) {
|
|
|
4987
5015
|
}
|
|
4988
5016
|
});
|
|
4989
5017
|
}
|
|
5018
|
+
var PAINT_OPS = new Set([
|
|
5019
|
+
"stroke",
|
|
5020
|
+
"closeStroke",
|
|
5021
|
+
"fill",
|
|
5022
|
+
"eoFill",
|
|
5023
|
+
"fillStroke",
|
|
5024
|
+
"eoFillStroke",
|
|
5025
|
+
"closeFillStroke",
|
|
5026
|
+
"closeEOFillStroke",
|
|
5027
|
+
"shadingFill",
|
|
5028
|
+
"paintImageXObject",
|
|
5029
|
+
"paintInlineImageXObject",
|
|
5030
|
+
"paintImageXObjectRepeat",
|
|
5031
|
+
"paintImageMaskXObject",
|
|
5032
|
+
"paintImageMaskXObjectGroup",
|
|
5033
|
+
"paintImageMaskXObjectRepeat",
|
|
5034
|
+
"paintSolidColorImageMask",
|
|
5035
|
+
"showText",
|
|
5036
|
+
"showSpacedText",
|
|
5037
|
+
"nextLineShowText",
|
|
5038
|
+
"nextLineSetSpacingShowText"
|
|
5039
|
+
].map((name) => OPS[name]).filter((op) => op !== undefined));
|
|
5040
|
+
async function pageHasMarks(doc, pageNum) {
|
|
5041
|
+
const ops = await (await doc.getPage(pageNum)).getOperatorList();
|
|
5042
|
+
return ops.fnArray.some((fn, i) => fn === OPS.constructPath ? PAINT_OPS.has(ops.argsArray[i][0]) : PAINT_OPS.has(fn));
|
|
5043
|
+
}
|
|
4990
5044
|
async function getImageResolutions(doc) {
|
|
4991
5045
|
const { imagesByPage } = await getOpPass(doc);
|
|
4992
5046
|
const out = [];
|
|
@@ -5490,8 +5544,62 @@ var check7 = {
|
|
|
5490
5544
|
};
|
|
5491
5545
|
registerCheck(check7);
|
|
5492
5546
|
|
|
5493
|
-
// src/checks/pdf/
|
|
5547
|
+
// src/checks/pdf/signature.ts
|
|
5494
5548
|
var check8 = {
|
|
5549
|
+
id: "pdf.print.signature",
|
|
5550
|
+
name: "Signature",
|
|
5551
|
+
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)",
|
|
5552
|
+
category: "pdf",
|
|
5553
|
+
phase: "post-build",
|
|
5554
|
+
enabledWhen: (config) => config.print.signature > 1,
|
|
5555
|
+
async run(ctx) {
|
|
5556
|
+
if (!ctx.pdfPath)
|
|
5557
|
+
return [];
|
|
5558
|
+
const doc = await loadPdf(ctx.pdfPath);
|
|
5559
|
+
if (!doc) {
|
|
5560
|
+
return [inspectionFailed(check8.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
|
|
5561
|
+
}
|
|
5562
|
+
const size = ctx.config.print.signature;
|
|
5563
|
+
const pages = doc.numPages;
|
|
5564
|
+
if ((pages + 1) % size === 0)
|
|
5565
|
+
return [];
|
|
5566
|
+
const data = { pages, signature: size };
|
|
5567
|
+
if (pages % size !== 0) {
|
|
5568
|
+
return [
|
|
5569
|
+
finding(check8.id, {
|
|
5570
|
+
severity: "error",
|
|
5571
|
+
message: `Page count ${pages} does not fit a signature of ${size}: it must be a multiple of ${size}, or one short of one.`,
|
|
5572
|
+
file: ctx.pdfPath,
|
|
5573
|
+
detail: "Set `print.signature` in the manifest and rebuild; the build pads the interior for you.",
|
|
5574
|
+
code: "signature-page-count",
|
|
5575
|
+
data
|
|
5576
|
+
})
|
|
5577
|
+
];
|
|
5578
|
+
}
|
|
5579
|
+
if (!lastPageMode(ctx.config.print))
|
|
5580
|
+
return [];
|
|
5581
|
+
try {
|
|
5582
|
+
if (!await pageHasMarks(doc, pages))
|
|
5583
|
+
return [];
|
|
5584
|
+
} catch {
|
|
5585
|
+
return [inspectionFailed(check8.id, "Could not read the last page.", { file: ctx.pdfPath })];
|
|
5586
|
+
}
|
|
5587
|
+
return [
|
|
5588
|
+
finding(check8.id, {
|
|
5589
|
+
severity: "error",
|
|
5590
|
+
message: `The last page (${pages}) has content, but the printer needs it blank for its own information.`,
|
|
5591
|
+
file: ctx.pdfPath,
|
|
5592
|
+
detail: "The build adds that page for you (`print.lastPage: blank`, or `short-by-one` to stop a page short of a multiple " + `of ${size} and let the printer add it). Check that the interior was built with this manifest, and that nothing in it ` + "forces content onto the final page.",
|
|
5593
|
+
code: "signature-last-page",
|
|
5594
|
+
data
|
|
5595
|
+
})
|
|
5596
|
+
];
|
|
5597
|
+
}
|
|
5598
|
+
};
|
|
5599
|
+
registerCheck(check8);
|
|
5600
|
+
|
|
5601
|
+
// src/checks/pdf/bookmarks.ts
|
|
5602
|
+
var check9 = {
|
|
5495
5603
|
id: "pdf.nav.bookmarks",
|
|
5496
5604
|
name: "Bookmarks",
|
|
5497
5605
|
description: "Checks for PDF outline (bookmarks) tree",
|
|
@@ -5505,14 +5613,14 @@ var check8 = {
|
|
|
5505
5613
|
const doc = await loadPdf(ctx.pdfPath);
|
|
5506
5614
|
if (!doc) {
|
|
5507
5615
|
return [
|
|
5508
|
-
inspectionFailed(
|
|
5616
|
+
inspectionFailed(check9.id, "Could not inspect PDF for bookmarks.", {
|
|
5509
5617
|
file: ctx.pdfPath
|
|
5510
5618
|
})
|
|
5511
5619
|
];
|
|
5512
5620
|
}
|
|
5513
5621
|
if (await getOutlineCount(doc) === 0) {
|
|
5514
5622
|
return [
|
|
5515
|
-
finding(
|
|
5623
|
+
finding(check9.id, {
|
|
5516
5624
|
severity: "warning",
|
|
5517
5625
|
message: "PDF does not contain bookmarks (outline tree).",
|
|
5518
5626
|
file: ctx.pdfPath
|
|
@@ -5522,10 +5630,10 @@ var check8 = {
|
|
|
5522
5630
|
return [];
|
|
5523
5631
|
}
|
|
5524
5632
|
};
|
|
5525
|
-
registerCheck(
|
|
5633
|
+
registerCheck(check9);
|
|
5526
5634
|
|
|
5527
5635
|
// src/checks/pdf/toc-links.ts
|
|
5528
|
-
var
|
|
5636
|
+
var check10 = {
|
|
5529
5637
|
id: "pdf.nav.toc-links",
|
|
5530
5638
|
name: "TOC Links",
|
|
5531
5639
|
description: "Detects link annotations on TOC pages",
|
|
@@ -5540,7 +5648,7 @@ var check9 = {
|
|
|
5540
5648
|
if (!doc) {
|
|
5541
5649
|
return [
|
|
5542
5650
|
{
|
|
5543
|
-
checkId:
|
|
5651
|
+
checkId: check10.id,
|
|
5544
5652
|
severity: "warning",
|
|
5545
5653
|
message: "Could not inspect PDF for TOC links.",
|
|
5546
5654
|
file: ctx.pdfPath
|
|
@@ -5551,7 +5659,7 @@ var check9 = {
|
|
|
5551
5659
|
if (!hasLinks) {
|
|
5552
5660
|
return [
|
|
5553
5661
|
{
|
|
5554
|
-
checkId:
|
|
5662
|
+
checkId: check10.id,
|
|
5555
5663
|
severity: "warning",
|
|
5556
5664
|
message: "No link annotations found in PDF (expected for TOC).",
|
|
5557
5665
|
file: ctx.pdfPath
|
|
@@ -5561,10 +5669,10 @@ var check9 = {
|
|
|
5561
5669
|
return [];
|
|
5562
5670
|
}
|
|
5563
5671
|
};
|
|
5564
|
-
registerCheck(
|
|
5672
|
+
registerCheck(check10);
|
|
5565
5673
|
|
|
5566
5674
|
// src/checks/pdf/cross-refs.ts
|
|
5567
|
-
var
|
|
5675
|
+
var check11 = {
|
|
5568
5676
|
id: "pdf.nav.cross-refs",
|
|
5569
5677
|
name: "Cross References",
|
|
5570
5678
|
description: "Counts and verifies internal link annotations",
|
|
@@ -5577,7 +5685,7 @@ var check10 = {
|
|
|
5577
5685
|
if (!doc) {
|
|
5578
5686
|
return [
|
|
5579
5687
|
{
|
|
5580
|
-
checkId:
|
|
5688
|
+
checkId: check11.id,
|
|
5581
5689
|
severity: "info",
|
|
5582
5690
|
message: "Could not inspect PDF for cross-references.",
|
|
5583
5691
|
file: ctx.pdfPath
|
|
@@ -5587,7 +5695,7 @@ var check10 = {
|
|
|
5587
5695
|
const linkCount = await countLinkAnnotations(doc);
|
|
5588
5696
|
return [
|
|
5589
5697
|
{
|
|
5590
|
-
checkId:
|
|
5698
|
+
checkId: check11.id,
|
|
5591
5699
|
severity: "info",
|
|
5592
5700
|
message: `PDF contains ${linkCount} internal link annotation${linkCount !== 1 ? "s" : ""}.`,
|
|
5593
5701
|
file: ctx.pdfPath
|
|
@@ -5595,10 +5703,10 @@ var check10 = {
|
|
|
5595
5703
|
];
|
|
5596
5704
|
}
|
|
5597
5705
|
};
|
|
5598
|
-
registerCheck(
|
|
5706
|
+
registerCheck(check11);
|
|
5599
5707
|
|
|
5600
5708
|
// src/checks/pdf/page-labels.ts
|
|
5601
|
-
var
|
|
5709
|
+
var check12 = {
|
|
5602
5710
|
id: "pdf.nav.page-labels",
|
|
5603
5711
|
name: "Page Labels",
|
|
5604
5712
|
description: "Verifies PDF page labels / folio numbering",
|
|
@@ -5613,7 +5721,7 @@ var check11 = {
|
|
|
5613
5721
|
const labels = await getPageLabels(doc);
|
|
5614
5722
|
return [
|
|
5615
5723
|
{
|
|
5616
|
-
checkId:
|
|
5724
|
+
checkId: check12.id,
|
|
5617
5725
|
severity: "info",
|
|
5618
5726
|
message: labels && labels.length > 0 ? "PDF contains page labels." : "PDF does not contain explicit page labels.",
|
|
5619
5727
|
file: ctx.pdfPath
|
|
@@ -5621,10 +5729,10 @@ var check11 = {
|
|
|
5621
5729
|
];
|
|
5622
5730
|
}
|
|
5623
5731
|
};
|
|
5624
|
-
registerCheck(
|
|
5732
|
+
registerCheck(check12);
|
|
5625
5733
|
|
|
5626
5734
|
// src/checks/pdf/image-resolution.ts
|
|
5627
|
-
var
|
|
5735
|
+
var check13 = {
|
|
5628
5736
|
id: "pdf.print.image-resolution",
|
|
5629
5737
|
name: "Image Resolution",
|
|
5630
5738
|
description: "Checks DPI of images embedded in the PDF",
|
|
@@ -5651,7 +5759,7 @@ var check12 = {
|
|
|
5651
5759
|
lowResPages.sort((a, b) => a - b);
|
|
5652
5760
|
return [
|
|
5653
5761
|
{
|
|
5654
|
-
checkId:
|
|
5762
|
+
checkId: check13.id,
|
|
5655
5763
|
severity: "warning",
|
|
5656
5764
|
message: `Low-resolution images (below ${minDpi} DPI) found on pages: ${lowResPages.join(", ")}`,
|
|
5657
5765
|
file: ctx.pdfPath
|
|
@@ -5661,10 +5769,10 @@ var check12 = {
|
|
|
5661
5769
|
return [];
|
|
5662
5770
|
}
|
|
5663
5771
|
};
|
|
5664
|
-
registerCheck(
|
|
5772
|
+
registerCheck(check13);
|
|
5665
5773
|
|
|
5666
5774
|
// src/checks/pdf/transparency.ts
|
|
5667
|
-
var
|
|
5775
|
+
var check14 = {
|
|
5668
5776
|
id: "pdf.print.transparency",
|
|
5669
5777
|
name: "Transparency",
|
|
5670
5778
|
description: "Checks for transparency markers (/Transparency, /SMask, /BM) in the PDF",
|
|
@@ -5686,7 +5794,7 @@ var check13 = {
|
|
|
5686
5794
|
if (found.length > 0) {
|
|
5687
5795
|
return [
|
|
5688
5796
|
{
|
|
5689
|
-
checkId:
|
|
5797
|
+
checkId: check14.id,
|
|
5690
5798
|
severity: "warning",
|
|
5691
5799
|
message: `Transparency detected in PDF: ${found.join(", ")}. This may cause issues with some printers.`,
|
|
5692
5800
|
file: ctx.pdfPath
|
|
@@ -5696,7 +5804,7 @@ var check13 = {
|
|
|
5696
5804
|
return [];
|
|
5697
5805
|
}
|
|
5698
5806
|
};
|
|
5699
|
-
registerCheck(
|
|
5807
|
+
registerCheck(check14);
|
|
5700
5808
|
|
|
5701
5809
|
// src/checks/pdf/bleed.ts
|
|
5702
5810
|
function findBox(bytes, box) {
|
|
@@ -5706,7 +5814,7 @@ function findBox(bytes, box) {
|
|
|
5706
5814
|
return null;
|
|
5707
5815
|
return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
|
|
5708
5816
|
}
|
|
5709
|
-
var
|
|
5817
|
+
var check15 = {
|
|
5710
5818
|
id: "pdf.print.bleed",
|
|
5711
5819
|
name: "Bleed Box",
|
|
5712
5820
|
description: "Compares MediaBox vs TrimBox/BleedBox to verify bleed area",
|
|
@@ -5720,7 +5828,7 @@ var check14 = {
|
|
|
5720
5828
|
const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
|
|
5721
5829
|
if (!bytes) {
|
|
5722
5830
|
return [
|
|
5723
|
-
inspectionFailed(
|
|
5831
|
+
inspectionFailed(check15.id, "Could not inspect PDF for bleed boxes.", {
|
|
5724
5832
|
file: ctx.pdfPath
|
|
5725
5833
|
})
|
|
5726
5834
|
];
|
|
@@ -5730,14 +5838,14 @@ var check14 = {
|
|
|
5730
5838
|
const bleed = findBox(bytes, "BleedBox");
|
|
5731
5839
|
if (!media) {
|
|
5732
5840
|
return [
|
|
5733
|
-
inspectionFailed(
|
|
5841
|
+
inspectionFailed(check15.id, "Could not parse MediaBox from PDF.", {
|
|
5734
5842
|
file: ctx.pdfPath
|
|
5735
5843
|
})
|
|
5736
5844
|
];
|
|
5737
5845
|
}
|
|
5738
5846
|
if (!trim && !bleed) {
|
|
5739
5847
|
return [
|
|
5740
|
-
finding(
|
|
5848
|
+
finding(check15.id, {
|
|
5741
5849
|
severity: "warning",
|
|
5742
5850
|
message: "No TrimBox or BleedBox found. Bleed area cannot be verified.",
|
|
5743
5851
|
file: ctx.pdfPath
|
|
@@ -5750,7 +5858,7 @@ var check14 = {
|
|
|
5750
5858
|
const bleedSize = ctx.config.validate.pdf.bleedSize;
|
|
5751
5859
|
if (mediaW - trimW < bleedSize * 2 * 0.9) {
|
|
5752
5860
|
return [
|
|
5753
|
-
finding(
|
|
5861
|
+
finding(check15.id, {
|
|
5754
5862
|
severity: "warning",
|
|
5755
5863
|
message: `Bleed area appears insufficient. Expected at least ${bleedSize}pt on each side.`,
|
|
5756
5864
|
file: ctx.pdfPath
|
|
@@ -5761,10 +5869,10 @@ var check14 = {
|
|
|
5761
5869
|
return [];
|
|
5762
5870
|
}
|
|
5763
5871
|
};
|
|
5764
|
-
registerCheck(
|
|
5872
|
+
registerCheck(check15);
|
|
5765
5873
|
|
|
5766
5874
|
// src/checks/pdf/pdfx-metadata.ts
|
|
5767
|
-
var
|
|
5875
|
+
var check16 = {
|
|
5768
5876
|
id: "pdf.print.pdfx-metadata",
|
|
5769
5877
|
name: "PDF/X Metadata",
|
|
5770
5878
|
description: "Verifies PDF/X DOCINFO metadata via qpdf JSON",
|
|
@@ -5783,22 +5891,22 @@ var check15 = {
|
|
|
5783
5891
|
const objects = parseQpdfObjectsJson(stdout);
|
|
5784
5892
|
if (!objects) {
|
|
5785
5893
|
return [
|
|
5786
|
-
inspectionFailed(
|
|
5894
|
+
inspectionFailed(check16.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
|
|
5787
5895
|
];
|
|
5788
5896
|
}
|
|
5789
|
-
return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(
|
|
5897
|
+
return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check16.id, {
|
|
5790
5898
|
severity: "error",
|
|
5791
5899
|
message,
|
|
5792
5900
|
file: ctx.pdfPath
|
|
5793
5901
|
}));
|
|
5794
5902
|
} catch {
|
|
5795
5903
|
return [
|
|
5796
|
-
inspectionFailed(
|
|
5904
|
+
inspectionFailed(check16.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
|
|
5797
5905
|
];
|
|
5798
5906
|
}
|
|
5799
5907
|
}
|
|
5800
5908
|
};
|
|
5801
|
-
registerCheck(
|
|
5909
|
+
registerCheck(check16);
|
|
5802
5910
|
|
|
5803
5911
|
// src/checks/source/markdownlint.ts
|
|
5804
5912
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
@@ -5853,7 +5961,7 @@ async function loadConfig(configPath) {
|
|
|
5853
5961
|
return {};
|
|
5854
5962
|
}
|
|
5855
5963
|
var MARKDOWNLINT_CHECK_ID = "source.markdownlint";
|
|
5856
|
-
var
|
|
5964
|
+
var check17 = {
|
|
5857
5965
|
id: MARKDOWNLINT_CHECK_ID,
|
|
5858
5966
|
name: "Markdownlint",
|
|
5859
5967
|
description: "Runs markdownlint with project config to validate Markdown files",
|
|
@@ -5878,7 +5986,7 @@ var check16 = {
|
|
|
5878
5986
|
continue;
|
|
5879
5987
|
for (const v of violations) {
|
|
5880
5988
|
out.push({
|
|
5881
|
-
checkId:
|
|
5989
|
+
checkId: check17.id,
|
|
5882
5990
|
severity: "warning",
|
|
5883
5991
|
message: `${v.ruleDescription} (${v.ruleNames.join("/")})`,
|
|
5884
5992
|
file,
|
|
@@ -5891,7 +5999,7 @@ var check16 = {
|
|
|
5891
5999
|
return out;
|
|
5892
6000
|
}
|
|
5893
6001
|
};
|
|
5894
|
-
registerCheck(
|
|
6002
|
+
registerCheck(check17);
|
|
5895
6003
|
|
|
5896
6004
|
// src/checks/source/htmlhint.ts
|
|
5897
6005
|
import { existsSync as existsSync7 } from "node:fs";
|
|
@@ -5899,7 +6007,7 @@ import { readFile as readFile10 } from "node:fs/promises";
|
|
|
5899
6007
|
import htmlhint from "htmlhint";
|
|
5900
6008
|
var { HTMLHint } = htmlhint;
|
|
5901
6009
|
var CONFIG_NAMES2 = [".htmlhintrc"];
|
|
5902
|
-
var
|
|
6010
|
+
var check18 = {
|
|
5903
6011
|
id: "source.htmlhint",
|
|
5904
6012
|
name: "HTMLHint",
|
|
5905
6013
|
description: "Runs htmlhint with project config to validate generated HTML",
|
|
@@ -5919,7 +6027,7 @@ var check17 = {
|
|
|
5919
6027
|
const html = await readFile10(ctx.htmlPath, "utf8");
|
|
5920
6028
|
const messages = HTMLHint.verify(html, ruleset);
|
|
5921
6029
|
return messages.map((m) => ({
|
|
5922
|
-
checkId:
|
|
6030
|
+
checkId: check18.id,
|
|
5923
6031
|
severity: m.type === "error" ? "error" : "warning",
|
|
5924
6032
|
message: `${m.rule.id}: ${m.message}`,
|
|
5925
6033
|
file: ctx.htmlPath,
|
|
@@ -5928,13 +6036,35 @@ var check17 = {
|
|
|
5928
6036
|
}));
|
|
5929
6037
|
}
|
|
5930
6038
|
};
|
|
5931
|
-
registerCheck(
|
|
6039
|
+
registerCheck(check18);
|
|
5932
6040
|
|
|
5933
6041
|
// src/checks/source/stylelint.ts
|
|
5934
6042
|
import { readFile as readFile11 } from "node:fs/promises";
|
|
5935
6043
|
|
|
5936
6044
|
// src/lib/printsafe.ts
|
|
5937
6045
|
import postcss from "postcss";
|
|
6046
|
+
|
|
6047
|
+
// src/lib/lint-rules.ts
|
|
6048
|
+
function ruleLevel(rules, id) {
|
|
6049
|
+
const v = rules?.[id];
|
|
6050
|
+
if (v === "off" || v === false)
|
|
6051
|
+
return "off";
|
|
6052
|
+
if (v === "warn" || v === "warning")
|
|
6053
|
+
return "warn";
|
|
6054
|
+
if (v === "error")
|
|
6055
|
+
return "error";
|
|
6056
|
+
return;
|
|
6057
|
+
}
|
|
6058
|
+
var DIRECTIVE = /^\s*gutterpress-disable-next-line\s+(.*?)\s*$/;
|
|
6059
|
+
function parseDisableDirective(commentText) {
|
|
6060
|
+
const m = DIRECTIVE.exec(commentText);
|
|
6061
|
+
if (!m)
|
|
6062
|
+
return null;
|
|
6063
|
+
const ids = m[1].split(/\s--(?:\s|$)/)[0].split(/[\s,]+/).filter(Boolean);
|
|
6064
|
+
return ids.length > 0 ? ids : null;
|
|
6065
|
+
}
|
|
6066
|
+
|
|
6067
|
+
// src/lib/printsafe.ts
|
|
5938
6068
|
var ruleRemoteUrls = "printsafe/no-remote-urls";
|
|
5939
6069
|
var ruleRiskyProps = "printsafe/no-risky-print-effects";
|
|
5940
6070
|
var ruleSyntax = "printsafe/syntax-error";
|
|
@@ -5959,9 +6089,11 @@ var riskyProperties = new Set([
|
|
|
5959
6089
|
"animation",
|
|
5960
6090
|
"animation-name",
|
|
5961
6091
|
"transition",
|
|
5962
|
-
"will-change"
|
|
5963
|
-
"clip-path"
|
|
6092
|
+
"will-change"
|
|
5964
6093
|
]);
|
|
6094
|
+
function isRasterizingClipPath(decl) {
|
|
6095
|
+
return decl.prop.toLowerCase() === "clip-path" && /\burl\(/i.test(decl.value);
|
|
6096
|
+
}
|
|
5965
6097
|
var marginBoxAtRuleNames = new Set([
|
|
5966
6098
|
"top-left-corner",
|
|
5967
6099
|
"top-left",
|
|
@@ -6088,7 +6220,34 @@ function nodeLoc(node) {
|
|
|
6088
6220
|
column: node.source?.start?.column ?? 1
|
|
6089
6221
|
};
|
|
6090
6222
|
}
|
|
6091
|
-
function checkCss(css, from) {
|
|
6223
|
+
function checkCss(css, from, options = {}) {
|
|
6224
|
+
return filterFindings(checkCssRaw(css, from), css, options.rules);
|
|
6225
|
+
}
|
|
6226
|
+
function filterFindings(findings, css, rules) {
|
|
6227
|
+
const silenced = new Map;
|
|
6228
|
+
for (const m of css.matchAll(/\/\*([\s\S]*?)\*\//g)) {
|
|
6229
|
+
const ids = parseDisableDirective(m[1]);
|
|
6230
|
+
if (!ids)
|
|
6231
|
+
continue;
|
|
6232
|
+
const endLine = css.slice(0, m.index + m[0].length).split(`
|
|
6233
|
+
`).length;
|
|
6234
|
+
const set = silenced.get(endLine + 1) ?? new Set;
|
|
6235
|
+
for (const id of ids)
|
|
6236
|
+
set.add(id);
|
|
6237
|
+
silenced.set(endLine + 1, set);
|
|
6238
|
+
}
|
|
6239
|
+
const out = [];
|
|
6240
|
+
for (const f of findings) {
|
|
6241
|
+
if (silenced.get(f.line)?.has(f.rule))
|
|
6242
|
+
continue;
|
|
6243
|
+
const level = ruleLevel(rules, f.rule);
|
|
6244
|
+
if (level === "off")
|
|
6245
|
+
continue;
|
|
6246
|
+
out.push(level === "warn" ? { ...f, severity: "warning" } : level === "error" ? { ...f, severity: "error" } : f);
|
|
6247
|
+
}
|
|
6248
|
+
return out;
|
|
6249
|
+
}
|
|
6250
|
+
function checkCssRaw(css, from) {
|
|
6092
6251
|
let root;
|
|
6093
6252
|
try {
|
|
6094
6253
|
root = postcss.parse(css, from ? { from } : undefined);
|
|
@@ -6137,6 +6296,13 @@ function checkCss(css, from) {
|
|
|
6137
6296
|
message: `Property is high-risk for print/PDF: 'filter' rasterizes its subtree to a 300 DPI bitmap in the printed PDF (text becomes unselectable, unsearchable, and inaccessible), and it dominates build time (~90% measured; 57.0s -> 6.2s over 60pp when scoped — see ENGINE.md §10). Scope it to the smallest possible selector. It also means the render-parity gate cannot see a text-only change inside it (docs/render-parity-gate.md, "Known blind spot").`,
|
|
6138
6297
|
...nodeLoc(decl)
|
|
6139
6298
|
});
|
|
6299
|
+
} else if (isRasterizingClipPath(decl)) {
|
|
6300
|
+
warnings.push({
|
|
6301
|
+
rule: ruleRiskyProps,
|
|
6302
|
+
severity: "warning",
|
|
6303
|
+
message: `Property is high-risk for print/PDF (can force rasterization): ${decl.prop} with url(...) references an SVG mask. Use a basic shape (polygon(), inset(), circle(), ellipse(), path()) instead — those print as vector clips.`,
|
|
6304
|
+
...nodeLoc(decl)
|
|
6305
|
+
});
|
|
6140
6306
|
} else if (riskyProperties.has(prop)) {
|
|
6141
6307
|
warnings.push({
|
|
6142
6308
|
rule: ruleRiskyProps,
|
|
@@ -6190,7 +6356,7 @@ function checkCss(css, from) {
|
|
|
6190
6356
|
}
|
|
6191
6357
|
|
|
6192
6358
|
// src/checks/source/stylelint.ts
|
|
6193
|
-
var
|
|
6359
|
+
var check19 = {
|
|
6194
6360
|
id: "source.stylelint",
|
|
6195
6361
|
name: "Print-safety CSS",
|
|
6196
6362
|
description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
|
|
@@ -6210,16 +6376,16 @@ var check18 = {
|
|
|
6210
6376
|
css = await readFile11(file, "utf8");
|
|
6211
6377
|
} catch (err) {
|
|
6212
6378
|
results.push({
|
|
6213
|
-
checkId:
|
|
6379
|
+
checkId: check19.id,
|
|
6214
6380
|
severity: "error",
|
|
6215
6381
|
message: `Cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`,
|
|
6216
6382
|
file
|
|
6217
6383
|
});
|
|
6218
6384
|
continue;
|
|
6219
6385
|
}
|
|
6220
|
-
for (const w of checkCss(css, file)) {
|
|
6386
|
+
for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
|
|
6221
6387
|
results.push({
|
|
6222
|
-
checkId:
|
|
6388
|
+
checkId: check19.id,
|
|
6223
6389
|
severity: w.severity,
|
|
6224
6390
|
message: `${w.rule}: ${w.message}`,
|
|
6225
6391
|
file,
|
|
@@ -6231,7 +6397,7 @@ var check18 = {
|
|
|
6231
6397
|
return results;
|
|
6232
6398
|
}
|
|
6233
6399
|
};
|
|
6234
|
-
registerCheck(
|
|
6400
|
+
registerCheck(check19);
|
|
6235
6401
|
|
|
6236
6402
|
// src/checks/source/css-ownership.ts
|
|
6237
6403
|
import { readFile as readFile12 } from "node:fs/promises";
|
|
@@ -6415,7 +6581,7 @@ function checkOwnership(root, file, maps) {
|
|
|
6415
6581
|
});
|
|
6416
6582
|
return out;
|
|
6417
6583
|
}
|
|
6418
|
-
var
|
|
6584
|
+
var check20 = {
|
|
6419
6585
|
id: "source.css-ownership",
|
|
6420
6586
|
name: "CSS ownership contract",
|
|
6421
6587
|
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.",
|
|
@@ -6438,7 +6604,7 @@ var check19 = {
|
|
|
6438
6604
|
} catch (err) {
|
|
6439
6605
|
return [
|
|
6440
6606
|
{
|
|
6441
|
-
checkId:
|
|
6607
|
+
checkId: check20.id,
|
|
6442
6608
|
severity: "error",
|
|
6443
6609
|
message: `Failed to parse CSS ownership contract "${configPath}": ${err instanceof Error ? err.message : String(err)}`,
|
|
6444
6610
|
file: configPath
|
|
@@ -6480,7 +6646,7 @@ var check19 = {
|
|
|
6480
6646
|
}
|
|
6481
6647
|
for (const v of violations) {
|
|
6482
6648
|
results.push({
|
|
6483
|
-
checkId:
|
|
6649
|
+
checkId: check20.id,
|
|
6484
6650
|
severity: "warning",
|
|
6485
6651
|
message: v.message,
|
|
6486
6652
|
file: absFile,
|
|
@@ -6492,7 +6658,7 @@ var check19 = {
|
|
|
6492
6658
|
return results;
|
|
6493
6659
|
}
|
|
6494
6660
|
};
|
|
6495
|
-
registerCheck(
|
|
6661
|
+
registerCheck(check20);
|
|
6496
6662
|
|
|
6497
6663
|
// src/checks/source/local-refs.ts
|
|
6498
6664
|
import { existsSync as existsSync9 } from "node:fs";
|
|
@@ -6883,7 +7049,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
|
|
|
6883
7049
|
}
|
|
6884
7050
|
|
|
6885
7051
|
// src/checks/source/local-refs.ts
|
|
6886
|
-
var
|
|
7052
|
+
var check21 = {
|
|
6887
7053
|
id: "source.links.local-refs",
|
|
6888
7054
|
name: "Local Markdown References",
|
|
6889
7055
|
description: "Checks local markdown links and image refs resolve on disk",
|
|
@@ -6891,12 +7057,12 @@ var check20 = {
|
|
|
6891
7057
|
phase: "pre-build",
|
|
6892
7058
|
async run(ctx) {
|
|
6893
7059
|
const results = [];
|
|
6894
|
-
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx,
|
|
7060
|
+
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check21.id, results)) {
|
|
6895
7061
|
if (isNonFilesystemRef(ref))
|
|
6896
7062
|
continue;
|
|
6897
7063
|
const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
|
|
6898
7064
|
if (escape) {
|
|
6899
|
-
results.push(finding(
|
|
7065
|
+
results.push(finding(check21.id, {
|
|
6900
7066
|
severity: "error",
|
|
6901
7067
|
message: escape,
|
|
6902
7068
|
file,
|
|
@@ -6906,7 +7072,7 @@ var check20 = {
|
|
|
6906
7072
|
}
|
|
6907
7073
|
if (localRefExists(ref, kind, file, ctx.inputDir))
|
|
6908
7074
|
continue;
|
|
6909
|
-
results.push(finding(
|
|
7075
|
+
results.push(finding(check21.id, {
|
|
6910
7076
|
severity: kind === "image" ? "warning" : "error",
|
|
6911
7077
|
code: kind === "image" ? "missing-image-placeholder" : undefined,
|
|
6912
7078
|
message: kind === "image" ? `Local image not found; build will substitute a magenta placeholder: ${ref}` : `Local reference not found: ${ref}`,
|
|
@@ -6931,7 +7097,7 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
|
|
|
6931
7097
|
return existsSync9(fileRelative);
|
|
6932
7098
|
}
|
|
6933
7099
|
}
|
|
6934
|
-
registerCheck(
|
|
7100
|
+
registerCheck(check21);
|
|
6935
7101
|
|
|
6936
7102
|
// src/lib/build-staging.ts
|
|
6937
7103
|
import path9 from "node:path";
|
|
@@ -7197,7 +7363,7 @@ async function createStageRoot() {
|
|
|
7197
7363
|
}
|
|
7198
7364
|
|
|
7199
7365
|
// src/checks/source/dangling-links.ts
|
|
7200
|
-
var
|
|
7366
|
+
var check22 = {
|
|
7201
7367
|
id: "source.links.dangling",
|
|
7202
7368
|
name: "Dangling Links",
|
|
7203
7369
|
description: "Flags relative links the printed book cannot open (their href is dropped at print time)",
|
|
@@ -7205,10 +7371,10 @@ var check21 = {
|
|
|
7205
7371
|
phase: "pre-build",
|
|
7206
7372
|
async run(ctx) {
|
|
7207
7373
|
const results = [];
|
|
7208
|
-
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx,
|
|
7374
|
+
for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check22.id, results)) {
|
|
7209
7375
|
if (kind !== "link" || isPrintResolvableHref(ref))
|
|
7210
7376
|
continue;
|
|
7211
|
-
results.push(finding(
|
|
7377
|
+
results.push(finding(check22.id, {
|
|
7212
7378
|
severity: "warning",
|
|
7213
7379
|
code: "dangling-link",
|
|
7214
7380
|
data: { ref },
|
|
@@ -7220,11 +7386,11 @@ var check21 = {
|
|
|
7220
7386
|
return results;
|
|
7221
7387
|
}
|
|
7222
7388
|
};
|
|
7223
|
-
registerCheck(
|
|
7389
|
+
registerCheck(check22);
|
|
7224
7390
|
|
|
7225
7391
|
// src/checks/source/accessibility-alt-text.ts
|
|
7226
7392
|
import { readFile as readFile15 } from "node:fs/promises";
|
|
7227
|
-
var
|
|
7393
|
+
var check23 = {
|
|
7228
7394
|
id: "source.accessibility.alt-text",
|
|
7229
7395
|
name: "Image Alt Text",
|
|
7230
7396
|
description: "Checks markdown images include non-empty alt text",
|
|
@@ -7253,7 +7419,7 @@ var check22 = {
|
|
|
7253
7419
|
if (alt.trim())
|
|
7254
7420
|
continue;
|
|
7255
7421
|
results.push({
|
|
7256
|
-
checkId:
|
|
7422
|
+
checkId: check23.id,
|
|
7257
7423
|
severity: "warning",
|
|
7258
7424
|
message: "Image is missing alt text",
|
|
7259
7425
|
file,
|
|
@@ -7278,11 +7444,11 @@ function extractImageAlts(line) {
|
|
|
7278
7444
|
}
|
|
7279
7445
|
return alts;
|
|
7280
7446
|
}
|
|
7281
|
-
registerCheck(
|
|
7447
|
+
registerCheck(check23);
|
|
7282
7448
|
|
|
7283
7449
|
// src/checks/source/accessibility-heading-order.ts
|
|
7284
7450
|
import { readFile as readFile16 } from "node:fs/promises";
|
|
7285
|
-
var
|
|
7451
|
+
var check24 = {
|
|
7286
7452
|
id: "source.accessibility.heading-order",
|
|
7287
7453
|
name: "Heading Order",
|
|
7288
7454
|
description: "Checks markdown heading levels do not jump by more than one",
|
|
@@ -7300,6 +7466,7 @@ var check23 = {
|
|
|
7300
7466
|
`);
|
|
7301
7467
|
let inFence = false;
|
|
7302
7468
|
let prevLevel;
|
|
7469
|
+
let silencedLine = -1;
|
|
7303
7470
|
for (let i = 0;i < lines.length; i++) {
|
|
7304
7471
|
const line = lines[i] ?? "";
|
|
7305
7472
|
if (/^\s*(```|~~~)/.test(line)) {
|
|
@@ -7308,13 +7475,18 @@ var check23 = {
|
|
|
7308
7475
|
}
|
|
7309
7476
|
if (inFence)
|
|
7310
7477
|
continue;
|
|
7478
|
+
const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
|
|
7479
|
+
if (comment && parseDisableDirective(comment[1])?.includes(check24.id)) {
|
|
7480
|
+
silencedLine = i + 1;
|
|
7481
|
+
continue;
|
|
7482
|
+
}
|
|
7311
7483
|
const match = line.match(/^(#{1,6})\s+\S/);
|
|
7312
7484
|
if (!match)
|
|
7313
7485
|
continue;
|
|
7314
7486
|
const level = match[1].length;
|
|
7315
|
-
if (prevLevel != null && level > prevLevel + 1) {
|
|
7487
|
+
if (prevLevel != null && level > prevLevel + 1 && i !== silencedLine) {
|
|
7316
7488
|
results.push({
|
|
7317
|
-
checkId:
|
|
7489
|
+
checkId: check24.id,
|
|
7318
7490
|
severity: "warning",
|
|
7319
7491
|
message: `Heading level jump from h${prevLevel} to h${level}`,
|
|
7320
7492
|
file,
|
|
@@ -7328,11 +7500,11 @@ var check23 = {
|
|
|
7328
7500
|
return results;
|
|
7329
7501
|
}
|
|
7330
7502
|
};
|
|
7331
|
-
registerCheck(
|
|
7503
|
+
registerCheck(check24);
|
|
7332
7504
|
|
|
7333
7505
|
// src/checks/source/layout-markers.ts
|
|
7334
7506
|
import { readFile as readFile17 } from "node:fs/promises";
|
|
7335
|
-
var
|
|
7507
|
+
var check25 = {
|
|
7336
7508
|
id: "source.markdown.layout-markers",
|
|
7337
7509
|
name: "Layout Markers",
|
|
7338
7510
|
description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class",
|
|
@@ -7344,7 +7516,7 @@ var check24 = {
|
|
|
7344
7516
|
return [];
|
|
7345
7517
|
const results = [];
|
|
7346
7518
|
const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
|
|
7347
|
-
results.push(inspectionFailed(
|
|
7519
|
+
results.push(inspectionFailed(check25.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
|
|
7348
7520
|
});
|
|
7349
7521
|
const md = createMarkdownRenderer(plugins);
|
|
7350
7522
|
for (const file of files) {
|
|
@@ -7353,7 +7525,7 @@ var check24 = {
|
|
|
7353
7525
|
const env = {};
|
|
7354
7526
|
md.render(content, env);
|
|
7355
7527
|
for (const w of env.layoutWarnings ?? []) {
|
|
7356
|
-
results.push(finding(
|
|
7528
|
+
results.push(finding(check25.id, {
|
|
7357
7529
|
severity: w.severity ?? "warning",
|
|
7358
7530
|
message: w.message,
|
|
7359
7531
|
file,
|
|
@@ -7362,13 +7534,13 @@ var check24 = {
|
|
|
7362
7534
|
}));
|
|
7363
7535
|
}
|
|
7364
7536
|
} catch (error2) {
|
|
7365
|
-
results.push(inspectionFailed(
|
|
7537
|
+
results.push(inspectionFailed(check25.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
|
|
7366
7538
|
}
|
|
7367
7539
|
}
|
|
7368
7540
|
return results;
|
|
7369
7541
|
}
|
|
7370
7542
|
};
|
|
7371
|
-
registerCheck(
|
|
7543
|
+
registerCheck(check25);
|
|
7372
7544
|
|
|
7373
7545
|
// src/checks/source/merge-markers.ts
|
|
7374
7546
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
@@ -7401,7 +7573,7 @@ var CLOSE_SENTINEL = ">>>>>>> online version";
|
|
|
7401
7573
|
function withoutOnlineTag(relPath) {
|
|
7402
7574
|
return relPath.replace(/\.online(?=\.[^./]*$|$)/, "");
|
|
7403
7575
|
}
|
|
7404
|
-
var
|
|
7576
|
+
var check26 = {
|
|
7405
7577
|
id: "source.sync.merge-markers",
|
|
7406
7578
|
name: "Combined Versions",
|
|
7407
7579
|
description: "Finds passages and files still holding two versions (yours and the online copy) after a sync",
|
|
@@ -7415,7 +7587,7 @@ var check25 = {
|
|
|
7415
7587
|
try {
|
|
7416
7588
|
content = await readFile18(file, "utf8");
|
|
7417
7589
|
} catch {
|
|
7418
|
-
results.push(inspectionFailed(
|
|
7590
|
+
results.push(inspectionFailed(check26.id, `Could not read source file: ${file}`, { file }));
|
|
7419
7591
|
continue;
|
|
7420
7592
|
}
|
|
7421
7593
|
if (!content.includes(OPEN_SENTINEL) && !content.includes(CLOSE_SENTINEL))
|
|
@@ -7427,7 +7599,7 @@ var check25 = {
|
|
|
7427
7599
|
const line = lines[i].endsWith("\r") ? lines[i].slice(0, -1) : lines[i];
|
|
7428
7600
|
if (line === OPEN_SENTINEL) {
|
|
7429
7601
|
inBlock = true;
|
|
7430
|
-
results.push(finding(
|
|
7602
|
+
results.push(finding(check26.id, {
|
|
7431
7603
|
severity: "error",
|
|
7432
7604
|
code: "two-versions-passage",
|
|
7433
7605
|
message: "This passage has two versions (yours and the online copy) — keep what you want, then delete the marker lines.",
|
|
@@ -7436,7 +7608,7 @@ var check25 = {
|
|
|
7436
7608
|
}));
|
|
7437
7609
|
} else if (line === CLOSE_SENTINEL) {
|
|
7438
7610
|
if (!inBlock) {
|
|
7439
|
-
results.push(finding(
|
|
7611
|
+
results.push(finding(check26.id, {
|
|
7440
7612
|
severity: "error",
|
|
7441
7613
|
code: "leftover-version-marker",
|
|
7442
7614
|
message: "This marker line is left over from combining two versions — delete it.",
|
|
@@ -7457,7 +7629,7 @@ var check25 = {
|
|
|
7457
7629
|
});
|
|
7458
7630
|
for (const sibling of siblings.sort()) {
|
|
7459
7631
|
const rel = path10.relative(ctx.inputDir, sibling).split(path10.sep).join("/");
|
|
7460
|
-
results.push(finding(
|
|
7632
|
+
results.push(finding(check26.id, {
|
|
7461
7633
|
severity: "warning",
|
|
7462
7634
|
code: "kept-both-versions",
|
|
7463
7635
|
message: `Two versions of ${withoutOnlineTag(rel)} are in your book — keep the one you want, then delete the other.`,
|
|
@@ -7465,12 +7637,12 @@ var check25 = {
|
|
|
7465
7637
|
}));
|
|
7466
7638
|
}
|
|
7467
7639
|
} catch (error2) {
|
|
7468
|
-
results.push(inspectionFailed(
|
|
7640
|
+
results.push(inspectionFailed(check26.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
|
|
7469
7641
|
}
|
|
7470
7642
|
return results;
|
|
7471
7643
|
}
|
|
7472
7644
|
};
|
|
7473
|
-
registerCheck(
|
|
7645
|
+
registerCheck(check26);
|
|
7474
7646
|
|
|
7475
7647
|
// src/checks/asset/image-file-size.ts
|
|
7476
7648
|
import { stat as stat4 } from "node:fs/promises";
|
|
@@ -7696,7 +7868,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
|
|
|
7696
7868
|
}
|
|
7697
7869
|
|
|
7698
7870
|
// src/checks/asset/image-file-size.ts
|
|
7699
|
-
var
|
|
7871
|
+
var check27 = {
|
|
7700
7872
|
id: "asset.image.file-size",
|
|
7701
7873
|
name: "Image File Size",
|
|
7702
7874
|
description: "Checks that image files do not exceed the maximum size limit",
|
|
@@ -7717,14 +7889,14 @@ var check26 = {
|
|
|
7717
7889
|
if (info2.size > maxSize) {
|
|
7718
7890
|
const sizeMb = (info2.size / 1e6).toFixed(1);
|
|
7719
7891
|
const maxMb = (maxSize / 1e6).toFixed(1);
|
|
7720
|
-
results.push(finding(
|
|
7892
|
+
results.push(finding(check27.id, {
|
|
7721
7893
|
severity: "warning",
|
|
7722
7894
|
message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
|
|
7723
7895
|
file
|
|
7724
7896
|
}));
|
|
7725
7897
|
}
|
|
7726
7898
|
} catch {
|
|
7727
|
-
results.push(inspectionFailed(
|
|
7899
|
+
results.push(inspectionFailed(check27.id, `Could not stat image file: ${file}`, {
|
|
7728
7900
|
file
|
|
7729
7901
|
}));
|
|
7730
7902
|
}
|
|
@@ -7732,10 +7904,10 @@ var check26 = {
|
|
|
7732
7904
|
return results;
|
|
7733
7905
|
}
|
|
7734
7906
|
};
|
|
7735
|
-
registerCheck(
|
|
7907
|
+
registerCheck(check27);
|
|
7736
7908
|
|
|
7737
7909
|
// src/checks/asset/image-resolution.ts
|
|
7738
|
-
var
|
|
7910
|
+
var check28 = {
|
|
7739
7911
|
id: "asset.image.resolution",
|
|
7740
7912
|
name: "Image Resolution",
|
|
7741
7913
|
description: "Checks source image DPI from embedded density metadata",
|
|
@@ -7757,7 +7929,7 @@ var check27 = {
|
|
|
7757
7929
|
const { xDpi, yDpi } = info2;
|
|
7758
7930
|
if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
|
|
7759
7931
|
results.push({
|
|
7760
|
-
checkId:
|
|
7932
|
+
checkId: check28.id,
|
|
7761
7933
|
severity: "warning",
|
|
7762
7934
|
message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
|
|
7763
7935
|
file
|
|
@@ -7767,7 +7939,7 @@ var check27 = {
|
|
|
7767
7939
|
return results;
|
|
7768
7940
|
}
|
|
7769
7941
|
};
|
|
7770
|
-
registerCheck(
|
|
7942
|
+
registerCheck(check28);
|
|
7771
7943
|
|
|
7772
7944
|
// src/checks/asset/image-color-space.ts
|
|
7773
7945
|
var LABEL = {
|
|
@@ -7775,7 +7947,7 @@ var LABEL = {
|
|
|
7775
7947
|
gray: "Gray",
|
|
7776
7948
|
cmyk: "CMYK"
|
|
7777
7949
|
};
|
|
7778
|
-
var
|
|
7950
|
+
var check29 = {
|
|
7779
7951
|
id: "asset.image.color-space",
|
|
7780
7952
|
name: "Image Color Space",
|
|
7781
7953
|
description: "Validates image color spaces against allowed list",
|
|
@@ -7796,7 +7968,7 @@ var check28 = {
|
|
|
7796
7968
|
const cs = info2?.colorSpace;
|
|
7797
7969
|
if (cs && !allowedLower.has(cs)) {
|
|
7798
7970
|
results.push({
|
|
7799
|
-
checkId:
|
|
7971
|
+
checkId: check29.id,
|
|
7800
7972
|
severity: "warning",
|
|
7801
7973
|
message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
|
|
7802
7974
|
file
|
|
@@ -7806,10 +7978,10 @@ var check28 = {
|
|
|
7806
7978
|
return results;
|
|
7807
7979
|
}
|
|
7808
7980
|
};
|
|
7809
|
-
registerCheck(
|
|
7981
|
+
registerCheck(check29);
|
|
7810
7982
|
|
|
7811
7983
|
// src/checks/asset/image-alpha.ts
|
|
7812
|
-
var
|
|
7984
|
+
var check30 = {
|
|
7813
7985
|
id: "asset.image.alpha-channel",
|
|
7814
7986
|
name: "Image Alpha Channel",
|
|
7815
7987
|
description: "Checks for alpha channels in PNG/TIFF images",
|
|
@@ -7827,7 +7999,7 @@ var check29 = {
|
|
|
7827
7999
|
const info2 = await inspectImage(file);
|
|
7828
8000
|
if (info2?.hasAlpha) {
|
|
7829
8001
|
results.push({
|
|
7830
|
-
checkId:
|
|
8002
|
+
checkId: check30.id,
|
|
7831
8003
|
severity: "warning",
|
|
7832
8004
|
message: "Image contains alpha channel, which may cause print issues",
|
|
7833
8005
|
file
|
|
@@ -7837,10 +8009,10 @@ var check29 = {
|
|
|
7837
8009
|
return results;
|
|
7838
8010
|
}
|
|
7839
8011
|
};
|
|
7840
|
-
registerCheck(
|
|
8012
|
+
registerCheck(check30);
|
|
7841
8013
|
|
|
7842
8014
|
// src/checks/asset/image-tac.ts
|
|
7843
|
-
var
|
|
8015
|
+
var check31 = {
|
|
7844
8016
|
id: "asset.image.tac-raster",
|
|
7845
8017
|
name: "Image TAC (Raster)",
|
|
7846
8018
|
description: "Rasterizes and checks TAC per image using Ghostscript",
|
|
@@ -7859,7 +8031,7 @@ var check30 = {
|
|
|
7859
8031
|
const ghostscript = await resolveGhostscript();
|
|
7860
8032
|
if (!ghostscript) {
|
|
7861
8033
|
return [
|
|
7862
|
-
inspectionFailed(
|
|
8034
|
+
inspectionFailed(check31.id, "Could not inspect image ink coverage: Ghostscript executable not found")
|
|
7863
8035
|
];
|
|
7864
8036
|
}
|
|
7865
8037
|
for (const file of files) {
|
|
@@ -7877,7 +8049,7 @@ var check30 = {
|
|
|
7877
8049
|
if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
|
|
7878
8050
|
const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
|
|
7879
8051
|
if (tac > maxTac) {
|
|
7880
|
-
results.push(finding(
|
|
8052
|
+
results.push(finding(check31.id, {
|
|
7881
8053
|
severity: "warning",
|
|
7882
8054
|
message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
|
|
7883
8055
|
file,
|
|
@@ -7889,16 +8061,16 @@ var check30 = {
|
|
|
7889
8061
|
}
|
|
7890
8062
|
}
|
|
7891
8063
|
} catch {
|
|
7892
|
-
results.push(inspectionFailed(
|
|
8064
|
+
results.push(inspectionFailed(check31.id, `Could not inspect image ink coverage: ${file}`, { file }));
|
|
7893
8065
|
}
|
|
7894
8066
|
}
|
|
7895
8067
|
return results;
|
|
7896
8068
|
}
|
|
7897
8069
|
};
|
|
7898
|
-
registerCheck(
|
|
8070
|
+
registerCheck(check31);
|
|
7899
8071
|
|
|
7900
8072
|
// src/checks/asset/approved-fonts.ts
|
|
7901
|
-
var
|
|
8073
|
+
var check32 = {
|
|
7902
8074
|
id: "asset.font.approved-files",
|
|
7903
8075
|
name: "Approved Font Files",
|
|
7904
8076
|
description: "Checks font files against the approved file patterns",
|
|
@@ -7937,7 +8109,7 @@ var check31 = {
|
|
|
7937
8109
|
for (const font of allFonts) {
|
|
7938
8110
|
if (!approvedFonts.has(font)) {
|
|
7939
8111
|
results.push({
|
|
7940
|
-
checkId:
|
|
8112
|
+
checkId: check32.id,
|
|
7941
8113
|
severity: "warning",
|
|
7942
8114
|
message: "Font file not in approved list",
|
|
7943
8115
|
file: font
|
|
@@ -7947,7 +8119,7 @@ var check31 = {
|
|
|
7947
8119
|
return results;
|
|
7948
8120
|
}
|
|
7949
8121
|
};
|
|
7950
|
-
registerCheck(
|
|
8122
|
+
registerCheck(check32);
|
|
7951
8123
|
|
|
7952
8124
|
// src/checks/asset/font-license.ts
|
|
7953
8125
|
import { existsSync as existsSync10 } from "node:fs";
|
|
@@ -7962,7 +8134,7 @@ var LICENSE_NAMES = [
|
|
|
7962
8134
|
"OFL-1.1.txt",
|
|
7963
8135
|
"COPYING"
|
|
7964
8136
|
];
|
|
7965
|
-
var
|
|
8137
|
+
var check33 = {
|
|
7966
8138
|
id: "asset.font.license",
|
|
7967
8139
|
name: "Font License",
|
|
7968
8140
|
description: "Checks for license files in font directories",
|
|
@@ -7989,7 +8161,7 @@ var check32 = {
|
|
|
7989
8161
|
const hasLicense = LICENSE_NAMES.some((name) => existsSync10(resolve6(fontDir, name)));
|
|
7990
8162
|
if (!hasLicense) {
|
|
7991
8163
|
results.push({
|
|
7992
|
-
checkId:
|
|
8164
|
+
checkId: check33.id,
|
|
7993
8165
|
severity: "warning",
|
|
7994
8166
|
message: `No font license file found in directory`,
|
|
7995
8167
|
file: fontDir
|
|
@@ -7999,10 +8171,10 @@ var check32 = {
|
|
|
7999
8171
|
return results;
|
|
8000
8172
|
}
|
|
8001
8173
|
};
|
|
8002
|
-
registerCheck(
|
|
8174
|
+
registerCheck(check33);
|
|
8003
8175
|
|
|
8004
8176
|
// src/checks/heuristic/text-density.ts
|
|
8005
|
-
var
|
|
8177
|
+
var check34 = {
|
|
8006
8178
|
id: "heuristic.whitespace.text-density",
|
|
8007
8179
|
name: "Text Density",
|
|
8008
8180
|
description: "Checks characters-per-page ratio",
|
|
@@ -8031,7 +8203,7 @@ var check33 = {
|
|
|
8031
8203
|
});
|
|
8032
8204
|
if (lowPages.length > 0) {
|
|
8033
8205
|
results.push({
|
|
8034
|
-
checkId:
|
|
8206
|
+
checkId: check34.id,
|
|
8035
8207
|
severity: "info",
|
|
8036
8208
|
message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
|
|
8037
8209
|
file: ctx.pdfPath
|
|
@@ -8039,7 +8211,7 @@ var check33 = {
|
|
|
8039
8211
|
}
|
|
8040
8212
|
if (highPages.length > 0) {
|
|
8041
8213
|
results.push({
|
|
8042
|
-
checkId:
|
|
8214
|
+
checkId: check34.id,
|
|
8043
8215
|
severity: "info",
|
|
8044
8216
|
message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
|
|
8045
8217
|
file: ctx.pdfPath
|
|
@@ -8048,11 +8220,11 @@ var check33 = {
|
|
|
8048
8220
|
return results;
|
|
8049
8221
|
}
|
|
8050
8222
|
};
|
|
8051
|
-
registerCheck(
|
|
8223
|
+
registerCheck(check34);
|
|
8052
8224
|
|
|
8053
8225
|
// src/checks/heuristic/section-density.ts
|
|
8054
8226
|
import { readFile as readFile20 } from "node:fs/promises";
|
|
8055
|
-
var
|
|
8227
|
+
var check35 = {
|
|
8056
8228
|
id: "heuristic.chunking.section-density",
|
|
8057
8229
|
name: "Section Density",
|
|
8058
8230
|
description: "Checks heading/paragraph/callout density from source Markdown",
|
|
@@ -8078,7 +8250,7 @@ var check34 = {
|
|
|
8078
8250
|
const line = lines[i];
|
|
8079
8251
|
if (/^#{1,6}\s/.test(line)) {
|
|
8080
8252
|
if (paragraphCount > maxParas) {
|
|
8081
|
-
results.push(finding(
|
|
8253
|
+
results.push(finding(check35.id, {
|
|
8082
8254
|
severity: "info",
|
|
8083
8255
|
message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
|
|
8084
8256
|
file,
|
|
@@ -8098,7 +8270,7 @@ var check34 = {
|
|
|
8098
8270
|
}
|
|
8099
8271
|
}
|
|
8100
8272
|
if (paragraphCount > maxParas) {
|
|
8101
|
-
results.push(finding(
|
|
8273
|
+
results.push(finding(check35.id, {
|
|
8102
8274
|
severity: "info",
|
|
8103
8275
|
message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
|
|
8104
8276
|
file,
|
|
@@ -8106,7 +8278,7 @@ var check34 = {
|
|
|
8106
8278
|
}));
|
|
8107
8279
|
}
|
|
8108
8280
|
} catch {
|
|
8109
|
-
results.push(inspectionFailed(
|
|
8281
|
+
results.push(inspectionFailed(check35.id, `Could not read source file: ${file}`, {
|
|
8110
8282
|
file
|
|
8111
8283
|
}));
|
|
8112
8284
|
}
|
|
@@ -8114,10 +8286,10 @@ var check34 = {
|
|
|
8114
8286
|
return results;
|
|
8115
8287
|
}
|
|
8116
8288
|
};
|
|
8117
|
-
registerCheck(
|
|
8289
|
+
registerCheck(check35);
|
|
8118
8290
|
|
|
8119
8291
|
// src/checks/heuristic/layer-count.ts
|
|
8120
|
-
var
|
|
8292
|
+
var check36 = {
|
|
8121
8293
|
id: "heuristic.decoration.layer-count",
|
|
8122
8294
|
name: "Layer Count",
|
|
8123
8295
|
description: "Counts image objects per page",
|
|
@@ -8142,7 +8314,7 @@ var check35 = {
|
|
|
8142
8314
|
heavyPages.sort((a, b) => a - b);
|
|
8143
8315
|
return [
|
|
8144
8316
|
{
|
|
8145
|
-
checkId:
|
|
8317
|
+
checkId: check36.id,
|
|
8146
8318
|
severity: "info",
|
|
8147
8319
|
message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
|
|
8148
8320
|
file: ctx.pdfPath
|
|
@@ -8152,10 +8324,10 @@ var check35 = {
|
|
|
8152
8324
|
return [];
|
|
8153
8325
|
}
|
|
8154
8326
|
};
|
|
8155
|
-
registerCheck(
|
|
8327
|
+
registerCheck(check36);
|
|
8156
8328
|
|
|
8157
8329
|
// src/checks/heuristic/placement-variance.ts
|
|
8158
|
-
var
|
|
8330
|
+
var check37 = {
|
|
8159
8331
|
id: "heuristic.layout.placement-variance",
|
|
8160
8332
|
name: "Placement Variance",
|
|
8161
8333
|
description: "Analyzes text baseline coordinates for layout consistency",
|
|
@@ -8173,7 +8345,7 @@ var check36 = {
|
|
|
8173
8345
|
const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
|
|
8174
8346
|
return [
|
|
8175
8347
|
{
|
|
8176
|
-
checkId:
|
|
8348
|
+
checkId: check37.id,
|
|
8177
8349
|
severity: "info",
|
|
8178
8350
|
message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
|
|
8179
8351
|
file: ctx.pdfPath
|
|
@@ -8181,21 +8353,21 @@ var check36 = {
|
|
|
8181
8353
|
];
|
|
8182
8354
|
}
|
|
8183
8355
|
};
|
|
8184
|
-
registerCheck(
|
|
8356
|
+
registerCheck(check37);
|
|
8185
8357
|
|
|
8186
8358
|
// src/checks/tool-check.ts
|
|
8187
8359
|
async function checkToolAvailability(config, opts = {}) {
|
|
8188
8360
|
const { checks: checks2 } = selectChecks(opts, config);
|
|
8189
8361
|
const toolToChecks = new Map;
|
|
8190
|
-
for (const
|
|
8191
|
-
if (!
|
|
8362
|
+
for (const check38 of checks2) {
|
|
8363
|
+
if (!check38.requiredTools?.length)
|
|
8192
8364
|
continue;
|
|
8193
|
-
for (const tool of
|
|
8365
|
+
for (const tool of check38.requiredTools) {
|
|
8194
8366
|
const existing = toolToChecks.get(tool);
|
|
8195
8367
|
if (existing) {
|
|
8196
|
-
existing.push(
|
|
8368
|
+
existing.push(check38.id);
|
|
8197
8369
|
} else {
|
|
8198
|
-
toolToChecks.set(tool, [
|
|
8370
|
+
toolToChecks.set(tool, [check38.id]);
|
|
8199
8371
|
}
|
|
8200
8372
|
}
|
|
8201
8373
|
}
|
|
@@ -8211,11 +8383,11 @@ async function checkToolAvailability(config, opts = {}) {
|
|
|
8211
8383
|
const missing = results.filter((r) => !r.found).map((r) => r.tool);
|
|
8212
8384
|
const missingSet = new Set(missing);
|
|
8213
8385
|
const skippedChecks = [];
|
|
8214
|
-
for (const
|
|
8215
|
-
if (!
|
|
8386
|
+
for (const check38 of checks2) {
|
|
8387
|
+
if (!check38.requiredTools?.length)
|
|
8216
8388
|
continue;
|
|
8217
|
-
if (
|
|
8218
|
-
skippedChecks.push(
|
|
8389
|
+
if (check38.requiredTools.some((t) => missingSet.has(t))) {
|
|
8390
|
+
skippedChecks.push(check38.id);
|
|
8219
8391
|
}
|
|
8220
8392
|
}
|
|
8221
8393
|
return { available, missing, skippedChecks, toolToChecks };
|
|
@@ -8333,7 +8505,8 @@ ${projectCss.trim()}` : null
|
|
|
8333
8505
|
`);
|
|
8334
8506
|
const preloadTags = (opts.preloadImages ?? []).map((href) => `
|
|
8335
8507
|
<link rel="preload" as="image" href="${href}">`).join("");
|
|
8336
|
-
const
|
|
8508
|
+
const lastPage = opts.print && lastPageMode(opts.print);
|
|
8509
|
+
const printAttrs = (opts.print?.signature && opts.print.signature > 1 ? ` data-gp-signature="${opts.print.signature}"` : "") + (lastPage ? ` data-gp-last-page="${lastPage}"` : "");
|
|
8337
8510
|
return `<!DOCTYPE html>
|
|
8338
8511
|
<html lang="en"${printAttrs}>
|
|
8339
8512
|
<head>
|
|
@@ -8574,25 +8747,32 @@ async function runChecks(ctx, opts = {}) {
|
|
|
8574
8747
|
}
|
|
8575
8748
|
const release = retainPdfCache();
|
|
8576
8749
|
try {
|
|
8577
|
-
for (const
|
|
8750
|
+
for (const check38 of checks2) {
|
|
8578
8751
|
try {
|
|
8579
|
-
|
|
8580
|
-
const severityOverride = getCheckSeverityOverride(
|
|
8752
|
+
let results = await check38.run(ctx);
|
|
8753
|
+
const severityOverride = getCheckSeverityOverride(check38.id, ctx.config);
|
|
8581
8754
|
if (severityOverride) {
|
|
8582
8755
|
for (const r of results) {
|
|
8583
8756
|
r.severity = severityOverride;
|
|
8584
8757
|
}
|
|
8585
8758
|
}
|
|
8759
|
+
const lintLevel = ruleLevel(ctx.config.lint.rules, check38.id);
|
|
8760
|
+
if (lintLevel === "off")
|
|
8761
|
+
results = [];
|
|
8762
|
+
else if (lintLevel) {
|
|
8763
|
+
for (const r of results)
|
|
8764
|
+
r.severity = lintLevel === "warn" ? "warning" : "error";
|
|
8765
|
+
}
|
|
8586
8766
|
if (results.length === 0) {
|
|
8587
|
-
passed.push(
|
|
8767
|
+
passed.push(check38.id);
|
|
8588
8768
|
} else {
|
|
8589
8769
|
allResults.push(...results);
|
|
8590
8770
|
}
|
|
8591
8771
|
} catch (err) {
|
|
8592
8772
|
allResults.push({
|
|
8593
|
-
checkId:
|
|
8773
|
+
checkId: check38.id,
|
|
8594
8774
|
severity: "error",
|
|
8595
|
-
message: `Check "${
|
|
8775
|
+
message: `Check "${check38.id}" threw: ${err instanceof Error ? err.message : String(err)}`
|
|
8596
8776
|
});
|
|
8597
8777
|
}
|
|
8598
8778
|
}
|
|
@@ -8884,7 +9064,7 @@ async function executeValidation(args) {
|
|
|
8884
9064
|
};
|
|
8885
9065
|
if (args.fix) {
|
|
8886
9066
|
const selected = selectChecks(runnerOptions, config).checks;
|
|
8887
|
-
if (selected.some((
|
|
9067
|
+
if (selected.some((check38) => check38.id === MARKDOWNLINT_CHECK_ID)) {
|
|
8888
9068
|
await applyMarkdownlintFixes(context);
|
|
8889
9069
|
} else {
|
|
8890
9070
|
log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
|
|
@@ -9121,7 +9301,8 @@ async function writeBuildFingerprint(input) {
|
|
|
9121
9301
|
requestedFlavor: input.pdfx.requestedFlavor,
|
|
9122
9302
|
resolvedFlavor: input.pdfx.resolvedFlavor,
|
|
9123
9303
|
iccPath: input.pdfx.iccPath,
|
|
9124
|
-
stripAnnotations: input.pdfx.stripAnnotations
|
|
9304
|
+
stripAnnotations: input.pdfx.stripAnnotations,
|
|
9305
|
+
blackText: input.pdfx.blackText ?? null
|
|
9125
9306
|
},
|
|
9126
9307
|
outputDir: input.recordedOutputDir ? path12.resolve(input.recordedOutputDir) : outputDir
|
|
9127
9308
|
},
|
|
@@ -9208,13 +9389,64 @@ function* tokenize(s) {
|
|
|
9208
9389
|
}
|
|
9209
9390
|
}
|
|
9210
9391
|
}
|
|
9211
|
-
|
|
9392
|
+
var LARGE_TEXT_PT = 24;
|
|
9393
|
+
var SIZE_EPS = 0.05;
|
|
9394
|
+
var area = (m) => Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
|
|
9395
|
+
|
|
9396
|
+
class TextScale {
|
|
9397
|
+
ctm;
|
|
9398
|
+
font = 0;
|
|
9399
|
+
tm = 1;
|
|
9400
|
+
stack = [];
|
|
9401
|
+
constructor(ctm) {
|
|
9402
|
+
this.ctm = ctm;
|
|
9403
|
+
}
|
|
9404
|
+
get unit() {
|
|
9405
|
+
return this.ctm;
|
|
9406
|
+
}
|
|
9407
|
+
get size() {
|
|
9408
|
+
return this.font * this.tm * this.ctm;
|
|
9409
|
+
}
|
|
9410
|
+
op(op, nums) {
|
|
9411
|
+
switch (op) {
|
|
9412
|
+
case "q":
|
|
9413
|
+
this.stack.push([this.ctm, this.font]);
|
|
9414
|
+
return false;
|
|
9415
|
+
case "Q": {
|
|
9416
|
+
const p = this.stack.pop();
|
|
9417
|
+
if (p)
|
|
9418
|
+
[this.ctm, this.font] = p;
|
|
9419
|
+
return true;
|
|
9420
|
+
}
|
|
9421
|
+
case "cm":
|
|
9422
|
+
if (nums.length === 6)
|
|
9423
|
+
this.ctm *= area(nums);
|
|
9424
|
+
return true;
|
|
9425
|
+
case "BT":
|
|
9426
|
+
this.tm = 1;
|
|
9427
|
+
return true;
|
|
9428
|
+
case "Tm":
|
|
9429
|
+
if (nums.length === 6)
|
|
9430
|
+
this.tm = area(nums);
|
|
9431
|
+
return true;
|
|
9432
|
+
case "Tf":
|
|
9433
|
+
this.font = Math.abs(nums[nums.length - 1] ?? 0) || 0;
|
|
9434
|
+
return true;
|
|
9435
|
+
}
|
|
9436
|
+
return false;
|
|
9437
|
+
}
|
|
9438
|
+
}
|
|
9439
|
+
function rewriteBlackText(src, ctx = {}) {
|
|
9212
9440
|
let fill = null;
|
|
9213
9441
|
let stroke = null;
|
|
9214
9442
|
const stack = [];
|
|
9443
|
+
const scale = new TextScale(ctx.baseScale ?? 1);
|
|
9215
9444
|
let inText = false;
|
|
9216
9445
|
let fillDirty = false;
|
|
9217
9446
|
let strokeDirty = false;
|
|
9447
|
+
const spans = [];
|
|
9448
|
+
let lastOp = "";
|
|
9449
|
+
let rect = null;
|
|
9218
9450
|
const edits = [];
|
|
9219
9451
|
let operands = [];
|
|
9220
9452
|
const rgbOf = () => {
|
|
@@ -9223,12 +9455,46 @@ function rewriteBlackText(src) {
|
|
|
9223
9455
|
const v = operands.map((t) => Number(t.text));
|
|
9224
9456
|
return v.some((x) => Number.isNaN(x)) ? null : { rgb: v, raw: operands.map((t) => t.text).join(" ") };
|
|
9225
9457
|
};
|
|
9458
|
+
const isSmall = () => scale.size <= LARGE_TEXT_PT + SIZE_EPS;
|
|
9459
|
+
let fillIns = null;
|
|
9460
|
+
let strokeIns = null;
|
|
9461
|
+
const syncOne = (colour, dirty, ins, at, gray, op) => {
|
|
9462
|
+
if (!colour || !isNearBlack(colour.rgb))
|
|
9463
|
+
return [dirty, ins];
|
|
9464
|
+
const small = isSmall();
|
|
9465
|
+
if (small && !dirty) {
|
|
9466
|
+
const e = { start: at, end: at, text: ` ${gray}` };
|
|
9467
|
+
edits.push(e);
|
|
9468
|
+
return [true, e];
|
|
9469
|
+
}
|
|
9470
|
+
if (!small && dirty) {
|
|
9471
|
+
if (ins)
|
|
9472
|
+
ins.text = "";
|
|
9473
|
+
else
|
|
9474
|
+
edits.push({ start: at, end: at, text: ` ${colour.raw} ${op}` });
|
|
9475
|
+
return [false, null];
|
|
9476
|
+
}
|
|
9477
|
+
return [dirty, ins];
|
|
9478
|
+
};
|
|
9479
|
+
const sync = (at) => {
|
|
9480
|
+
[fillDirty, fillIns] = syncOne(fill, fillDirty, fillIns, at, "0 g", "rg");
|
|
9481
|
+
[strokeDirty, strokeIns] = syncOne(stroke, strokeDirty, strokeIns, at, "0 G", "RG");
|
|
9482
|
+
};
|
|
9483
|
+
const flush = (s) => {
|
|
9484
|
+
for (const r of s.rects) {
|
|
9485
|
+
if (r.thickness > 0.2 * s.maxSmall)
|
|
9486
|
+
continue;
|
|
9487
|
+
edits.push({ start: r.start, end: r.start, text: "q 0 g " }, { start: r.end, end: r.end, text: " Q" });
|
|
9488
|
+
}
|
|
9489
|
+
};
|
|
9226
9490
|
for (const t of tokenize(src)) {
|
|
9227
9491
|
if (!t.op) {
|
|
9228
9492
|
operands.push(t);
|
|
9229
9493
|
continue;
|
|
9230
9494
|
}
|
|
9231
9495
|
const op = t.text;
|
|
9496
|
+
const nums = operands.map((o) => Number(o.text));
|
|
9497
|
+
const sizeMayHaveChanged = scale.op(op, nums);
|
|
9232
9498
|
switch (op) {
|
|
9233
9499
|
case "q":
|
|
9234
9500
|
stack.push([fill, stroke]);
|
|
@@ -9242,19 +9508,13 @@ function rewriteBlackText(src) {
|
|
|
9242
9508
|
case "BT":
|
|
9243
9509
|
inText = true;
|
|
9244
9510
|
fillDirty = strokeDirty = false;
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
ins += " 0 G";
|
|
9253
|
-
strokeDirty = true;
|
|
9254
|
-
}
|
|
9255
|
-
if (ins)
|
|
9256
|
-
edits.push({ start: t.end, end: t.end, text: ins });
|
|
9257
|
-
}
|
|
9511
|
+
fillIns = strokeIns = null;
|
|
9512
|
+
sync(t.end);
|
|
9513
|
+
break;
|
|
9514
|
+
case "Tf":
|
|
9515
|
+
case "Tm":
|
|
9516
|
+
if (inText && sizeMayHaveChanged)
|
|
9517
|
+
sync(t.end);
|
|
9258
9518
|
break;
|
|
9259
9519
|
case "ET":
|
|
9260
9520
|
if (inText) {
|
|
@@ -9267,6 +9527,7 @@ function rewriteBlackText(src) {
|
|
|
9267
9527
|
edits.push({ start: t.start, end: t.start, text: ins });
|
|
9268
9528
|
}
|
|
9269
9529
|
inText = false;
|
|
9530
|
+
fillIns = strokeIns = null;
|
|
9270
9531
|
break;
|
|
9271
9532
|
case "rg":
|
|
9272
9533
|
case "RG": {
|
|
@@ -9276,7 +9537,7 @@ function rewriteBlackText(src) {
|
|
|
9276
9537
|
fill = c;
|
|
9277
9538
|
else
|
|
9278
9539
|
stroke = c;
|
|
9279
|
-
if (inText && c && isNearBlack(c.rgb)) {
|
|
9540
|
+
if (inText && c && isNearBlack(c.rgb) && isSmall()) {
|
|
9280
9541
|
edits.push({ start: operands[0].start, end: t.end, text: isFill ? "0 g" : "0 G" });
|
|
9281
9542
|
if (isFill)
|
|
9282
9543
|
fillDirty = true;
|
|
@@ -9308,7 +9569,54 @@ function rewriteBlackText(src) {
|
|
|
9308
9569
|
if (inText)
|
|
9309
9570
|
strokeDirty = false;
|
|
9310
9571
|
break;
|
|
9572
|
+
case "Do": {
|
|
9573
|
+
const name = operands[operands.length - 1]?.text;
|
|
9574
|
+
if (name?.startsWith("/"))
|
|
9575
|
+
ctx.onDo?.(name.slice(1), scale.unit);
|
|
9576
|
+
break;
|
|
9577
|
+
}
|
|
9578
|
+
case "BDC":
|
|
9579
|
+
case "BMC":
|
|
9580
|
+
spans.push({ hasSmall: false, maxSmall: 0, rects: [] });
|
|
9581
|
+
break;
|
|
9582
|
+
case "EMC": {
|
|
9583
|
+
const s = spans.pop();
|
|
9584
|
+
if (!s)
|
|
9585
|
+
break;
|
|
9586
|
+
const parent = spans[spans.length - 1];
|
|
9587
|
+
if (s.hasSmall)
|
|
9588
|
+
flush(s);
|
|
9589
|
+
else
|
|
9590
|
+
parent?.rects.push(...s.rects);
|
|
9591
|
+
if (parent && s.hasSmall) {
|
|
9592
|
+
parent.hasSmall = true;
|
|
9593
|
+
parent.maxSmall = Math.max(parent.maxSmall, s.maxSmall);
|
|
9594
|
+
}
|
|
9595
|
+
break;
|
|
9596
|
+
}
|
|
9597
|
+
case "Tj":
|
|
9598
|
+
case "TJ":
|
|
9599
|
+
case "'":
|
|
9600
|
+
case '"': {
|
|
9601
|
+
fillIns = strokeIns = null;
|
|
9602
|
+
const span = spans[spans.length - 1];
|
|
9603
|
+
if (span && inText && isSmall()) {
|
|
9604
|
+
span.hasSmall = true;
|
|
9605
|
+
span.maxSmall = Math.max(span.maxSmall, scale.size);
|
|
9606
|
+
}
|
|
9607
|
+
break;
|
|
9608
|
+
}
|
|
9609
|
+
case "re":
|
|
9610
|
+
rect = !inText && fill && isNearBlack(fill.rgb) && operands.length === 4 && nums.every((x) => !Number.isNaN(x)) ? { start: operands[0].start, thickness: Math.min(Math.abs(nums[2]), Math.abs(nums[3])) * scale.unit } : null;
|
|
9611
|
+
break;
|
|
9612
|
+
case "f":
|
|
9613
|
+
case "F":
|
|
9614
|
+
case "f*":
|
|
9615
|
+
if (rect && lastOp === "re")
|
|
9616
|
+
spans[spans.length - 1]?.rects.push({ ...rect, end: t.end });
|
|
9617
|
+
break;
|
|
9311
9618
|
}
|
|
9619
|
+
lastOp = op;
|
|
9312
9620
|
operands = [];
|
|
9313
9621
|
}
|
|
9314
9622
|
return applyEdits(src, edits);
|
|
@@ -9316,6 +9624,7 @@ function rewriteBlackText(src) {
|
|
|
9316
9624
|
function applyEdits(s, edits) {
|
|
9317
9625
|
if (!edits.length)
|
|
9318
9626
|
return s;
|
|
9627
|
+
edits.sort((a, b) => a.start - b.start);
|
|
9319
9628
|
let out = "";
|
|
9320
9629
|
let pos = 0;
|
|
9321
9630
|
for (const e of edits) {
|
|
@@ -9324,9 +9633,8 @@ function applyEdits(s, edits) {
|
|
|
9324
9633
|
}
|
|
9325
9634
|
return out + s.slice(pos);
|
|
9326
9635
|
}
|
|
9327
|
-
async function
|
|
9328
|
-
const {
|
|
9329
|
-
const doc = await PDFDocument.load(pdf, { updateMetadata: false });
|
|
9636
|
+
async function visitContent(doc, visit) {
|
|
9637
|
+
const { PDFName, PDFRawStream, PDFArray, PDFRef, PDFDict, decodePDFRawStream } = await import("pdf-lib");
|
|
9330
9638
|
const ctx = doc.context;
|
|
9331
9639
|
const SKIP = ["Filter", "DecodeParms", "Length"].map((k) => PDFName.of(k));
|
|
9332
9640
|
let changed = false;
|
|
@@ -9345,19 +9653,15 @@ async function blackTextToGray(pdf) {
|
|
|
9345
9653
|
ctx.assign(ref, ctx.flateStream(Buffer.from(text, "latin1"), entries));
|
|
9346
9654
|
changed = true;
|
|
9347
9655
|
};
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
const
|
|
9357
|
-
if (next !== text)
|
|
9358
|
-
replace(ref, obj, next);
|
|
9359
|
-
}
|
|
9360
|
-
for (const page of doc.getPages()) {
|
|
9656
|
+
const formScale = new Map;
|
|
9657
|
+
const formMatrixScale = (form) => {
|
|
9658
|
+
const m = form.dict.lookupMaybe(PDFName.of("Matrix"), PDFArray);
|
|
9659
|
+
const nums = m?.asArray().map((n) => Number(n.toString()));
|
|
9660
|
+
return nums?.length === 6 && !nums.some(Number.isNaN) ? area(nums) : 1;
|
|
9661
|
+
};
|
|
9662
|
+
const pages = doc.getPages();
|
|
9663
|
+
for (let p = 0;p < pages.length; p++) {
|
|
9664
|
+
const page = pages[p];
|
|
9361
9665
|
const contents = page.node.Contents();
|
|
9362
9666
|
const refs = [];
|
|
9363
9667
|
if (contents instanceof PDFArray) {
|
|
@@ -9375,16 +9679,49 @@ async function blackTextToGray(pdf) {
|
|
|
9375
9679
|
if (!refs.length || !streams.every((s) => s instanceof PDFRawStream))
|
|
9376
9680
|
continue;
|
|
9377
9681
|
const parts = streams.map((s) => decode(s));
|
|
9378
|
-
if (parts.some((
|
|
9682
|
+
if (parts.some((q) => q === null))
|
|
9379
9683
|
continue;
|
|
9380
9684
|
const joined = parts.join(`
|
|
9381
9685
|
`);
|
|
9382
|
-
const
|
|
9686
|
+
const xobjects = page.node.Resources()?.lookupMaybe(PDFName.of("XObject"), PDFDict);
|
|
9687
|
+
const next = visit(joined, {
|
|
9688
|
+
baseScale: 1,
|
|
9689
|
+
onDo: (name, scale) => {
|
|
9690
|
+
const ref = xobjects?.get(PDFName.of(name));
|
|
9691
|
+
const form = ref instanceof PDFRef ? ctx.lookup(ref) : undefined;
|
|
9692
|
+
if (!(ref instanceof PDFRef) || !(form instanceof PDFRawStream))
|
|
9693
|
+
return;
|
|
9694
|
+
if (form.dict.get(PDFName.of("Subtype")) !== PDFName.of("Form"))
|
|
9695
|
+
return;
|
|
9696
|
+
const at = scale * formMatrixScale(form);
|
|
9697
|
+
formScale.set(ref.tag, Math.min(formScale.get(ref.tag) ?? Infinity, at));
|
|
9698
|
+
}
|
|
9699
|
+
});
|
|
9383
9700
|
if (next === joined)
|
|
9384
9701
|
continue;
|
|
9385
9702
|
replace(refs[0], streams[0], next);
|
|
9386
9703
|
page.node.set(PDFName.of("Contents"), refs[0]);
|
|
9387
9704
|
}
|
|
9705
|
+
for (const [ref, obj] of ctx.enumerateIndirectObjects()) {
|
|
9706
|
+
if (!(obj instanceof PDFRawStream))
|
|
9707
|
+
continue;
|
|
9708
|
+
if (obj.dict.get(PDFName.of("Subtype")) !== PDFName.of("Form"))
|
|
9709
|
+
continue;
|
|
9710
|
+
const text = decode(obj);
|
|
9711
|
+
if (text === null)
|
|
9712
|
+
continue;
|
|
9713
|
+
const next = visit(text, {
|
|
9714
|
+
baseScale: formScale.get(ref.tag) ?? 0
|
|
9715
|
+
});
|
|
9716
|
+
if (next !== text)
|
|
9717
|
+
replace(ref, obj, next);
|
|
9718
|
+
}
|
|
9719
|
+
return changed;
|
|
9720
|
+
}
|
|
9721
|
+
async function blackTextToGray(pdf) {
|
|
9722
|
+
const { PDFDocument } = await import("pdf-lib");
|
|
9723
|
+
const doc = await PDFDocument.load(pdf, { updateMetadata: false });
|
|
9724
|
+
const changed = await visitContent(doc, (src, info2) => rewriteBlackText(src, info2));
|
|
9388
9725
|
return changed ? doc.save({ updateFieldAppearances: false }) : pdf;
|
|
9389
9726
|
}
|
|
9390
9727
|
|
|
@@ -9662,7 +9999,8 @@ class HtmlOutput {
|
|
|
9662
9999
|
requestedFlavor: null,
|
|
9663
10000
|
resolvedFlavor: config.pdfx.flavor,
|
|
9664
10001
|
iccPath: null,
|
|
9665
|
-
stripAnnotations: null
|
|
10002
|
+
stripAnnotations: null,
|
|
10003
|
+
blackText: null
|
|
9666
10004
|
}
|
|
9667
10005
|
}, `Wrote: ${path13.join(outDir, BOOK_HTML)}`, { htmlPath: htmlFile, pdfPath: null });
|
|
9668
10006
|
}
|
|
@@ -9682,12 +10020,12 @@ class PdfOutput {
|
|
|
9682
10020
|
const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
|
|
9683
10021
|
await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
|
|
9684
10022
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
9685
|
-
const { buildNativePdf } = await import("./engine-
|
|
10023
|
+
const { buildNativePdf } = await import("./engine-zdb41ay1.js");
|
|
9686
10024
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
9687
10025
|
title: config.title,
|
|
9688
10026
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
9689
10027
|
signature: config.print.signature,
|
|
9690
|
-
|
|
10028
|
+
lastPage: lastPageMode(config.print),
|
|
9691
10029
|
allowShrink: opts.allowShrink
|
|
9692
10030
|
}, browser);
|
|
9693
10031
|
for (const d of engineDiagnostics)
|
|
@@ -9746,7 +10084,8 @@ class PdfOutput {
|
|
|
9746
10084
|
requestedFlavor: pdfxMode ?? null,
|
|
9747
10085
|
resolvedFlavor: config.pdfx.flavor,
|
|
9748
10086
|
iccPath: effectiveIccPath,
|
|
9749
|
-
stripAnnotations: shouldStripAnnotations
|
|
10087
|
+
stripAnnotations: shouldStripAnnotations,
|
|
10088
|
+
blackText: pdfxMode ? config.pdfx.blackText : null
|
|
9750
10089
|
}
|
|
9751
10090
|
}, `Wrote: ${reportedPdf}`, { htmlPath: htmlFile, pdfPath: reportedPdf }, pdfName, engineDiagnostics);
|
|
9752
10091
|
} finally {
|
|
@@ -10903,6 +11242,26 @@ async function startPreviewServer(options) {
|
|
|
10903
11242
|
import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile26, stat as stat6, writeFile as writeFile6 } from "node:fs/promises";
|
|
10904
11243
|
import { constants as FS, existsSync as existsSync12 } from "node:fs";
|
|
10905
11244
|
import path18 from "node:path";
|
|
11245
|
+
|
|
11246
|
+
// src/lib/page-size.ts
|
|
11247
|
+
import postcss4 from "postcss";
|
|
11248
|
+
import { Scalar as Scalar2 } from "yaml";
|
|
11249
|
+
function isPositivePoints(value) {
|
|
11250
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
11251
|
+
}
|
|
11252
|
+
function writePageBounds(doc, page) {
|
|
11253
|
+
const out = { width: page.width, height: page.height };
|
|
11254
|
+
const tolerance = page.tolerance ?? numberAt(doc, ["page", "tolerance"]);
|
|
11255
|
+
if (tolerance !== undefined)
|
|
11256
|
+
out.tolerance = tolerance;
|
|
11257
|
+
doc.set("page", doc.createNode(out));
|
|
11258
|
+
}
|
|
11259
|
+
function numberAt(doc, keyPath) {
|
|
11260
|
+
const v = doc.getIn(keyPath);
|
|
11261
|
+
return isPositivePoints(v) ? v : undefined;
|
|
11262
|
+
}
|
|
11263
|
+
|
|
11264
|
+
// src/lib/project-scaffold.ts
|
|
10906
11265
|
function starterOverridesCss() {
|
|
10907
11266
|
return `/**
|
|
10908
11267
|
* Your book's own styles.
|
|
@@ -10931,9 +11290,6 @@ function escapeYamlScalar(value) {
|
|
|
10931
11290
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
10932
11291
|
}
|
|
10933
11292
|
var DEFAULT_AUTHOR = "Anonymous";
|
|
10934
|
-
function isPositivePoints(value) {
|
|
10935
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
10936
|
-
}
|
|
10937
11293
|
function requirePreset(preset, customPage) {
|
|
10938
11294
|
if (!preset || !PRESET_IDS.includes(preset)) {
|
|
10939
11295
|
const got = preset ? ` (got "${preset}")` : "";
|
|
@@ -11037,16 +11393,8 @@ async function scaffoldProject(options) {
|
|
|
11037
11393
|
const { doc, file } = await loadManifestDoc(projectDir);
|
|
11038
11394
|
doc.set("preset", preset);
|
|
11039
11395
|
doc.set("targets", doc.createNode(targets ?? []));
|
|
11040
|
-
if (options.customPage)
|
|
11041
|
-
|
|
11042
|
-
width: options.customPage.width,
|
|
11043
|
-
height: options.customPage.height
|
|
11044
|
-
};
|
|
11045
|
-
if (options.customPage.tolerance !== undefined) {
|
|
11046
|
-
page.tolerance = options.customPage.tolerance;
|
|
11047
|
-
}
|
|
11048
|
-
doc.set("page", doc.createNode(page));
|
|
11049
|
-
}
|
|
11396
|
+
if (options.customPage)
|
|
11397
|
+
writePageBounds(doc, options.customPage);
|
|
11050
11398
|
await writeManifestDoc(file, doc);
|
|
11051
11399
|
}
|
|
11052
11400
|
const firstSource = await firstSourceFile(manifestPath);
|
|
@@ -13562,13 +13910,16 @@ async function disconnectPublishCredential(key, deps, options = {}) {
|
|
|
13562
13910
|
}
|
|
13563
13911
|
}
|
|
13564
13912
|
// src/lib/manifest-config.ts
|
|
13565
|
-
import { isSeq as isSeq4, isMap as isMap3, YAMLSeq as YAMLSeq3, Scalar as
|
|
13913
|
+
import { isSeq as isSeq4, isMap as isMap3, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
|
|
13566
13914
|
// src/lib/host-policy.ts
|
|
13567
13915
|
var AUTO_SYNC_MAX_MINUTES = 24 * 60;
|
|
13568
13916
|
// src/lib/remote-auth/github-repos.ts
|
|
13569
13917
|
var MANIFEST_NAMES = new Set(MANIFEST_FILENAMES);
|
|
13570
|
-
// src/lib/remote-auth/
|
|
13571
|
-
import
|
|
13918
|
+
// src/lib/remote-auth/online-backup.ts
|
|
13919
|
+
import git5 from "isomorphic-git";
|
|
13920
|
+
|
|
13921
|
+
// src/lib/remote-auth/sync.ts
|
|
13922
|
+
import git4 from "isomorphic-git";
|
|
13572
13923
|
|
|
13573
13924
|
// src/lib/remote-auth/git-http.ts
|
|
13574
13925
|
import httpNode from "isomorphic-git/http/node";
|
|
@@ -13661,21 +14012,21 @@ function withIdleTimeout(http3, idleMs = GIT_HTTP_IDLE_TIMEOUT_MS, uploadMs = GI
|
|
|
13661
14012
|
}
|
|
13662
14013
|
var defaultGitHttp = withIdleTimeout(httpNode);
|
|
13663
14014
|
|
|
13664
|
-
// src/lib/remote-auth/
|
|
14015
|
+
// src/lib/remote-auth/converge-merge.ts
|
|
13665
14016
|
import git2 from "isomorphic-git";
|
|
14017
|
+
import diff3Merge from "diff3";
|
|
14018
|
+
|
|
14019
|
+
// src/lib/remote-auth/transport.ts
|
|
14020
|
+
import git3 from "isomorphic-git";
|
|
14021
|
+
// src/lib/remote-auth/clone.ts
|
|
14022
|
+
import git6 from "isomorphic-git";
|
|
13666
14023
|
// src/lib/remote-auth/test-access.ts
|
|
13667
|
-
import
|
|
14024
|
+
import git7 from "isomorphic-git";
|
|
13668
14025
|
import httpNode2 from "isomorphic-git/http/node";
|
|
13669
|
-
// src/lib/remote-auth/sync.ts
|
|
13670
|
-
import git6 from "isomorphic-git";
|
|
13671
|
-
|
|
13672
|
-
// src/lib/remote-auth/converge-merge.ts
|
|
13673
|
-
import git5 from "isomorphic-git";
|
|
13674
|
-
import diff3Merge from "diff3";
|
|
13675
14026
|
// src/lib/remote-auth/repair.ts
|
|
13676
|
-
import git7 from "isomorphic-git";
|
|
13677
|
-
// src/lib/remote-auth/scorched-earth.ts
|
|
13678
14027
|
import git8 from "isomorphic-git";
|
|
14028
|
+
// src/lib/remote-auth/scorched-earth.ts
|
|
14029
|
+
import git9 from "isomorphic-git";
|
|
13679
14030
|
// src/lib/publish/selections.ts
|
|
13680
14031
|
import { chmod as chmod2, mkdir as mkdir13, readFile as readFile30, writeFile as writeFile12 } from "node:fs/promises";
|
|
13681
14032
|
import path26 from "node:path";
|