gutterpress 0.11.14-alpha.1 → 0.11.15-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.js +2 -2
- package/dist/{build-9ccmg4jw.js → build-r395dbr9.js} +4 -4
- package/dist/{cli-s38v88rv.js → cli-1chdeea4.js} +270 -8
- package/dist/{cli-tctw7rcm.js → cli-7j9jvvs7.js} +1 -1
- package/dist/{cli-cmyfeqg6.js → cli-bscaare6.js} +1 -1
- package/dist/{cli-tjhn2mdn.js → cli-kchpmn97.js} +9 -11
- package/dist/cli.js +12 -12
- package/dist/{doctor-2kp6wsnx.js → doctor-5rq3qhdp.js} +2 -2
- package/dist/engine/compiler/build.d.ts +2 -0
- package/dist/engine/compiler/postprocess.d.ts +6 -0
- package/dist/{engine-132czsq9.js → engine-0bybzbwq.js} +1 -1
- package/dist/{engine-bgd4aa99.js → engine-k7xfrh7g.js} +2 -2
- package/dist/{ext-kct0k4ne.js → ext-0bvaceew.js} +4 -4
- package/dist/{index-b6p1fh61.js → index-6t5e811b.js} +9 -11
- package/dist/{index-mw8b25yx.js → index-9fwjqyv5.js} +1 -1
- package/dist/{index-ch9k0kam.js → index-qpm8kr79.js} +269 -7
- package/dist/index.js +3 -3
- package/dist/lib/black-text-gray.d.ts +40 -0
- package/dist/lib/engine.d.ts +1 -0
- package/dist/{manifest.schema-61bsspsb.json → manifest.schema-b9ph2xcr.json} +6 -0
- package/dist/{new-pa0pgcat.js → new-rqdgep00.js} +4 -4
- package/dist/{preflight-6v0j7ae8.js → preflight-vg3d7a0c.js} +4 -4
- package/dist/{preview-msq4mz3a.js → preview-1b2cpwt4.js} +4 -4
- package/dist/{publish-2rgbacn1.js → publish-kedn43sb.js} +4 -4
- package/dist/schema/manifest.types.d.ts +10 -0
- package/dist/{validate-ekbmn8wh.js → validate-vqe18k3t.js} +4 -4
- package/package.json +1 -1
package/dist/api/index.js
CHANGED
|
@@ -129,10 +129,10 @@ import {
|
|
|
129
129
|
testRemoteAccess,
|
|
130
130
|
updateExtensions,
|
|
131
131
|
validateProjectExtensions
|
|
132
|
-
} from "../index-
|
|
132
|
+
} from "../index-qpm8kr79.js";
|
|
133
133
|
import {
|
|
134
134
|
BuildError
|
|
135
|
-
} from "../index-
|
|
135
|
+
} from "../index-9fwjqyv5.js";
|
|
136
136
|
import {
|
|
137
137
|
DEFAULT_PRINT_OPTS,
|
|
138
138
|
assertMilestone,
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runBuild,
|
|
3
3
|
splitOutPath
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-1chdeea4.js";
|
|
5
5
|
import {
|
|
6
6
|
log
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-bscaare6.js";
|
|
8
8
|
import {
|
|
9
9
|
UsageError,
|
|
10
10
|
parseFormat,
|
|
11
11
|
parsePdfxFlavor,
|
|
12
12
|
rejectExtraPositionals,
|
|
13
13
|
rejectUnknownFlags
|
|
14
|
-
} from "./cli-
|
|
15
|
-
import"./cli-
|
|
14
|
+
} from "./cli-7j9jvvs7.js";
|
|
15
|
+
import"./cli-kchpmn97.js";
|
|
16
16
|
import {
|
|
17
17
|
BuildError
|
|
18
18
|
} from "./cli-46ycxe6r.js";
|
|
@@ -13,17 +13,17 @@ import {
|
|
|
13
13
|
stampCreator,
|
|
14
14
|
stripAnnotations,
|
|
15
15
|
warn
|
|
16
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-bscaare6.js";
|
|
17
17
|
import {
|
|
18
18
|
DEBOUNCE,
|
|
19
19
|
UsageError,
|
|
20
20
|
resolvePort
|
|
21
|
-
} from "./cli-
|
|
21
|
+
} from "./cli-7j9jvvs7.js";
|
|
22
22
|
import {
|
|
23
23
|
MARGIN_BOX_IGNORED_PROPERTIES,
|
|
24
24
|
getAssetPath,
|
|
25
25
|
getAssetsDir
|
|
26
|
-
} from "./cli-
|
|
26
|
+
} from "./cli-kchpmn97.js";
|
|
27
27
|
import {
|
|
28
28
|
BuildError
|
|
29
29
|
} from "./cli-46ycxe6r.js";
|
|
@@ -105,11 +105,12 @@ var DTRPG_PRESET = {
|
|
|
105
105
|
source: {
|
|
106
106
|
files: null
|
|
107
107
|
},
|
|
108
|
-
print: { signature:
|
|
108
|
+
print: { signature: 4, reserveLastPage: true },
|
|
109
109
|
pdfx: {
|
|
110
110
|
flavor: "x1a",
|
|
111
111
|
icc: "profiles/CGATS21_CRPC1.icc",
|
|
112
|
-
stripAnnotations: true
|
|
112
|
+
stripAnnotations: true,
|
|
113
|
+
blackText: "k-only"
|
|
113
114
|
},
|
|
114
115
|
page: {
|
|
115
116
|
width: 621,
|
|
@@ -179,11 +180,12 @@ var BOOK_PRESET = {
|
|
|
179
180
|
source: {
|
|
180
181
|
files: null
|
|
181
182
|
},
|
|
182
|
-
print: { signature: 1 },
|
|
183
|
+
print: { signature: 1, reserveLastPage: false },
|
|
183
184
|
pdfx: {
|
|
184
185
|
flavor: "x1a",
|
|
185
186
|
icc: "profiles/CGATS21_CRPC1.icc",
|
|
186
|
-
stripAnnotations: true
|
|
187
|
+
stripAnnotations: true,
|
|
188
|
+
blackText: "icc"
|
|
187
189
|
},
|
|
188
190
|
page: {
|
|
189
191
|
width: 432,
|
|
@@ -7436,6 +7438,261 @@ async function writeBuildFingerprint(input) {
|
|
|
7436
7438
|
return outPath;
|
|
7437
7439
|
}
|
|
7438
7440
|
|
|
7441
|
+
// src/lib/black-text-gray.ts
|
|
7442
|
+
var MAX_CHANNEL = 0.12;
|
|
7443
|
+
var MAX_SPREAD = 0.04;
|
|
7444
|
+
function isNearBlack([r, g, b]) {
|
|
7445
|
+
const max = Math.max(r, g, b);
|
|
7446
|
+
return max <= MAX_CHANNEL && max - Math.min(r, g, b) <= MAX_SPREAD;
|
|
7447
|
+
}
|
|
7448
|
+
var WS = new Set([0, 9, 10, 12, 13, 32]);
|
|
7449
|
+
var DELIM = new Set("()<>[]{}/%".split("").map((c) => c.charCodeAt(0)));
|
|
7450
|
+
function* tokenize(s) {
|
|
7451
|
+
let i = 0;
|
|
7452
|
+
const n = s.length;
|
|
7453
|
+
while (i < n) {
|
|
7454
|
+
const c = s.charCodeAt(i);
|
|
7455
|
+
if (WS.has(c)) {
|
|
7456
|
+
i++;
|
|
7457
|
+
continue;
|
|
7458
|
+
}
|
|
7459
|
+
const start = i;
|
|
7460
|
+
if (c === 37) {
|
|
7461
|
+
while (i < n && s[i] !== `
|
|
7462
|
+
` && s[i] !== "\r")
|
|
7463
|
+
i++;
|
|
7464
|
+
continue;
|
|
7465
|
+
}
|
|
7466
|
+
if (c === 40) {
|
|
7467
|
+
let depth = 0;
|
|
7468
|
+
while (i < n) {
|
|
7469
|
+
const d = s[i++];
|
|
7470
|
+
if (d === "\\")
|
|
7471
|
+
i++;
|
|
7472
|
+
else if (d === "(")
|
|
7473
|
+
depth++;
|
|
7474
|
+
else if (d === ")" && --depth === 0)
|
|
7475
|
+
break;
|
|
7476
|
+
}
|
|
7477
|
+
yield { text: s.slice(start, i), start, end: i, op: false };
|
|
7478
|
+
continue;
|
|
7479
|
+
}
|
|
7480
|
+
if (c === 60 && s[i + 1] === "<") {
|
|
7481
|
+
i += 2;
|
|
7482
|
+
yield { text: "<<", start, end: i, op: false };
|
|
7483
|
+
continue;
|
|
7484
|
+
}
|
|
7485
|
+
if (c === 62 && s[i + 1] === ">") {
|
|
7486
|
+
i += 2;
|
|
7487
|
+
yield { text: ">>", start, end: i, op: false };
|
|
7488
|
+
continue;
|
|
7489
|
+
}
|
|
7490
|
+
if (c === 60) {
|
|
7491
|
+
while (i < n && s[i] !== ">")
|
|
7492
|
+
i++;
|
|
7493
|
+
i++;
|
|
7494
|
+
yield { text: s.slice(start, i), start, end: i, op: false };
|
|
7495
|
+
continue;
|
|
7496
|
+
}
|
|
7497
|
+
if (c === 91 || c === 93 || c === 62 || c === 123 || c === 125 || c === 41) {
|
|
7498
|
+
i++;
|
|
7499
|
+
yield { text: s[start], start, end: i, op: false };
|
|
7500
|
+
continue;
|
|
7501
|
+
}
|
|
7502
|
+
i++;
|
|
7503
|
+
while (i < n && !WS.has(s.charCodeAt(i)) && !DELIM.has(s.charCodeAt(i)))
|
|
7504
|
+
i++;
|
|
7505
|
+
const text = s.slice(start, i);
|
|
7506
|
+
const isOperand = c === 47 || /^[+-]?(\d+\.?\d*|\.\d+)$/.test(text);
|
|
7507
|
+
yield { text, start, end: i, op: !isOperand };
|
|
7508
|
+
if (text === "ID") {
|
|
7509
|
+
const m = /\sEI(?=\s|$)/g;
|
|
7510
|
+
m.lastIndex = i;
|
|
7511
|
+
const hit = m.exec(s);
|
|
7512
|
+
i = hit ? hit.index + hit[0].length : n;
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
}
|
|
7516
|
+
function rewriteBlackText(src) {
|
|
7517
|
+
let fill = null;
|
|
7518
|
+
let stroke = null;
|
|
7519
|
+
const stack = [];
|
|
7520
|
+
let inText = false;
|
|
7521
|
+
let fillDirty = false;
|
|
7522
|
+
let strokeDirty = false;
|
|
7523
|
+
const edits = [];
|
|
7524
|
+
let operands = [];
|
|
7525
|
+
const rgbOf = () => {
|
|
7526
|
+
if (operands.length !== 3)
|
|
7527
|
+
return null;
|
|
7528
|
+
const v = operands.map((t) => Number(t.text));
|
|
7529
|
+
return v.some((x) => Number.isNaN(x)) ? null : { rgb: v, raw: operands.map((t) => t.text).join(" ") };
|
|
7530
|
+
};
|
|
7531
|
+
for (const t of tokenize(src)) {
|
|
7532
|
+
if (!t.op) {
|
|
7533
|
+
operands.push(t);
|
|
7534
|
+
continue;
|
|
7535
|
+
}
|
|
7536
|
+
const op = t.text;
|
|
7537
|
+
switch (op) {
|
|
7538
|
+
case "q":
|
|
7539
|
+
stack.push([fill, stroke]);
|
|
7540
|
+
break;
|
|
7541
|
+
case "Q": {
|
|
7542
|
+
const p = stack.pop();
|
|
7543
|
+
if (p)
|
|
7544
|
+
[fill, stroke] = p;
|
|
7545
|
+
break;
|
|
7546
|
+
}
|
|
7547
|
+
case "BT":
|
|
7548
|
+
inText = true;
|
|
7549
|
+
fillDirty = strokeDirty = false;
|
|
7550
|
+
{
|
|
7551
|
+
let ins = "";
|
|
7552
|
+
if (fill && isNearBlack(fill.rgb)) {
|
|
7553
|
+
ins += " 0 g";
|
|
7554
|
+
fillDirty = true;
|
|
7555
|
+
}
|
|
7556
|
+
if (stroke && isNearBlack(stroke.rgb)) {
|
|
7557
|
+
ins += " 0 G";
|
|
7558
|
+
strokeDirty = true;
|
|
7559
|
+
}
|
|
7560
|
+
if (ins)
|
|
7561
|
+
edits.push({ start: t.end, end: t.end, text: ins });
|
|
7562
|
+
}
|
|
7563
|
+
break;
|
|
7564
|
+
case "ET":
|
|
7565
|
+
if (inText) {
|
|
7566
|
+
let ins = "";
|
|
7567
|
+
if (fillDirty && fill)
|
|
7568
|
+
ins += `${fill.raw} rg `;
|
|
7569
|
+
if (strokeDirty && stroke)
|
|
7570
|
+
ins += `${stroke.raw} RG `;
|
|
7571
|
+
if (ins)
|
|
7572
|
+
edits.push({ start: t.start, end: t.start, text: ins });
|
|
7573
|
+
}
|
|
7574
|
+
inText = false;
|
|
7575
|
+
break;
|
|
7576
|
+
case "rg":
|
|
7577
|
+
case "RG": {
|
|
7578
|
+
const c = rgbOf();
|
|
7579
|
+
const isFill = op === "rg";
|
|
7580
|
+
if (isFill)
|
|
7581
|
+
fill = c;
|
|
7582
|
+
else
|
|
7583
|
+
stroke = c;
|
|
7584
|
+
if (inText && c && isNearBlack(c.rgb)) {
|
|
7585
|
+
edits.push({ start: operands[0].start, end: t.end, text: isFill ? "0 g" : "0 G" });
|
|
7586
|
+
if (isFill)
|
|
7587
|
+
fillDirty = true;
|
|
7588
|
+
else
|
|
7589
|
+
strokeDirty = true;
|
|
7590
|
+
} else if (inText) {
|
|
7591
|
+
if (isFill)
|
|
7592
|
+
fillDirty = false;
|
|
7593
|
+
else
|
|
7594
|
+
strokeDirty = false;
|
|
7595
|
+
}
|
|
7596
|
+
break;
|
|
7597
|
+
}
|
|
7598
|
+
case "g":
|
|
7599
|
+
case "k":
|
|
7600
|
+
case "sc":
|
|
7601
|
+
case "scn":
|
|
7602
|
+
case "cs":
|
|
7603
|
+
fill = null;
|
|
7604
|
+
if (inText)
|
|
7605
|
+
fillDirty = false;
|
|
7606
|
+
break;
|
|
7607
|
+
case "G":
|
|
7608
|
+
case "K":
|
|
7609
|
+
case "SC":
|
|
7610
|
+
case "SCN":
|
|
7611
|
+
case "CS":
|
|
7612
|
+
stroke = null;
|
|
7613
|
+
if (inText)
|
|
7614
|
+
strokeDirty = false;
|
|
7615
|
+
break;
|
|
7616
|
+
}
|
|
7617
|
+
operands = [];
|
|
7618
|
+
}
|
|
7619
|
+
return applyEdits(src, edits);
|
|
7620
|
+
}
|
|
7621
|
+
function applyEdits(s, edits) {
|
|
7622
|
+
if (!edits.length)
|
|
7623
|
+
return s;
|
|
7624
|
+
let out = "";
|
|
7625
|
+
let pos = 0;
|
|
7626
|
+
for (const e of edits) {
|
|
7627
|
+
out += s.slice(pos, e.start) + e.text;
|
|
7628
|
+
pos = e.end;
|
|
7629
|
+
}
|
|
7630
|
+
return out + s.slice(pos);
|
|
7631
|
+
}
|
|
7632
|
+
async function blackTextToGray(pdf) {
|
|
7633
|
+
const { PDFDocument, PDFName, PDFRawStream, PDFArray, PDFRef, decodePDFRawStream } = await import("pdf-lib");
|
|
7634
|
+
const doc = await PDFDocument.load(pdf, { updateMetadata: false });
|
|
7635
|
+
const ctx = doc.context;
|
|
7636
|
+
const SKIP = ["Filter", "DecodeParms", "Length"].map((k) => PDFName.of(k));
|
|
7637
|
+
let changed = false;
|
|
7638
|
+
const decode = (s) => {
|
|
7639
|
+
try {
|
|
7640
|
+
return Buffer.from(decodePDFRawStream(s).decode()).toString("latin1");
|
|
7641
|
+
} catch {
|
|
7642
|
+
return null;
|
|
7643
|
+
}
|
|
7644
|
+
};
|
|
7645
|
+
const replace = (ref, old, text) => {
|
|
7646
|
+
const entries = {};
|
|
7647
|
+
for (const [k, v] of old.dict.entries())
|
|
7648
|
+
if (!SKIP.includes(k))
|
|
7649
|
+
entries[k.decodeText()] = v;
|
|
7650
|
+
ctx.assign(ref, ctx.flateStream(Buffer.from(text, "latin1"), entries));
|
|
7651
|
+
changed = true;
|
|
7652
|
+
};
|
|
7653
|
+
for (const [ref, obj] of ctx.enumerateIndirectObjects()) {
|
|
7654
|
+
if (!(obj instanceof PDFRawStream))
|
|
7655
|
+
continue;
|
|
7656
|
+
if (obj.dict.get(PDFName.of("Subtype")) !== PDFName.of("Form"))
|
|
7657
|
+
continue;
|
|
7658
|
+
const text = decode(obj);
|
|
7659
|
+
if (text === null)
|
|
7660
|
+
continue;
|
|
7661
|
+
const next = rewriteBlackText(text);
|
|
7662
|
+
if (next !== text)
|
|
7663
|
+
replace(ref, obj, next);
|
|
7664
|
+
}
|
|
7665
|
+
for (const page of doc.getPages()) {
|
|
7666
|
+
const contents = page.node.Contents();
|
|
7667
|
+
const refs = [];
|
|
7668
|
+
if (contents instanceof PDFArray) {
|
|
7669
|
+
for (let i = 0;i < contents.size(); i++) {
|
|
7670
|
+
const r = contents.get(i);
|
|
7671
|
+
if (r instanceof PDFRef)
|
|
7672
|
+
refs.push(r);
|
|
7673
|
+
}
|
|
7674
|
+
} else if (contents instanceof PDFRawStream) {
|
|
7675
|
+
const r = page.node.get(PDFName.of("Contents"));
|
|
7676
|
+
if (r instanceof PDFRef)
|
|
7677
|
+
refs.push(r);
|
|
7678
|
+
}
|
|
7679
|
+
const streams = refs.map((r) => ctx.lookup(r));
|
|
7680
|
+
if (!refs.length || !streams.every((s) => s instanceof PDFRawStream))
|
|
7681
|
+
continue;
|
|
7682
|
+
const parts = streams.map((s) => decode(s));
|
|
7683
|
+
if (parts.some((p) => p === null))
|
|
7684
|
+
continue;
|
|
7685
|
+
const joined = parts.join(`
|
|
7686
|
+
`);
|
|
7687
|
+
const next = rewriteBlackText(joined);
|
|
7688
|
+
if (next === joined)
|
|
7689
|
+
continue;
|
|
7690
|
+
replace(refs[0], streams[0], next);
|
|
7691
|
+
page.node.set(PDFName.of("Contents"), refs[0]);
|
|
7692
|
+
}
|
|
7693
|
+
return changed ? doc.save({ updateFieldAppearances: false }) : pdf;
|
|
7694
|
+
}
|
|
7695
|
+
|
|
7439
7696
|
// src/lib/build-preflight.ts
|
|
7440
7697
|
var INSTALL_HINTS2 = {
|
|
7441
7698
|
gs: INSTALL_HINTS.gs.body,
|
|
@@ -7728,11 +7985,12 @@ class PdfOutput {
|
|
|
7728
7985
|
const rawPdf = pdfxMode ? path8.join(stage, "raw.pdf") : path8.resolve(pdfFile);
|
|
7729
7986
|
await fsp2.mkdir(path8.dirname(path8.resolve(pdfFile)), { recursive: true });
|
|
7730
7987
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
7731
|
-
const { buildNativePdf } = await import("./engine-
|
|
7988
|
+
const { buildNativePdf } = await import("./engine-0bybzbwq.js");
|
|
7732
7989
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
7733
7990
|
title: config.title,
|
|
7734
7991
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
7735
7992
|
signature: config.print.signature,
|
|
7993
|
+
reserveLastPage: config.print.reserveLastPage,
|
|
7736
7994
|
allowShrink: opts.allowShrink
|
|
7737
7995
|
}, browser);
|
|
7738
7996
|
for (const d of engineDiagnostics)
|
|
@@ -7758,6 +8016,10 @@ class PdfOutput {
|
|
|
7758
8016
|
log.info("Stripping annotations for PDF/X compliance");
|
|
7759
8017
|
await stripAnnotations(rawPdf, stage);
|
|
7760
8018
|
}
|
|
8019
|
+
if (config.pdfx.blackText === "k-only") {
|
|
8020
|
+
log.info("Rewriting near-black text to K-only gray");
|
|
8021
|
+
await fsp2.writeFile(rawPdf, await blackTextToGray(await fsp2.readFile(rawPdf)));
|
|
8022
|
+
}
|
|
7761
8023
|
log.info(`Converting to CMYK PDF/X (${pdfxMode})`);
|
|
7762
8024
|
await convertToPdfxCmyk(rawPdf, path8.resolve(pdfFile), {
|
|
7763
8025
|
iccPath: effectiveIccPath,
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
// package.json
|
|
5
5
|
var package_default = {
|
|
6
6
|
name: "gutterpress",
|
|
7
|
-
version: "0.11.
|
|
7
|
+
version: "0.11.15-alpha.1",
|
|
8
8
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
9
9
|
author: "itlackey",
|
|
10
10
|
license: "MPL-2.0",
|
|
@@ -1106,7 +1106,7 @@ import { fileURLToPath } from "node:url";
|
|
|
1106
1106
|
var favicon_default = "./favicon-wkbm9cjn.ico";
|
|
1107
1107
|
|
|
1108
1108
|
// src/assets/manifest.schema.json
|
|
1109
|
-
var manifest_schema_default = "./manifest.schema-
|
|
1109
|
+
var manifest_schema_default = "./manifest.schema-b9ph2xcr.json";
|
|
1110
1110
|
|
|
1111
1111
|
// src/assets/preview/scripts/preview-interface.js
|
|
1112
1112
|
var preview_interface_default = "./preview-interface-twd6nsf5.js";
|
|
@@ -1450,15 +1450,12 @@ async function postprocess(input, opts) {
|
|
|
1450
1450
|
g.media.width - inset,
|
|
1451
1451
|
g.media.height - inset
|
|
1452
1452
|
];
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
doc.addPage([g.media.width, g.media.height]);
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1453
|
+
const signature = opts.signature && opts.signature > 1 ? opts.signature : 1;
|
|
1454
|
+
let padded = (signature - doc.getPageCount() % signature) % signature;
|
|
1455
|
+
if (padded === 0 && opts.reserveLastPage)
|
|
1456
|
+
padded = signature;
|
|
1457
|
+
for (let i = 0;i < padded; i++)
|
|
1458
|
+
doc.addPage([g.media.width, g.media.height]);
|
|
1462
1459
|
const wantMarks = opts.marks || g.marks.includes("crop") || g.marks.includes("cross");
|
|
1463
1460
|
for (const page of doc.getPages()) {
|
|
1464
1461
|
const node = page.node;
|
|
@@ -2233,11 +2230,12 @@ ${mapCss}`;
|
|
|
2233
2230
|
const post = await postprocess(bytes, {
|
|
2234
2231
|
geometry: tier2.geometry,
|
|
2235
2232
|
signature: opts.signature,
|
|
2233
|
+
reserveLastPage: opts.reserveLastPage,
|
|
2236
2234
|
marks: opts.marks,
|
|
2237
2235
|
title: opts.title,
|
|
2238
2236
|
author: opts.author
|
|
2239
2237
|
});
|
|
2240
|
-
log(`postprocess: ${post.pageCount} pages` + (post.padded ? ` (+${post.padded}
|
|
2238
|
+
log(`postprocess: ${post.pageCount} pages` + (post.padded ? ` (+${post.padded} blank${opts.reserveLastPage ? "; last page reserved blank" : ""})` : ""));
|
|
2241
2239
|
return {
|
|
2242
2240
|
bytes: post.bytes,
|
|
2243
2241
|
tier,
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
UsageError,
|
|
7
7
|
package_default,
|
|
8
8
|
rejectUnknownFlags
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-7j9jvvs7.js";
|
|
10
10
|
import {
|
|
11
11
|
EXIT_CODES
|
|
12
12
|
} from "./cli-46ycxe6r.js";
|
|
@@ -18,14 +18,14 @@ import {
|
|
|
18
18
|
import { defineCommand, parseArgs, runMain } from "citty";
|
|
19
19
|
import { statSync } from "node:fs";
|
|
20
20
|
var SUBCOMMANDS = {
|
|
21
|
-
new: () => import("./new-
|
|
22
|
-
preview: () => import("./preview-
|
|
23
|
-
build: () => import("./build-
|
|
24
|
-
publish: () => import("./publish-
|
|
25
|
-
validate: () => import("./validate-
|
|
26
|
-
preflight: () => import("./preflight-
|
|
27
|
-
doctor: () => import("./doctor-
|
|
28
|
-
ext: () => import("./ext-
|
|
21
|
+
new: () => import("./new-rqdgep00.js").then((m) => m.default),
|
|
22
|
+
preview: () => import("./preview-1b2cpwt4.js").then((m) => m.default),
|
|
23
|
+
build: () => import("./build-r395dbr9.js").then((m) => m.default),
|
|
24
|
+
publish: () => import("./publish-kedn43sb.js").then((m) => m.default),
|
|
25
|
+
validate: () => import("./validate-vqe18k3t.js").then((m) => m.default),
|
|
26
|
+
preflight: () => import("./preflight-vg3d7a0c.js").then((m) => m.default),
|
|
27
|
+
doctor: () => import("./doctor-5rq3qhdp.js").then((m) => m.default),
|
|
28
|
+
ext: () => import("./ext-0bvaceew.js").then((m) => m.default)
|
|
29
29
|
};
|
|
30
30
|
var VERSION = package_default.version;
|
|
31
31
|
var main = defineCommand({
|
|
@@ -50,7 +50,7 @@ async function preflightRequiredInvocations(rawArgs) {
|
|
|
50
50
|
const [command, ...commandArgs] = rawArgs;
|
|
51
51
|
try {
|
|
52
52
|
if (command === "new") {
|
|
53
|
-
const { newArgs } = await import("./new-
|
|
53
|
+
const { newArgs } = await import("./new-rqdgep00.js");
|
|
54
54
|
rejectUnknownFlags(commandArgs, newArgs, "new");
|
|
55
55
|
const parsed = parseArgs(commandArgs, {
|
|
56
56
|
...newArgs,
|
|
@@ -62,7 +62,7 @@ async function preflightRequiredInvocations(rawArgs) {
|
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
if (command === "preflight") {
|
|
65
|
-
const { preflightArgs } = await import("./preflight-
|
|
65
|
+
const { preflightArgs } = await import("./preflight-vg3d7a0c.js");
|
|
66
66
|
rejectUnknownFlags(commandArgs, preflightArgs, "preflight");
|
|
67
67
|
const parsed = parseArgs(commandArgs, {
|
|
68
68
|
...preflightArgs,
|
|
@@ -82,7 +82,7 @@ async function preflightRequiredInvocations(rawArgs) {
|
|
|
82
82
|
}
|
|
83
83
|
if (subcommand.startsWith("-"))
|
|
84
84
|
return;
|
|
85
|
-
const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-
|
|
85
|
+
const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-0bvaceew.js");
|
|
86
86
|
if (!EXT_SUBCOMMANDS.includes(subcommand)) {
|
|
87
87
|
throw new UsageError(`gutterpress ext: unknown command "${subcommand}"`);
|
|
88
88
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSystemDiagnostics,
|
|
3
3
|
log
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-bscaare6.js";
|
|
5
5
|
import {
|
|
6
6
|
UsageError,
|
|
7
7
|
rejectExtraPositionals,
|
|
8
8
|
rejectUnknownFlags
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-7j9jvvs7.js";
|
|
10
10
|
import"./cli-46ycxe6r.js";
|
|
11
11
|
import"./cli-7fkhgtfn.js";
|
|
12
12
|
import"./cli-37x76zdn.js";
|
|
@@ -3,6 +3,12 @@ export interface PostprocessOptions {
|
|
|
3
3
|
geometry: PageTrim;
|
|
4
4
|
/** append blank pages until pageCount % signature === 0 */
|
|
5
5
|
signature?: number;
|
|
6
|
+
/**
|
|
7
|
+
* Guarantee the last page is a blank page added here. If signature padding
|
|
8
|
+
* would add nothing, append one full signature of blank pages (one page when
|
|
9
|
+
* signature <= 1).
|
|
10
|
+
*/
|
|
11
|
+
reserveLastPage?: boolean;
|
|
6
12
|
title?: string;
|
|
7
13
|
author?: string;
|
|
8
14
|
subject?: string;
|
|
@@ -12,16 +12,16 @@ import {
|
|
|
12
12
|
searchNpmExtensions,
|
|
13
13
|
setExtensionEnabled,
|
|
14
14
|
updateExtensions
|
|
15
|
-
} from "./cli-
|
|
16
|
-
import"./cli-
|
|
15
|
+
} from "./cli-1chdeea4.js";
|
|
16
|
+
import"./cli-bscaare6.js";
|
|
17
17
|
import {
|
|
18
18
|
UsageError,
|
|
19
19
|
exitForUsage,
|
|
20
20
|
rejectExtraPositionals,
|
|
21
21
|
rejectUnknownFlags,
|
|
22
22
|
resolveProjectDir
|
|
23
|
-
} from "./cli-
|
|
24
|
-
import"./cli-
|
|
23
|
+
} from "./cli-7j9jvvs7.js";
|
|
24
|
+
import"./cli-kchpmn97.js";
|
|
25
25
|
import {
|
|
26
26
|
EXIT_CODES
|
|
27
27
|
} from "./cli-46ycxe6r.js";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
isIgnoredMarginBoxProperty,
|
|
4
4
|
marginBoxAlign,
|
|
5
5
|
marginBoxRectPt
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-9fwjqyv5.js";
|
|
7
7
|
import {
|
|
8
8
|
launchChromium
|
|
9
9
|
} from "./index-rzm1phg0.js";
|
|
@@ -1207,15 +1207,12 @@ async function postprocess(input, opts) {
|
|
|
1207
1207
|
g.media.width - inset,
|
|
1208
1208
|
g.media.height - inset
|
|
1209
1209
|
];
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
doc.addPage([g.media.width, g.media.height]);
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1210
|
+
const signature = opts.signature && opts.signature > 1 ? opts.signature : 1;
|
|
1211
|
+
let padded = (signature - doc.getPageCount() % signature) % signature;
|
|
1212
|
+
if (padded === 0 && opts.reserveLastPage)
|
|
1213
|
+
padded = signature;
|
|
1214
|
+
for (let i = 0;i < padded; i++)
|
|
1215
|
+
doc.addPage([g.media.width, g.media.height]);
|
|
1219
1216
|
const wantMarks = opts.marks || g.marks.includes("crop") || g.marks.includes("cross");
|
|
1220
1217
|
for (const page of doc.getPages()) {
|
|
1221
1218
|
const node = page.node;
|
|
@@ -2002,11 +1999,12 @@ ${mapCss}`;
|
|
|
2002
1999
|
const post = await postprocess(bytes, {
|
|
2003
2000
|
geometry: tier2.geometry,
|
|
2004
2001
|
signature: opts.signature,
|
|
2002
|
+
reserveLastPage: opts.reserveLastPage,
|
|
2005
2003
|
marks: opts.marks,
|
|
2006
2004
|
title: opts.title,
|
|
2007
2005
|
author: opts.author
|
|
2008
2006
|
});
|
|
2009
|
-
log(`postprocess: ${post.pageCount} pages` + (post.padded ? ` (+${post.padded}
|
|
2007
|
+
log(`postprocess: ${post.pageCount} pages` + (post.padded ? ` (+${post.padded} blank${opts.reserveLastPage ? "; last page reserved blank" : ""})` : ""));
|
|
2010
2008
|
return {
|
|
2011
2009
|
bytes: post.bytes,
|
|
2012
2010
|
tier,
|
|
@@ -26,7 +26,7 @@ import { fileURLToPath } from "node:url";
|
|
|
26
26
|
var favicon_default = "./favicon-wkbm9cjn.ico";
|
|
27
27
|
|
|
28
28
|
// src/assets/manifest.schema.json
|
|
29
|
-
var manifest_schema_default = "./manifest.schema-
|
|
29
|
+
var manifest_schema_default = "./manifest.schema-b9ph2xcr.json";
|
|
30
30
|
|
|
31
31
|
// src/assets/preview/scripts/preview-interface.js
|
|
32
32
|
var preview_interface_default = "./preview-interface-twd6nsf5.js";
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
MARGIN_BOX_IGNORED_PROPERTIES,
|
|
5
5
|
getAssetPath,
|
|
6
6
|
getAssetsDir
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-9fwjqyv5.js";
|
|
8
8
|
import {
|
|
9
9
|
INSTALL_HINTS,
|
|
10
10
|
execCapture,
|
|
@@ -140,11 +140,12 @@ var DTRPG_PRESET = {
|
|
|
140
140
|
source: {
|
|
141
141
|
files: null
|
|
142
142
|
},
|
|
143
|
-
print: { signature:
|
|
143
|
+
print: { signature: 4, reserveLastPage: true },
|
|
144
144
|
pdfx: {
|
|
145
145
|
flavor: "x1a",
|
|
146
146
|
icc: "profiles/CGATS21_CRPC1.icc",
|
|
147
|
-
stripAnnotations: true
|
|
147
|
+
stripAnnotations: true,
|
|
148
|
+
blackText: "k-only"
|
|
148
149
|
},
|
|
149
150
|
page: {
|
|
150
151
|
width: 621,
|
|
@@ -214,11 +215,12 @@ var BOOK_PRESET = {
|
|
|
214
215
|
source: {
|
|
215
216
|
files: null
|
|
216
217
|
},
|
|
217
|
-
print: { signature: 1 },
|
|
218
|
+
print: { signature: 1, reserveLastPage: false },
|
|
218
219
|
pdfx: {
|
|
219
220
|
flavor: "x1a",
|
|
220
221
|
icc: "profiles/CGATS21_CRPC1.icc",
|
|
221
|
-
stripAnnotations: true
|
|
222
|
+
stripAnnotations: true,
|
|
223
|
+
blackText: "icc"
|
|
222
224
|
},
|
|
223
225
|
page: {
|
|
224
226
|
width: 432,
|
|
@@ -3858,7 +3860,7 @@ import git from "isomorphic-git";
|
|
|
3858
3860
|
// package.json
|
|
3859
3861
|
var package_default = {
|
|
3860
3862
|
name: "gutterpress",
|
|
3861
|
-
version: "0.11.
|
|
3863
|
+
version: "0.11.15-alpha.1",
|
|
3862
3864
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
3863
3865
|
author: "itlackey",
|
|
3864
3866
|
license: "MPL-2.0",
|
|
@@ -4123,6 +4125,261 @@ async function writeBuildFingerprint(input) {
|
|
|
4123
4125
|
return outPath;
|
|
4124
4126
|
}
|
|
4125
4127
|
|
|
4128
|
+
// src/lib/black-text-gray.ts
|
|
4129
|
+
var MAX_CHANNEL = 0.12;
|
|
4130
|
+
var MAX_SPREAD = 0.04;
|
|
4131
|
+
function isNearBlack([r, g, b]) {
|
|
4132
|
+
const max = Math.max(r, g, b);
|
|
4133
|
+
return max <= MAX_CHANNEL && max - Math.min(r, g, b) <= MAX_SPREAD;
|
|
4134
|
+
}
|
|
4135
|
+
var WS = new Set([0, 9, 10, 12, 13, 32]);
|
|
4136
|
+
var DELIM = new Set("()<>[]{}/%".split("").map((c) => c.charCodeAt(0)));
|
|
4137
|
+
function* tokenize(s) {
|
|
4138
|
+
let i = 0;
|
|
4139
|
+
const n = s.length;
|
|
4140
|
+
while (i < n) {
|
|
4141
|
+
const c = s.charCodeAt(i);
|
|
4142
|
+
if (WS.has(c)) {
|
|
4143
|
+
i++;
|
|
4144
|
+
continue;
|
|
4145
|
+
}
|
|
4146
|
+
const start = i;
|
|
4147
|
+
if (c === 37) {
|
|
4148
|
+
while (i < n && s[i] !== `
|
|
4149
|
+
` && s[i] !== "\r")
|
|
4150
|
+
i++;
|
|
4151
|
+
continue;
|
|
4152
|
+
}
|
|
4153
|
+
if (c === 40) {
|
|
4154
|
+
let depth = 0;
|
|
4155
|
+
while (i < n) {
|
|
4156
|
+
const d = s[i++];
|
|
4157
|
+
if (d === "\\")
|
|
4158
|
+
i++;
|
|
4159
|
+
else if (d === "(")
|
|
4160
|
+
depth++;
|
|
4161
|
+
else if (d === ")" && --depth === 0)
|
|
4162
|
+
break;
|
|
4163
|
+
}
|
|
4164
|
+
yield { text: s.slice(start, i), start, end: i, op: false };
|
|
4165
|
+
continue;
|
|
4166
|
+
}
|
|
4167
|
+
if (c === 60 && s[i + 1] === "<") {
|
|
4168
|
+
i += 2;
|
|
4169
|
+
yield { text: "<<", start, end: i, op: false };
|
|
4170
|
+
continue;
|
|
4171
|
+
}
|
|
4172
|
+
if (c === 62 && s[i + 1] === ">") {
|
|
4173
|
+
i += 2;
|
|
4174
|
+
yield { text: ">>", start, end: i, op: false };
|
|
4175
|
+
continue;
|
|
4176
|
+
}
|
|
4177
|
+
if (c === 60) {
|
|
4178
|
+
while (i < n && s[i] !== ">")
|
|
4179
|
+
i++;
|
|
4180
|
+
i++;
|
|
4181
|
+
yield { text: s.slice(start, i), start, end: i, op: false };
|
|
4182
|
+
continue;
|
|
4183
|
+
}
|
|
4184
|
+
if (c === 91 || c === 93 || c === 62 || c === 123 || c === 125 || c === 41) {
|
|
4185
|
+
i++;
|
|
4186
|
+
yield { text: s[start], start, end: i, op: false };
|
|
4187
|
+
continue;
|
|
4188
|
+
}
|
|
4189
|
+
i++;
|
|
4190
|
+
while (i < n && !WS.has(s.charCodeAt(i)) && !DELIM.has(s.charCodeAt(i)))
|
|
4191
|
+
i++;
|
|
4192
|
+
const text = s.slice(start, i);
|
|
4193
|
+
const isOperand = c === 47 || /^[+-]?(\d+\.?\d*|\.\d+)$/.test(text);
|
|
4194
|
+
yield { text, start, end: i, op: !isOperand };
|
|
4195
|
+
if (text === "ID") {
|
|
4196
|
+
const m = /\sEI(?=\s|$)/g;
|
|
4197
|
+
m.lastIndex = i;
|
|
4198
|
+
const hit = m.exec(s);
|
|
4199
|
+
i = hit ? hit.index + hit[0].length : n;
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
function rewriteBlackText(src) {
|
|
4204
|
+
let fill = null;
|
|
4205
|
+
let stroke = null;
|
|
4206
|
+
const stack = [];
|
|
4207
|
+
let inText = false;
|
|
4208
|
+
let fillDirty = false;
|
|
4209
|
+
let strokeDirty = false;
|
|
4210
|
+
const edits = [];
|
|
4211
|
+
let operands = [];
|
|
4212
|
+
const rgbOf = () => {
|
|
4213
|
+
if (operands.length !== 3)
|
|
4214
|
+
return null;
|
|
4215
|
+
const v = operands.map((t) => Number(t.text));
|
|
4216
|
+
return v.some((x) => Number.isNaN(x)) ? null : { rgb: v, raw: operands.map((t) => t.text).join(" ") };
|
|
4217
|
+
};
|
|
4218
|
+
for (const t of tokenize(src)) {
|
|
4219
|
+
if (!t.op) {
|
|
4220
|
+
operands.push(t);
|
|
4221
|
+
continue;
|
|
4222
|
+
}
|
|
4223
|
+
const op = t.text;
|
|
4224
|
+
switch (op) {
|
|
4225
|
+
case "q":
|
|
4226
|
+
stack.push([fill, stroke]);
|
|
4227
|
+
break;
|
|
4228
|
+
case "Q": {
|
|
4229
|
+
const p = stack.pop();
|
|
4230
|
+
if (p)
|
|
4231
|
+
[fill, stroke] = p;
|
|
4232
|
+
break;
|
|
4233
|
+
}
|
|
4234
|
+
case "BT":
|
|
4235
|
+
inText = true;
|
|
4236
|
+
fillDirty = strokeDirty = false;
|
|
4237
|
+
{
|
|
4238
|
+
let ins = "";
|
|
4239
|
+
if (fill && isNearBlack(fill.rgb)) {
|
|
4240
|
+
ins += " 0 g";
|
|
4241
|
+
fillDirty = true;
|
|
4242
|
+
}
|
|
4243
|
+
if (stroke && isNearBlack(stroke.rgb)) {
|
|
4244
|
+
ins += " 0 G";
|
|
4245
|
+
strokeDirty = true;
|
|
4246
|
+
}
|
|
4247
|
+
if (ins)
|
|
4248
|
+
edits.push({ start: t.end, end: t.end, text: ins });
|
|
4249
|
+
}
|
|
4250
|
+
break;
|
|
4251
|
+
case "ET":
|
|
4252
|
+
if (inText) {
|
|
4253
|
+
let ins = "";
|
|
4254
|
+
if (fillDirty && fill)
|
|
4255
|
+
ins += `${fill.raw} rg `;
|
|
4256
|
+
if (strokeDirty && stroke)
|
|
4257
|
+
ins += `${stroke.raw} RG `;
|
|
4258
|
+
if (ins)
|
|
4259
|
+
edits.push({ start: t.start, end: t.start, text: ins });
|
|
4260
|
+
}
|
|
4261
|
+
inText = false;
|
|
4262
|
+
break;
|
|
4263
|
+
case "rg":
|
|
4264
|
+
case "RG": {
|
|
4265
|
+
const c = rgbOf();
|
|
4266
|
+
const isFill = op === "rg";
|
|
4267
|
+
if (isFill)
|
|
4268
|
+
fill = c;
|
|
4269
|
+
else
|
|
4270
|
+
stroke = c;
|
|
4271
|
+
if (inText && c && isNearBlack(c.rgb)) {
|
|
4272
|
+
edits.push({ start: operands[0].start, end: t.end, text: isFill ? "0 g" : "0 G" });
|
|
4273
|
+
if (isFill)
|
|
4274
|
+
fillDirty = true;
|
|
4275
|
+
else
|
|
4276
|
+
strokeDirty = true;
|
|
4277
|
+
} else if (inText) {
|
|
4278
|
+
if (isFill)
|
|
4279
|
+
fillDirty = false;
|
|
4280
|
+
else
|
|
4281
|
+
strokeDirty = false;
|
|
4282
|
+
}
|
|
4283
|
+
break;
|
|
4284
|
+
}
|
|
4285
|
+
case "g":
|
|
4286
|
+
case "k":
|
|
4287
|
+
case "sc":
|
|
4288
|
+
case "scn":
|
|
4289
|
+
case "cs":
|
|
4290
|
+
fill = null;
|
|
4291
|
+
if (inText)
|
|
4292
|
+
fillDirty = false;
|
|
4293
|
+
break;
|
|
4294
|
+
case "G":
|
|
4295
|
+
case "K":
|
|
4296
|
+
case "SC":
|
|
4297
|
+
case "SCN":
|
|
4298
|
+
case "CS":
|
|
4299
|
+
stroke = null;
|
|
4300
|
+
if (inText)
|
|
4301
|
+
strokeDirty = false;
|
|
4302
|
+
break;
|
|
4303
|
+
}
|
|
4304
|
+
operands = [];
|
|
4305
|
+
}
|
|
4306
|
+
return applyEdits(src, edits);
|
|
4307
|
+
}
|
|
4308
|
+
function applyEdits(s, edits) {
|
|
4309
|
+
if (!edits.length)
|
|
4310
|
+
return s;
|
|
4311
|
+
let out = "";
|
|
4312
|
+
let pos = 0;
|
|
4313
|
+
for (const e of edits) {
|
|
4314
|
+
out += s.slice(pos, e.start) + e.text;
|
|
4315
|
+
pos = e.end;
|
|
4316
|
+
}
|
|
4317
|
+
return out + s.slice(pos);
|
|
4318
|
+
}
|
|
4319
|
+
async function blackTextToGray(pdf) {
|
|
4320
|
+
const { PDFDocument, PDFName, PDFRawStream, PDFArray, PDFRef, decodePDFRawStream } = await import("pdf-lib");
|
|
4321
|
+
const doc = await PDFDocument.load(pdf, { updateMetadata: false });
|
|
4322
|
+
const ctx = doc.context;
|
|
4323
|
+
const SKIP = ["Filter", "DecodeParms", "Length"].map((k) => PDFName.of(k));
|
|
4324
|
+
let changed = false;
|
|
4325
|
+
const decode = (s) => {
|
|
4326
|
+
try {
|
|
4327
|
+
return Buffer.from(decodePDFRawStream(s).decode()).toString("latin1");
|
|
4328
|
+
} catch {
|
|
4329
|
+
return null;
|
|
4330
|
+
}
|
|
4331
|
+
};
|
|
4332
|
+
const replace = (ref, old, text) => {
|
|
4333
|
+
const entries = {};
|
|
4334
|
+
for (const [k, v] of old.dict.entries())
|
|
4335
|
+
if (!SKIP.includes(k))
|
|
4336
|
+
entries[k.decodeText()] = v;
|
|
4337
|
+
ctx.assign(ref, ctx.flateStream(Buffer.from(text, "latin1"), entries));
|
|
4338
|
+
changed = true;
|
|
4339
|
+
};
|
|
4340
|
+
for (const [ref, obj] of ctx.enumerateIndirectObjects()) {
|
|
4341
|
+
if (!(obj instanceof PDFRawStream))
|
|
4342
|
+
continue;
|
|
4343
|
+
if (obj.dict.get(PDFName.of("Subtype")) !== PDFName.of("Form"))
|
|
4344
|
+
continue;
|
|
4345
|
+
const text = decode(obj);
|
|
4346
|
+
if (text === null)
|
|
4347
|
+
continue;
|
|
4348
|
+
const next = rewriteBlackText(text);
|
|
4349
|
+
if (next !== text)
|
|
4350
|
+
replace(ref, obj, next);
|
|
4351
|
+
}
|
|
4352
|
+
for (const page of doc.getPages()) {
|
|
4353
|
+
const contents = page.node.Contents();
|
|
4354
|
+
const refs = [];
|
|
4355
|
+
if (contents instanceof PDFArray) {
|
|
4356
|
+
for (let i = 0;i < contents.size(); i++) {
|
|
4357
|
+
const r = contents.get(i);
|
|
4358
|
+
if (r instanceof PDFRef)
|
|
4359
|
+
refs.push(r);
|
|
4360
|
+
}
|
|
4361
|
+
} else if (contents instanceof PDFRawStream) {
|
|
4362
|
+
const r = page.node.get(PDFName.of("Contents"));
|
|
4363
|
+
if (r instanceof PDFRef)
|
|
4364
|
+
refs.push(r);
|
|
4365
|
+
}
|
|
4366
|
+
const streams = refs.map((r) => ctx.lookup(r));
|
|
4367
|
+
if (!refs.length || !streams.every((s) => s instanceof PDFRawStream))
|
|
4368
|
+
continue;
|
|
4369
|
+
const parts = streams.map((s) => decode(s));
|
|
4370
|
+
if (parts.some((p) => p === null))
|
|
4371
|
+
continue;
|
|
4372
|
+
const joined = parts.join(`
|
|
4373
|
+
`);
|
|
4374
|
+
const next = rewriteBlackText(joined);
|
|
4375
|
+
if (next === joined)
|
|
4376
|
+
continue;
|
|
4377
|
+
replace(refs[0], streams[0], next);
|
|
4378
|
+
page.node.set(PDFName.of("Contents"), refs[0]);
|
|
4379
|
+
}
|
|
4380
|
+
return changed ? doc.save({ updateFieldAppearances: false }) : pdf;
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4126
4383
|
// src/lib/validation-exec.ts
|
|
4127
4384
|
import { existsSync as existsSync10 } from "node:fs";
|
|
4128
4385
|
import { dirname as dirname4, join as join4, resolve as resolve8 } from "node:path";
|
|
@@ -8218,11 +8475,12 @@ class PdfOutput {
|
|
|
8218
8475
|
const rawPdf = pdfxMode ? path8.join(stage, "raw.pdf") : path8.resolve(pdfFile);
|
|
8219
8476
|
await fsp2.mkdir(path8.dirname(path8.resolve(pdfFile)), { recursive: true });
|
|
8220
8477
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
8221
|
-
const { buildNativePdf } = await import("./engine-
|
|
8478
|
+
const { buildNativePdf } = await import("./engine-k7xfrh7g.js");
|
|
8222
8479
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
8223
8480
|
title: config.title,
|
|
8224
8481
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
8225
8482
|
signature: config.print.signature,
|
|
8483
|
+
reserveLastPage: config.print.reserveLastPage,
|
|
8226
8484
|
allowShrink: opts.allowShrink
|
|
8227
8485
|
}, browser);
|
|
8228
8486
|
for (const d of engineDiagnostics)
|
|
@@ -8248,6 +8506,10 @@ class PdfOutput {
|
|
|
8248
8506
|
log.info("Stripping annotations for PDF/X compliance");
|
|
8249
8507
|
await stripAnnotations(rawPdf, stage);
|
|
8250
8508
|
}
|
|
8509
|
+
if (config.pdfx.blackText === "k-only") {
|
|
8510
|
+
log.info("Rewriting near-black text to K-only gray");
|
|
8511
|
+
await fsp2.writeFile(rawPdf, await blackTextToGray(await fsp2.readFile(rawPdf)));
|
|
8512
|
+
}
|
|
8251
8513
|
log.info(`Converting to CMYK PDF/X (${pdfxMode})`);
|
|
8252
8514
|
await convertToPdfxCmyk(rawPdf, path8.resolve(pdfFile), {
|
|
8253
8515
|
iccPath: effectiveIccPath,
|
package/dist/index.js
CHANGED
|
@@ -146,13 +146,13 @@ import {
|
|
|
146
146
|
testRemoteAccess,
|
|
147
147
|
updateExtensions,
|
|
148
148
|
validateProjectExtensions
|
|
149
|
-
} from "./index-
|
|
149
|
+
} from "./index-qpm8kr79.js";
|
|
150
150
|
import {
|
|
151
151
|
BUILD_DIAGNOSTIC_CODES
|
|
152
|
-
} from "./index-
|
|
152
|
+
} from "./index-6t5e811b.js";
|
|
153
153
|
import {
|
|
154
154
|
BuildError
|
|
155
|
-
} from "./index-
|
|
155
|
+
} from "./index-9fwjqyv5.js";
|
|
156
156
|
import {
|
|
157
157
|
DEFAULT_PRINT_OPTS,
|
|
158
158
|
assertMilestone,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* K-only black text for PDF/X (#331).
|
|
3
|
+
*
|
|
4
|
+
* PRINT-SPEC GAP: print vendors (DriveThruRPG) require body text to print on
|
|
5
|
+
* the black plate alone (100% K). Chromium writes every text fill as
|
|
6
|
+
* DeviceRGB (`r g b rg`), and the Ghostscript pass in `convertToPdfxCmyk`
|
|
7
|
+
* sends all RGB through the output ICC, so `#000` text lands as four-colour
|
|
8
|
+
* rich black (~47/42/40/100). Ghostscript keeps DeviceGray as gray, which
|
|
9
|
+
* maps to K only — so before that pass, near-black RGB that paints TEXT is
|
|
10
|
+
* rewritten to DeviceGray black. Standard PDF operators in, standard PDF
|
|
11
|
+
* operators out; nothing here is engine-private.
|
|
12
|
+
*
|
|
13
|
+
* REMOVAL TRIGGER: Chromium emitting DeviceGray/DeviceCMYK for black text, or
|
|
14
|
+
* Ghostscript gaining a text-only RGB-black → K mapping (`-dBlackText` and
|
|
15
|
+
* `Text_RGB None` do not do this, measured).
|
|
16
|
+
*
|
|
17
|
+
* What Chromium emits (measured): colour is set with one `r g b RG r g b rg`
|
|
18
|
+
* line directly BEFORE a `BT` block and simply stays in effect afterwards, so
|
|
19
|
+
* the same colour also paints whatever follows `ET` (rules, text-decoration
|
|
20
|
+
* rectangles). Those non-text fills must keep their colour. So the rewrite is
|
|
21
|
+
* scoped to `BT`…`ET`: on entering a text object whose inherited fill/stroke
|
|
22
|
+
* is near-black RGB we emit `0 g`/`0 G`, and at `ET` we put the original RGB
|
|
23
|
+
* back. A near-black `rg`/`RG` set inside the block is rewritten the same way.
|
|
24
|
+
* Page content streams and Form XObjects are both processed; a Form's
|
|
25
|
+
* inherited (pre-`Do`) colour is unknowable from its own stream, so only
|
|
26
|
+
* colours set inside the form are considered.
|
|
27
|
+
*
|
|
28
|
+
* Known limit: text-decoration rectangles (underline/strike) are `re f` after
|
|
29
|
+
* `ET`, i.e. non-text painting, and stay rich black.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Rewrite near-black RGB fill/stroke to DeviceGray black inside BT…ET of one
|
|
33
|
+
* content stream. Pure string → string; returns the input when nothing changed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function rewriteBlackText(src: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Apply {@link rewriteBlackText} to every page content stream and Form XObject
|
|
38
|
+
* of a PDF. Returns the input bytes untouched when nothing needed rewriting.
|
|
39
|
+
*/
|
|
40
|
+
export declare function blackTextToGray(pdf: Uint8Array): Promise<Uint8Array>;
|
package/dist/lib/engine.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface NativePdfOptions {
|
|
|
6
6
|
title?: string;
|
|
7
7
|
author?: string;
|
|
8
8
|
signature?: number;
|
|
9
|
+
reserveLastPage?: boolean;
|
|
9
10
|
/**
|
|
10
11
|
* Downgrade the engine's over-wide-content hard error to a warning +
|
|
11
12
|
* diagnostic. The engine's message tells the author to "pass allowShrink to
|
|
@@ -148,6 +148,12 @@
|
|
|
148
148
|
"type": "boolean",
|
|
149
149
|
"description": "Strip annotations (links, comments) during PDF/X conversion — PDF/X-1a does not permit them.",
|
|
150
150
|
"default": true
|
|
151
|
+
},
|
|
152
|
+
"blackText": {
|
|
153
|
+
"type": "string",
|
|
154
|
+
"description": "How near-black text is separated. `icc` converts it through the ICC profile (rich, four-colour black). `k-only` prints it on the black plate alone (100% K), which print vendors such as DriveThruRPG require for small text. Non-text black (rules, panels) is unaffected. Preset defaults: dtrpg `k-only`, book `icc`.",
|
|
155
|
+
"enum": ["icc", "k-only"],
|
|
156
|
+
"default": "icc"
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
},
|
|
@@ -7,16 +7,16 @@ import {
|
|
|
7
7
|
TARGET_IDS,
|
|
8
8
|
scaffoldExtension,
|
|
9
9
|
scaffoldProject
|
|
10
|
-
} from "./cli-
|
|
10
|
+
} from "./cli-1chdeea4.js";
|
|
11
11
|
import {
|
|
12
12
|
resolveGhostscript
|
|
13
|
-
} from "./cli-
|
|
13
|
+
} from "./cli-bscaare6.js";
|
|
14
14
|
import {
|
|
15
15
|
UsageError,
|
|
16
16
|
rejectExtraPositionals,
|
|
17
17
|
rejectUnknownFlags
|
|
18
|
-
} from "./cli-
|
|
19
|
-
import"./cli-
|
|
18
|
+
} from "./cli-7j9jvvs7.js";
|
|
19
|
+
import"./cli-kchpmn97.js";
|
|
20
20
|
import {
|
|
21
21
|
EXIT_CODES
|
|
22
22
|
} from "./cli-46ycxe6r.js";
|
|
@@ -2,16 +2,16 @@ import {
|
|
|
2
2
|
executeValidation,
|
|
3
3
|
publishTargetFor,
|
|
4
4
|
reportMissingTools
|
|
5
|
-
} from "./cli-
|
|
5
|
+
} from "./cli-1chdeea4.js";
|
|
6
6
|
import {
|
|
7
7
|
log
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-bscaare6.js";
|
|
9
9
|
import {
|
|
10
10
|
UsageError,
|
|
11
11
|
rejectExtraPositionals,
|
|
12
12
|
rejectUnknownFlags
|
|
13
|
-
} from "./cli-
|
|
14
|
-
import"./cli-
|
|
13
|
+
} from "./cli-7j9jvvs7.js";
|
|
14
|
+
import"./cli-kchpmn97.js";
|
|
15
15
|
import {
|
|
16
16
|
EXIT_CODES
|
|
17
17
|
} from "./cli-46ycxe6r.js";
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
runBuild,
|
|
7
7
|
splitOutPath,
|
|
8
8
|
startPreviewServer
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-1chdeea4.js";
|
|
10
10
|
import {
|
|
11
11
|
log
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-bscaare6.js";
|
|
13
13
|
import {
|
|
14
14
|
UsageError,
|
|
15
15
|
parseFormat,
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
rejectExtraPositionals,
|
|
18
18
|
rejectUnknownFlags,
|
|
19
19
|
resolvePort
|
|
20
|
-
} from "./cli-
|
|
21
|
-
import"./cli-
|
|
20
|
+
} from "./cli-7j9jvvs7.js";
|
|
21
|
+
import"./cli-kchpmn97.js";
|
|
22
22
|
import {
|
|
23
23
|
BuildError
|
|
24
24
|
} from "./cli-46ycxe6r.js";
|
|
@@ -11,17 +11,17 @@ import {
|
|
|
11
11
|
publishProviderFor,
|
|
12
12
|
resolvePublishFormat,
|
|
13
13
|
runPublish
|
|
14
|
-
} from "./cli-
|
|
14
|
+
} from "./cli-1chdeea4.js";
|
|
15
15
|
import {
|
|
16
16
|
FileTokenStore,
|
|
17
17
|
log
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-bscaare6.js";
|
|
19
19
|
import {
|
|
20
20
|
UsageError,
|
|
21
21
|
rejectExtraPositionals,
|
|
22
22
|
rejectUnknownFlags
|
|
23
|
-
} from "./cli-
|
|
24
|
-
import"./cli-
|
|
23
|
+
} from "./cli-7j9jvvs7.js";
|
|
24
|
+
import"./cli-kchpmn97.js";
|
|
25
25
|
import {
|
|
26
26
|
EXIT_CODES
|
|
27
27
|
} from "./cli-46ycxe6r.js";
|
|
@@ -115,6 +115,8 @@ export interface GutterpressManifest {
|
|
|
115
115
|
flavor?: "x1a" | "x3";
|
|
116
116
|
icc?: string;
|
|
117
117
|
stripAnnotations?: boolean;
|
|
118
|
+
/** `k-only` prints near-black text on the black plate alone (#331). */
|
|
119
|
+
blackText?: "icc" | "k-only";
|
|
118
120
|
};
|
|
119
121
|
/**
|
|
120
122
|
* Native-engine print-production options with no other manifest home
|
|
@@ -124,6 +126,12 @@ export interface GutterpressManifest {
|
|
|
124
126
|
print?: {
|
|
125
127
|
/** Pad the PDF with blank pages until pageCount is a multiple of this. */
|
|
126
128
|
signature?: number;
|
|
129
|
+
/**
|
|
130
|
+
* Guarantee the interior's final page is a blank page appended by the
|
|
131
|
+
* build (no content, no folio). Print-on-demand services such as
|
|
132
|
+
* DriveThruRPG reserve the last page for printer info.
|
|
133
|
+
*/
|
|
134
|
+
reserveLastPage?: boolean;
|
|
127
135
|
};
|
|
128
136
|
page?: {
|
|
129
137
|
width?: number;
|
|
@@ -227,9 +235,11 @@ export interface ResolvedConfig {
|
|
|
227
235
|
flavor: "x1a" | "x3";
|
|
228
236
|
icc: string;
|
|
229
237
|
stripAnnotations: boolean;
|
|
238
|
+
blackText: "icc" | "k-only";
|
|
230
239
|
};
|
|
231
240
|
print: {
|
|
232
241
|
signature: number;
|
|
242
|
+
reserveLastPage: boolean;
|
|
233
243
|
};
|
|
234
244
|
page: {
|
|
235
245
|
width: number;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
executeAndReport
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-1chdeea4.js";
|
|
4
4
|
import {
|
|
5
5
|
log
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-bscaare6.js";
|
|
7
7
|
import {
|
|
8
8
|
UsageError,
|
|
9
9
|
rejectExtraPositionals,
|
|
10
10
|
rejectUnknownFlags
|
|
11
|
-
} from "./cli-
|
|
12
|
-
import"./cli-
|
|
11
|
+
} from "./cli-7j9jvvs7.js";
|
|
12
|
+
import"./cli-kchpmn97.js";
|
|
13
13
|
import {
|
|
14
14
|
EXIT_CODES
|
|
15
15
|
} from "./cli-46ycxe6r.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gutterpress",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.15-alpha.1",
|
|
4
4
|
"description": "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
5
5
|
"author": "itlackey",
|
|
6
6
|
"license": "MPL-2.0",
|