gutterpress 0.10.0-alpha.4 → 0.10.0
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/README.md +2 -0
- package/dist/api/index.js +2 -2
- package/dist/{audit-k1vnfwvc.js → audit-cvarpa72.js} +4 -4
- package/dist/{build-hqmwgvdw.js → build-6r502chw.js} +6 -4
- package/dist/{cli-0r0tq16s.js → cli-149edp6b.js} +1 -1
- package/dist/{cli-ra0ed2xt.js → cli-cqtggsng.js} +1 -1
- package/dist/{cli-n25qycwz.js → cli-eq5naw4m.js} +222 -141
- package/dist/{cli-revgt4pr.js → cli-yzf38679.js} +1 -0
- package/dist/{cli-hp9r2pzt.js → cli-zfcryxg8.js} +327 -24
- package/dist/cli.js +16 -16
- package/dist/{doctor-dxms7ehm.js → doctor-akvxbtjb.js} +2 -2
- package/dist/engine/compiler/build.d.ts +1 -1
- package/dist/engine/shared/flush.d.ts +59 -0
- package/dist/engine/shared/margin-box-support.d.ts +24 -0
- package/dist/{engine-z4p9sr4h.js → engine-b159tbns.js} +2 -2
- package/dist/{engine-wa7y9av9.js → engine-ft4cr3ep.js} +1 -1
- package/dist/{gutterpress-agent-1ctgfz92.js → gutterpress-agent-cazqstr1.js} +49 -1
- package/dist/{gutterpress-viewer-cem7dmr5.js → gutterpress-viewer-te8g5grx.js} +191 -71
- package/dist/{index-05y3dnxq.js → index-ge7q9xj3.js} +330 -249
- package/dist/{index-xxg4zfrg.js → index-wq3r5pj7.js} +287 -23
- package/dist/{index-9tyq9kks.js → index-ycpvr0am.js} +47 -4
- package/dist/index.js +3 -3
- package/dist/lib/build-runner.d.ts +7 -0
- package/dist/lib/build-staging.d.ts +51 -0
- package/dist/lib/engine.d.ts +9 -0
- package/dist/lib/markdown/gutterpress-css.d.ts +9 -1
- package/dist/lib/markdown/markers.d.ts +51 -1
- package/dist/{lint-xjwm5ep8.js → lint-p2sw53d9.js} +4 -4
- package/dist/{new-kwdwpf0j.js → new-hvq0x91q.js} +4 -4
- package/dist/{plugin-rg4tnn96.js → plugin-pssmk0dx.js} +4 -4
- package/dist/{preflight-3127y25z.js → preflight-4j00yd0g.js} +4 -4
- package/dist/{preview-ncgfhqmw.js → preview-d4s6gk6p.js} +6 -5
- package/dist/{preview-interface-435cczt5.js → preview-interface-0ssk8bmm.js} +153 -6
- package/dist/{publish-pr0rwh6p.js → publish-tkc26en7.js} +4 -4
- package/dist/render.js +83 -7
- package/dist/{repair-8270smfw.js → repair-2va4w12t.js} +4 -4
- package/dist/{validate-54e17rae.js → validate-w9vfefrw.js} +4 -4
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
BuildError,
|
|
3
3
|
EXIT_CODES,
|
|
4
4
|
INSTALL_HINTS,
|
|
5
|
+
MARGIN_BOX_IGNORED_PROPERTIES,
|
|
5
6
|
RENDER_TIMEOUT_MS,
|
|
6
7
|
REQUIRED_MILESTONE,
|
|
7
8
|
closeBrowser,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
resolveChromiumExecutable,
|
|
17
18
|
run,
|
|
18
19
|
spawnCapture
|
|
19
|
-
} from "./index-
|
|
20
|
+
} from "./index-ycpvr0am.js";
|
|
20
21
|
import {
|
|
21
22
|
gitDirFor,
|
|
22
23
|
gitScopeFor,
|
|
@@ -731,6 +732,9 @@ function warn(env, line, type, message, marker) {
|
|
|
731
732
|
env.layoutWarnings = [];
|
|
732
733
|
env.layoutWarnings.push({ line, type, message, marker });
|
|
733
734
|
}
|
|
735
|
+
function proseEscapeHint(kind) {
|
|
736
|
+
return ` If this line is prose that happens to begin with "@${kind}" — a wrapped sentence, for` + ` instance — escape it as \\@${kind} or write it as \`@${kind}\` in backticks, and it stays text.`;
|
|
737
|
+
}
|
|
734
738
|
function addClasses(token, baseClass, extraClass) {
|
|
735
739
|
const cls = [];
|
|
736
740
|
if (baseClass)
|
|
@@ -792,14 +796,14 @@ function plugin(md, pluginOptions = {}) {
|
|
|
792
796
|
const unknown = parsed.__unknownTokens;
|
|
793
797
|
delete parsed.__unknownTokens;
|
|
794
798
|
for (const t of unknown) {
|
|
795
|
-
warn(state.env, startLine + 1, "unrecognized_marker_token", `@${parsed.kind}: "${t}" is not something a marker understands, so it was kept verbatim as a class name. Write a class as .my-class (or {.my-class}), an id as #my-id, and anything else as key=value
|
|
799
|
+
warn(state.env, startLine + 1, "unrecognized_marker_token", `@${parsed.kind}: "${t}" is not something a marker understands, so it was kept verbatim as a class name. Write a class as .my-class (or {.my-class}), an id as #my-id, and anything else as key=value.` + proseEscapeHint(parsed.kind), parsed);
|
|
796
800
|
}
|
|
797
801
|
}
|
|
798
802
|
if (parsed.__extraBareTokens) {
|
|
799
803
|
const { tokens, named } = parsed.__extraBareTokens;
|
|
800
804
|
delete parsed.__extraBareTokens;
|
|
801
805
|
const list = tokens.map((t) => `"${t}"`).join(", ");
|
|
802
|
-
warn(state.env, startLine + 1, "extra_bare_marker_token", named ? `@${parsed.kind}: only the first plain word (${list.split(", ")[0]}) is used as the name; the rest (${tokens.slice(1).map((t) => `"${t}"`).join(", ")}) became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.slice(1).join(" .")}.` : `@${parsed.kind}: ${list} are several plain words, but a marker has only one name slot — so NONE of them was used as the name and they all became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.join(" .")}
|
|
806
|
+
warn(state.env, startLine + 1, "extra_bare_marker_token", named ? `@${parsed.kind}: only the first plain word (${list.split(", ")[0]}) is used as the name; the rest (${tokens.slice(1).map((t) => `"${t}"`).join(", ")}) became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.slice(1).join(" .")}.` : `@${parsed.kind}: ${list} are several plain words, but a marker has only one name slot — so NONE of them was used as the name and they all became class names instead. Quote a name that contains spaces ("${tokens.join(" ")}"), or write classes as .${tokens.join(" .")}.` + proseEscapeHint(parsed.kind), parsed);
|
|
803
807
|
}
|
|
804
808
|
state.line = startLine + 1;
|
|
805
809
|
return true;
|
|
@@ -891,7 +895,12 @@ function plugin(md, pluginOptions = {}) {
|
|
|
891
895
|
t.meta = { line: meta.__line };
|
|
892
896
|
addClasses(t, "spread", meta.attrs && meta.attrs.class ? meta.attrs.class : "");
|
|
893
897
|
attachDataAttrs(t, "spread", meta.name, meta.attrs || {});
|
|
894
|
-
stack.open({
|
|
898
|
+
stack.open({
|
|
899
|
+
kind: "spread",
|
|
900
|
+
classes: meta.attrs && meta.attrs.class || "",
|
|
901
|
+
noPagesYet: true,
|
|
902
|
+
sawAnyPage: false
|
|
903
|
+
}, t);
|
|
895
904
|
}
|
|
896
905
|
function openPage(meta) {
|
|
897
906
|
const chapter = stack.get("chapter");
|
|
@@ -906,7 +915,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
906
915
|
const label = chapter ? chapter.label : "";
|
|
907
916
|
if (label)
|
|
908
917
|
t.attrSet("data-chapter-label", label);
|
|
909
|
-
stack.open({ kind: "page" }, t);
|
|
918
|
+
stack.open({ kind: "page", classes: explicit }, t);
|
|
910
919
|
if (label && !chapter.openerEmitted) {
|
|
911
920
|
const opener = new state.Token("html_block", "", 0);
|
|
912
921
|
opener.content = `<div class="chapter-opener" data-chapter-label="${escapeAttr(label)}">${escapeHtml(label)}</div>
|
|
@@ -929,13 +938,51 @@ function plugin(md, pluginOptions = {}) {
|
|
|
929
938
|
t.meta = { hasColumnBreak: false, line: meta.__line };
|
|
930
939
|
stack.open({
|
|
931
940
|
kind: "section",
|
|
941
|
+
classes: meta.attrs && meta.attrs.class || "",
|
|
942
|
+
sawContent: false,
|
|
932
943
|
meta: { name: meta.name || null, attrs: { ...meta.attrs || {} } },
|
|
933
944
|
openToken: t
|
|
934
945
|
}, t);
|
|
935
946
|
}
|
|
947
|
+
function breakHost() {
|
|
948
|
+
return stack.get("section") || stack.get("page") || stack.get("spread");
|
|
949
|
+
}
|
|
950
|
+
function gridClassOf(frame) {
|
|
951
|
+
if (!frame || !frame.classes)
|
|
952
|
+
return null;
|
|
953
|
+
return frame.classes.split(/\s+/).find((c) => c.startsWith("gp-grid-")) || null;
|
|
954
|
+
}
|
|
955
|
+
function warnBreakInsideGrid(kind, line, meta) {
|
|
956
|
+
const host = breakHost();
|
|
957
|
+
const gridClass = gridClassOf(host);
|
|
958
|
+
if (!gridClass)
|
|
959
|
+
return;
|
|
960
|
+
warn(state.env, line, "break_inside_grid", `@${kind} inside a grid container: the enclosing @${host.kind} carries .${gridClass}, so this break's <div> becomes a grid ITEM — it takes a cell of its own, corrupts the grid's placement, and print and the live preview then disagree about which page the content after it lands on. Move the break outside the grid @${host.kind}, or remove it — grid rows already flow and fragment across pages on their own.`, meta);
|
|
961
|
+
}
|
|
962
|
+
function warnIfEmptyDecoratedSection(closingKind, closingLine) {
|
|
963
|
+
const sec = stack.get("section");
|
|
964
|
+
if (!sec || sec.sawContent)
|
|
965
|
+
return;
|
|
966
|
+
const attrs = sec.meta && sec.meta.attrs || {};
|
|
967
|
+
const decorations = [];
|
|
968
|
+
for (const c of (attrs.class || "").split(/\s+/).filter(Boolean))
|
|
969
|
+
decorations.push(`.${c}`);
|
|
970
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
971
|
+
if (k === "class")
|
|
972
|
+
continue;
|
|
973
|
+
decorations.push(k === "id" ? `#${v}` : `${k}=${v}`);
|
|
974
|
+
}
|
|
975
|
+
if (!decorations.length)
|
|
976
|
+
return;
|
|
977
|
+
const openLine = sec.openToken && sec.openToken.meta && Number.isFinite(sec.openToken.meta.line) ? sec.openToken.meta.line : closingLine;
|
|
978
|
+
warn(state.env, openLine, "empty_section", `This @section (${decorations.join(" ")}) was closed by the @${closingKind} on line ${closingLine} with no content between the two markers, so its styling applies to an empty element and nothing prints the layout it asked for. Delete one of the two markers, or move the content that belongs inside the section between them.`, null);
|
|
979
|
+
}
|
|
936
980
|
for (let i = 0;i < state.tokens.length; i++) {
|
|
937
981
|
const tok = state.tokens[i];
|
|
938
982
|
if (tok.type !== "layout_marker") {
|
|
983
|
+
const openSectionFrame = stack.get("section");
|
|
984
|
+
if (openSectionFrame)
|
|
985
|
+
openSectionFrame.sawContent = true;
|
|
939
986
|
out.push(tok);
|
|
940
987
|
continue;
|
|
941
988
|
}
|
|
@@ -961,6 +1008,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
961
1008
|
continue;
|
|
962
1009
|
}
|
|
963
1010
|
if (kind === "section") {
|
|
1011
|
+
warnIfEmptyDecoratedSection("section", line);
|
|
964
1012
|
stack.close("section");
|
|
965
1013
|
const spread = stack.get("spread");
|
|
966
1014
|
if (spread && spread.noPagesYet) {
|
|
@@ -989,6 +1037,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
989
1037
|
continue;
|
|
990
1038
|
}
|
|
991
1039
|
if (kind === "page-break") {
|
|
1040
|
+
warnBreakInsideGrid("page-break", line, meta);
|
|
992
1041
|
const t = new state.Token("layout_page_break", "div", 0);
|
|
993
1042
|
t.meta = { line };
|
|
994
1043
|
t.attrSet("class", "gp-page-break");
|
|
@@ -997,6 +1046,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
997
1046
|
continue;
|
|
998
1047
|
}
|
|
999
1048
|
if (kind === "column-break") {
|
|
1049
|
+
warnBreakInsideGrid("column-break", line, meta);
|
|
1000
1050
|
const openSectionFrame = stack.get("section");
|
|
1001
1051
|
if (openSectionFrame && openSectionFrame.openToken && !openSectionFrame.openToken.meta.hasColumnBreak) {
|
|
1002
1052
|
const sectionCls = openSectionFrame.openToken.attrGet("class") || "";
|
|
@@ -1012,6 +1062,7 @@ function plugin(md, pluginOptions = {}) {
|
|
|
1012
1062
|
continue;
|
|
1013
1063
|
}
|
|
1014
1064
|
if (kind === "end-section") {
|
|
1065
|
+
warnIfEmptyDecoratedSection("end-section", line);
|
|
1015
1066
|
stack.close("section");
|
|
1016
1067
|
continue;
|
|
1017
1068
|
}
|
|
@@ -1099,7 +1150,7 @@ body { margin: 0; }
|
|
|
1099
1150
|
.gp-page-break { break-before: page; }
|
|
1100
1151
|
.page { break-before: page; }
|
|
1101
1152
|
.spread { break-before: page; }
|
|
1102
|
-
:where(.page, .spread) { position: relative; }
|
|
1153
|
+
:where(.page, .spread) { position: relative; display: flow-root; box-sizing: border-box; min-height: calc(var(--gp-content-h, 1px) - 1px); }
|
|
1103
1154
|
.gp-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }
|
|
1104
1155
|
|
|
1105
1156
|
:where(h1,h2,h3,h4,h5,h6) { break-after: avoid; }
|
|
@@ -1183,6 +1234,26 @@ var GUTTERPRESS_CSS = `
|
|
|
1183
1234
|
.gp-columns-2 { columns: 2; column-gap: var(--gp-column-gap, 1.5em); }
|
|
1184
1235
|
.gp-columns-3 { columns: 3; column-gap: var(--gp-column-gap, 1.5em); }
|
|
1185
1236
|
|
|
1237
|
+
/* grid runs — the SLOTTED counterpart to the column runs above. Grid places
|
|
1238
|
+
each child into the next cell, across then down (deterministic slots: card
|
|
1239
|
+
layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run
|
|
1240
|
+
down then across. Same neutral-primitive rationale as .gp-columns-*, and
|
|
1241
|
+
permanent vocabulary for the same reason: standard CSS Grid verbatim, no
|
|
1242
|
+
spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):
|
|
1243
|
+
grid rows fragment across sheets with EXACT print/viewer parity — 2- and
|
|
1244
|
+
3-col, unequal item heights, mid-row cuts, multi-sheet overflow,
|
|
1245
|
+
break-inside:avoid, gap geometry — so a grid taller than the page is safe,
|
|
1246
|
+
no fit-one-page constraint. Two things to know, not fix:
|
|
1247
|
+
- on a min-height page root (MARKER_CSS), default align-content
|
|
1248
|
+
stretches rows apart to fill the page — identically in both engines.
|
|
1249
|
+
Authors wanting packed rows set align-content: start.
|
|
1250
|
+
- a @page-break / @column-break marker DIRECTLY inside a grid container
|
|
1251
|
+
becomes a grid item and corrupts placement (the one measured parity
|
|
1252
|
+
break); markers.js diagnoses it (break_inside_grid).
|
|
1253
|
+
--gp-grid-gap is author-settable. */
|
|
1254
|
+
.gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }
|
|
1255
|
+
.gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }
|
|
1256
|
+
|
|
1186
1257
|
/* shape wrap — text follows the image's alpha silhouette instead of its
|
|
1187
1258
|
rectangular box. shape-outside only applies to floats, so this is inert
|
|
1188
1259
|
without .gp-left/.gp-right (and under .gp-pin, which un-floats). The
|
|
@@ -1245,8 +1316,14 @@ img.gp-shape {
|
|
|
1245
1316
|
opacity, filter, transform on it traps the negative layer inside).
|
|
1246
1317
|
Core keeps .page/.spread at 'position: relative; z-index: auto'
|
|
1247
1318
|
precisely so they are not stacking contexts.
|
|
1248
|
-
- a clipping ancestor (overflow other than visible)
|
|
1249
|
-
|
|
1319
|
+
- a clipping ancestor (overflow other than visible) — but only where
|
|
1320
|
+
the art actually overhangs that ancestor's clip box on a clipped
|
|
1321
|
+
axis: the overhang is cut off, the same mechanism that clips a
|
|
1322
|
+
.gp-bleed plate back to the wrapper's width. Clipping never reorders
|
|
1323
|
+
layers — within-bounds art under a clipping .page prints whole and
|
|
1324
|
+
still behind (measured; see the build audit's comment in
|
|
1325
|
+
engine/compiler/build.ts), and a static wrapper's overflow never
|
|
1326
|
+
binds an abspos .gp-pin at all.
|
|
1250
1327
|
The build-time engine.layer.trapped audit reports both against the live
|
|
1251
1328
|
ancestor chain. printsafe/page-containment is only an early source hint for
|
|
1252
1329
|
declarations written directly on .page/.spread. */
|
|
@@ -3873,7 +3950,7 @@ import git from "isomorphic-git";
|
|
|
3873
3950
|
// package.json
|
|
3874
3951
|
var package_default = {
|
|
3875
3952
|
name: "gutterpress",
|
|
3876
|
-
version: "0.10.0
|
|
3953
|
+
version: "0.10.0",
|
|
3877
3954
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
3878
3955
|
author: "itlackey",
|
|
3879
3956
|
license: "MPL-2.0",
|
|
@@ -4142,200 +4219,12 @@ async function writeBuildFingerprint(input) {
|
|
|
4142
4219
|
return outPath;
|
|
4143
4220
|
}
|
|
4144
4221
|
|
|
4145
|
-
// src/lib/missing-asset-placeholder.ts
|
|
4146
|
-
import { createHash as createHash5 } from "node:crypto";
|
|
4147
|
-
import { deflateSync } from "node:zlib";
|
|
4148
|
-
function placeholderOutputPath(missingOutputPath) {
|
|
4149
|
-
const hash = createHash5("sha256").update(missingOutputPath).digest("hex").slice(0, 16);
|
|
4150
|
-
return `assets/gutterpress-missing/${hash}.png`;
|
|
4151
|
-
}
|
|
4152
|
-
function decodeHtmlAttribute(value) {
|
|
4153
|
-
return value.replace(/"/g, '"').replace(/'|'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
4154
|
-
}
|
|
4155
|
-
function rewriteMissingImageReferences(html, replacements) {
|
|
4156
|
-
if (replacements.size === 0)
|
|
4157
|
-
return html;
|
|
4158
|
-
const replacementFor = (raw) => replacements.get(raw) ?? replacements.get(decodeHtmlAttribute(raw));
|
|
4159
|
-
const cssUrl = /url\(\s*(?:"([^"]*)"|'([^']*)'|"((?:(?!").)*)"|([^'"()\s]*))\s*\)/giy;
|
|
4160
|
-
const rewriteCssUrls = (css) => {
|
|
4161
|
-
let out2 = "";
|
|
4162
|
-
let copiedThrough = 0;
|
|
4163
|
-
let index = 0;
|
|
4164
|
-
while (index < css.length) {
|
|
4165
|
-
if (css.startsWith("/*", index)) {
|
|
4166
|
-
const end = css.indexOf("*/", index + 2);
|
|
4167
|
-
index = end < 0 ? css.length : end + 2;
|
|
4168
|
-
continue;
|
|
4169
|
-
}
|
|
4170
|
-
const char = css[index];
|
|
4171
|
-
if (char === '"' || char === "'") {
|
|
4172
|
-
const quote = char;
|
|
4173
|
-
index++;
|
|
4174
|
-
while (index < css.length) {
|
|
4175
|
-
if (css[index] === "\\") {
|
|
4176
|
-
index += 2;
|
|
4177
|
-
continue;
|
|
4178
|
-
}
|
|
4179
|
-
const current = css[index++];
|
|
4180
|
-
if (current === quote)
|
|
4181
|
-
break;
|
|
4182
|
-
}
|
|
4183
|
-
continue;
|
|
4184
|
-
}
|
|
4185
|
-
const previous = index > 0 ? css[index - 1] : "";
|
|
4186
|
-
if (!/[a-z0-9_-]/i.test(previous) && css.slice(index, index + 3).toLowerCase() === "url") {
|
|
4187
|
-
cssUrl.lastIndex = index;
|
|
4188
|
-
const match = cssUrl.exec(css);
|
|
4189
|
-
if (match) {
|
|
4190
|
-
const raw = match[1] ?? match[2] ?? match[3] ?? match[4] ?? "";
|
|
4191
|
-
const replacement = replacementFor(raw);
|
|
4192
|
-
if (replacement) {
|
|
4193
|
-
out2 += css.slice(copiedThrough, index);
|
|
4194
|
-
out2 += match[3] !== undefined ? `url("${replacement}")` : `url("${replacement}")`;
|
|
4195
|
-
index = cssUrl.lastIndex;
|
|
4196
|
-
copiedThrough = index;
|
|
4197
|
-
continue;
|
|
4198
|
-
}
|
|
4199
|
-
}
|
|
4200
|
-
}
|
|
4201
|
-
index++;
|
|
4202
|
-
}
|
|
4203
|
-
return copiedThrough === 0 ? css : out2 + css.slice(copiedThrough);
|
|
4204
|
-
};
|
|
4205
|
-
const rewriteTag = (tag) => {
|
|
4206
|
-
let out2 = tag;
|
|
4207
|
-
if (/^<img\b/i.test(tag)) {
|
|
4208
|
-
out2 = out2.replace(/(\s+src\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi, (whole, prefix, double, single, bare) => {
|
|
4209
|
-
const raw = double ?? single ?? bare ?? "";
|
|
4210
|
-
const replacement = replacementFor(raw);
|
|
4211
|
-
if (!replacement)
|
|
4212
|
-
return whole;
|
|
4213
|
-
if (double !== undefined)
|
|
4214
|
-
return `${prefix}"${replacement}"`;
|
|
4215
|
-
if (single !== undefined)
|
|
4216
|
-
return `${prefix}'${replacement}'`;
|
|
4217
|
-
return `${prefix}${replacement}`;
|
|
4218
|
-
});
|
|
4219
|
-
}
|
|
4220
|
-
if (/^<(?:img|source)\b/i.test(tag)) {
|
|
4221
|
-
out2 = out2.replace(/(\s+srcset\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi, (whole, prefix, double, single, bare) => {
|
|
4222
|
-
const value = double ?? single ?? bare ?? "";
|
|
4223
|
-
let rewritten = "";
|
|
4224
|
-
let copiedThrough = 0;
|
|
4225
|
-
let changed = false;
|
|
4226
|
-
for (const candidate of parseSrcsetUrlCandidates(value)) {
|
|
4227
|
-
const replacement = replacementFor(candidate.url);
|
|
4228
|
-
if (!replacement)
|
|
4229
|
-
continue;
|
|
4230
|
-
rewritten += value.slice(copiedThrough, candidate.start) + replacement;
|
|
4231
|
-
copiedThrough = candidate.end;
|
|
4232
|
-
changed = true;
|
|
4233
|
-
}
|
|
4234
|
-
if (!changed)
|
|
4235
|
-
return whole;
|
|
4236
|
-
rewritten += value.slice(copiedThrough);
|
|
4237
|
-
if (double !== undefined)
|
|
4238
|
-
return `${prefix}"${rewritten}"`;
|
|
4239
|
-
if (single !== undefined)
|
|
4240
|
-
return `${prefix}'${rewritten}'`;
|
|
4241
|
-
return `${prefix}${rewritten}`;
|
|
4242
|
-
});
|
|
4243
|
-
}
|
|
4244
|
-
out2 = out2.replace(/(\s+style\s*=\s*)(["'])(.*?)\2/gi, (whole, prefix, quote, value) => {
|
|
4245
|
-
const rewritten = rewriteCssUrls(value);
|
|
4246
|
-
return rewritten === value ? whole : `${prefix}${quote}${rewritten}${quote}`;
|
|
4247
|
-
});
|
|
4248
|
-
return out2;
|
|
4249
|
-
};
|
|
4250
|
-
const rewriteActiveHtml = (active) => active.replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g, (tag) => rewriteTag(tag));
|
|
4251
|
-
const protectedRegion = /<!--[\s\S]*?-->|<(script|style|pre|code|textarea)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
|
|
4252
|
-
let out = "";
|
|
4253
|
-
let last = 0;
|
|
4254
|
-
for (const match of html.matchAll(protectedRegion)) {
|
|
4255
|
-
out += rewriteActiveHtml(html.slice(last, match.index));
|
|
4256
|
-
if (match[1]?.toLowerCase() === "style") {
|
|
4257
|
-
const style = /^(<style\b[^>]*>)([\s\S]*)(<\/style\s*>)$/i.exec(match[0]);
|
|
4258
|
-
out += style ? `${rewriteTag(style[1])}${rewriteCssUrls(style[2])}${style[3]}` : match[0];
|
|
4259
|
-
} else {
|
|
4260
|
-
out += match[0];
|
|
4261
|
-
}
|
|
4262
|
-
last = (match.index ?? 0) + match[0].length;
|
|
4263
|
-
}
|
|
4264
|
-
out += rewriteActiveHtml(html.slice(last));
|
|
4265
|
-
return out;
|
|
4266
|
-
}
|
|
4267
|
-
function crc32(buf) {
|
|
4268
|
-
let c = ~0;
|
|
4269
|
-
for (let i = 0;i < buf.length; i++) {
|
|
4270
|
-
c ^= buf[i];
|
|
4271
|
-
for (let k = 0;k < 8; k++)
|
|
4272
|
-
c = c >>> 1 ^ 3988292384 & -(c & 1);
|
|
4273
|
-
}
|
|
4274
|
-
return ~c >>> 0;
|
|
4275
|
-
}
|
|
4276
|
-
function chunk(type, data) {
|
|
4277
|
-
const out = new Uint8Array(data.length + 12);
|
|
4278
|
-
const view = new DataView(out.buffer);
|
|
4279
|
-
view.setUint32(0, data.length);
|
|
4280
|
-
for (let i = 0;i < 4; i++)
|
|
4281
|
-
out[4 + i] = type.charCodeAt(i);
|
|
4282
|
-
out.set(data, 8);
|
|
4283
|
-
view.setUint32(data.length + 8, crc32(out.subarray(4, data.length + 8)));
|
|
4284
|
-
return out;
|
|
4285
|
-
}
|
|
4286
|
-
function placeholderPng(width = 640, height = 480, cell = 32) {
|
|
4287
|
-
const A = [217, 70, 239];
|
|
4288
|
-
const B = [26, 26, 26];
|
|
4289
|
-
const raw = new Uint8Array(height * (1 + width * 3));
|
|
4290
|
-
let p = 0;
|
|
4291
|
-
for (let y = 0;y < height; y++) {
|
|
4292
|
-
raw[p++] = 0;
|
|
4293
|
-
for (let x = 0;x < width; x++) {
|
|
4294
|
-
const c = ((x / cell | 0) + (y / cell | 0)) % 2 === 0 ? A : B;
|
|
4295
|
-
raw[p++] = c[0];
|
|
4296
|
-
raw[p++] = c[1];
|
|
4297
|
-
raw[p++] = c[2];
|
|
4298
|
-
}
|
|
4299
|
-
}
|
|
4300
|
-
const ihdr = new Uint8Array(13);
|
|
4301
|
-
const hv = new DataView(ihdr.buffer);
|
|
4302
|
-
hv.setUint32(0, width);
|
|
4303
|
-
hv.setUint32(4, height);
|
|
4304
|
-
ihdr[8] = 8;
|
|
4305
|
-
ihdr[9] = 2;
|
|
4306
|
-
const parts = [
|
|
4307
|
-
new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
4308
|
-
chunk("IHDR", ihdr),
|
|
4309
|
-
chunk("IDAT", new Uint8Array(deflateSync(raw))),
|
|
4310
|
-
chunk("IEND", new Uint8Array(0))
|
|
4311
|
-
];
|
|
4312
|
-
const total = parts.reduce((n, x) => n + x.length, 0);
|
|
4313
|
-
const png = new Uint8Array(total);
|
|
4314
|
-
let o = 0;
|
|
4315
|
-
for (const part of parts) {
|
|
4316
|
-
png.set(part, o);
|
|
4317
|
-
o += part.length;
|
|
4318
|
-
}
|
|
4319
|
-
return png;
|
|
4320
|
-
}
|
|
4321
|
-
|
|
4322
4222
|
// src/lib/lint-runner.ts
|
|
4323
4223
|
import { resolve as resolve4 } from "node:path";
|
|
4324
4224
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
4325
4225
|
|
|
4326
4226
|
// src/lib/printsafe.ts
|
|
4327
4227
|
import postcss2 from "postcss";
|
|
4328
|
-
|
|
4329
|
-
// src/engine/shared/margin-box-support.ts
|
|
4330
|
-
var MARGIN_BOX_IGNORED_PROPERTIES = new Set([
|
|
4331
|
-
"transform",
|
|
4332
|
-
"rotate",
|
|
4333
|
-
"translate",
|
|
4334
|
-
"scale",
|
|
4335
|
-
"box-shadow"
|
|
4336
|
-
]);
|
|
4337
|
-
|
|
4338
|
-
// src/lib/printsafe.ts
|
|
4339
4228
|
var ruleRemoteUrls = "printsafe/no-remote-urls";
|
|
4340
4229
|
var ruleRiskyProps = "printsafe/no-risky-print-effects";
|
|
4341
4230
|
var ruleSyntax = "printsafe/syntax-error";
|
|
@@ -7549,6 +7438,185 @@ function computeGates(format, opts, config) {
|
|
|
7549
7438
|
import path6 from "node:path";
|
|
7550
7439
|
import os from "node:os";
|
|
7551
7440
|
import fsp from "node:fs/promises";
|
|
7441
|
+
|
|
7442
|
+
// src/lib/missing-asset-placeholder.ts
|
|
7443
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
7444
|
+
import { deflateSync } from "node:zlib";
|
|
7445
|
+
function placeholderOutputPath(missingOutputPath) {
|
|
7446
|
+
const hash = createHash5("sha256").update(missingOutputPath).digest("hex").slice(0, 16);
|
|
7447
|
+
return `assets/gutterpress-missing/${hash}.png`;
|
|
7448
|
+
}
|
|
7449
|
+
function decodeHtmlAttribute(value) {
|
|
7450
|
+
return value.replace(/"/g, '"').replace(/'|'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
7451
|
+
}
|
|
7452
|
+
function rewriteMissingImageReferences(html, replacements) {
|
|
7453
|
+
if (replacements.size === 0)
|
|
7454
|
+
return html;
|
|
7455
|
+
const replacementFor = (raw) => replacements.get(raw) ?? replacements.get(decodeHtmlAttribute(raw));
|
|
7456
|
+
const cssUrl = /url\(\s*(?:"([^"]*)"|'([^']*)'|"((?:(?!").)*)"|([^'"()\s]*))\s*\)/giy;
|
|
7457
|
+
const rewriteCssUrls = (css) => {
|
|
7458
|
+
let out2 = "";
|
|
7459
|
+
let copiedThrough = 0;
|
|
7460
|
+
let index = 0;
|
|
7461
|
+
while (index < css.length) {
|
|
7462
|
+
if (css.startsWith("/*", index)) {
|
|
7463
|
+
const end = css.indexOf("*/", index + 2);
|
|
7464
|
+
index = end < 0 ? css.length : end + 2;
|
|
7465
|
+
continue;
|
|
7466
|
+
}
|
|
7467
|
+
const char = css[index];
|
|
7468
|
+
if (char === '"' || char === "'") {
|
|
7469
|
+
const quote = char;
|
|
7470
|
+
index++;
|
|
7471
|
+
while (index < css.length) {
|
|
7472
|
+
if (css[index] === "\\") {
|
|
7473
|
+
index += 2;
|
|
7474
|
+
continue;
|
|
7475
|
+
}
|
|
7476
|
+
const current = css[index++];
|
|
7477
|
+
if (current === quote)
|
|
7478
|
+
break;
|
|
7479
|
+
}
|
|
7480
|
+
continue;
|
|
7481
|
+
}
|
|
7482
|
+
const previous = index > 0 ? css[index - 1] : "";
|
|
7483
|
+
if (!/[a-z0-9_-]/i.test(previous) && css.slice(index, index + 3).toLowerCase() === "url") {
|
|
7484
|
+
cssUrl.lastIndex = index;
|
|
7485
|
+
const match = cssUrl.exec(css);
|
|
7486
|
+
if (match) {
|
|
7487
|
+
const raw = match[1] ?? match[2] ?? match[3] ?? match[4] ?? "";
|
|
7488
|
+
const replacement = replacementFor(raw);
|
|
7489
|
+
if (replacement) {
|
|
7490
|
+
out2 += css.slice(copiedThrough, index);
|
|
7491
|
+
out2 += match[3] !== undefined ? `url("${replacement}")` : `url("${replacement}")`;
|
|
7492
|
+
index = cssUrl.lastIndex;
|
|
7493
|
+
copiedThrough = index;
|
|
7494
|
+
continue;
|
|
7495
|
+
}
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7498
|
+
index++;
|
|
7499
|
+
}
|
|
7500
|
+
return copiedThrough === 0 ? css : out2 + css.slice(copiedThrough);
|
|
7501
|
+
};
|
|
7502
|
+
const rewriteTag = (tag) => {
|
|
7503
|
+
let out2 = tag;
|
|
7504
|
+
if (/^<img\b/i.test(tag)) {
|
|
7505
|
+
out2 = out2.replace(/(\s+src\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi, (whole, prefix, double, single, bare) => {
|
|
7506
|
+
const raw = double ?? single ?? bare ?? "";
|
|
7507
|
+
const replacement = replacementFor(raw);
|
|
7508
|
+
if (!replacement)
|
|
7509
|
+
return whole;
|
|
7510
|
+
if (double !== undefined)
|
|
7511
|
+
return `${prefix}"${replacement}"`;
|
|
7512
|
+
if (single !== undefined)
|
|
7513
|
+
return `${prefix}'${replacement}'`;
|
|
7514
|
+
return `${prefix}${replacement}`;
|
|
7515
|
+
});
|
|
7516
|
+
}
|
|
7517
|
+
if (/^<(?:img|source)\b/i.test(tag)) {
|
|
7518
|
+
out2 = out2.replace(/(\s+srcset\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi, (whole, prefix, double, single, bare) => {
|
|
7519
|
+
const value = double ?? single ?? bare ?? "";
|
|
7520
|
+
let rewritten = "";
|
|
7521
|
+
let copiedThrough = 0;
|
|
7522
|
+
let changed = false;
|
|
7523
|
+
for (const candidate of parseSrcsetUrlCandidates(value)) {
|
|
7524
|
+
const replacement = replacementFor(candidate.url);
|
|
7525
|
+
if (!replacement)
|
|
7526
|
+
continue;
|
|
7527
|
+
rewritten += value.slice(copiedThrough, candidate.start) + replacement;
|
|
7528
|
+
copiedThrough = candidate.end;
|
|
7529
|
+
changed = true;
|
|
7530
|
+
}
|
|
7531
|
+
if (!changed)
|
|
7532
|
+
return whole;
|
|
7533
|
+
rewritten += value.slice(copiedThrough);
|
|
7534
|
+
if (double !== undefined)
|
|
7535
|
+
return `${prefix}"${rewritten}"`;
|
|
7536
|
+
if (single !== undefined)
|
|
7537
|
+
return `${prefix}'${rewritten}'`;
|
|
7538
|
+
return `${prefix}${rewritten}`;
|
|
7539
|
+
});
|
|
7540
|
+
}
|
|
7541
|
+
out2 = out2.replace(/(\s+style\s*=\s*)(["'])(.*?)\2/gi, (whole, prefix, quote, value) => {
|
|
7542
|
+
const rewritten = rewriteCssUrls(value);
|
|
7543
|
+
return rewritten === value ? whole : `${prefix}${quote}${rewritten}${quote}`;
|
|
7544
|
+
});
|
|
7545
|
+
return out2;
|
|
7546
|
+
};
|
|
7547
|
+
const rewriteActiveHtml = (active) => active.replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g, (tag) => rewriteTag(tag));
|
|
7548
|
+
const protectedRegion = /<!--[\s\S]*?-->|<(script|style|pre|code|textarea)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
|
|
7549
|
+
let out = "";
|
|
7550
|
+
let last = 0;
|
|
7551
|
+
for (const match of html.matchAll(protectedRegion)) {
|
|
7552
|
+
out += rewriteActiveHtml(html.slice(last, match.index));
|
|
7553
|
+
if (match[1]?.toLowerCase() === "style") {
|
|
7554
|
+
const style = /^(<style\b[^>]*>)([\s\S]*)(<\/style\s*>)$/i.exec(match[0]);
|
|
7555
|
+
out += style ? `${rewriteTag(style[1])}${rewriteCssUrls(style[2])}${style[3]}` : match[0];
|
|
7556
|
+
} else {
|
|
7557
|
+
out += match[0];
|
|
7558
|
+
}
|
|
7559
|
+
last = (match.index ?? 0) + match[0].length;
|
|
7560
|
+
}
|
|
7561
|
+
out += rewriteActiveHtml(html.slice(last));
|
|
7562
|
+
return out;
|
|
7563
|
+
}
|
|
7564
|
+
function crc32(buf) {
|
|
7565
|
+
let c = ~0;
|
|
7566
|
+
for (let i = 0;i < buf.length; i++) {
|
|
7567
|
+
c ^= buf[i];
|
|
7568
|
+
for (let k = 0;k < 8; k++)
|
|
7569
|
+
c = c >>> 1 ^ 3988292384 & -(c & 1);
|
|
7570
|
+
}
|
|
7571
|
+
return ~c >>> 0;
|
|
7572
|
+
}
|
|
7573
|
+
function chunk(type, data) {
|
|
7574
|
+
const out = new Uint8Array(data.length + 12);
|
|
7575
|
+
const view = new DataView(out.buffer);
|
|
7576
|
+
view.setUint32(0, data.length);
|
|
7577
|
+
for (let i = 0;i < 4; i++)
|
|
7578
|
+
out[4 + i] = type.charCodeAt(i);
|
|
7579
|
+
out.set(data, 8);
|
|
7580
|
+
view.setUint32(data.length + 8, crc32(out.subarray(4, data.length + 8)));
|
|
7581
|
+
return out;
|
|
7582
|
+
}
|
|
7583
|
+
function placeholderPng(width = 640, height = 480, cell = 32) {
|
|
7584
|
+
const A = [217, 70, 239];
|
|
7585
|
+
const B = [26, 26, 26];
|
|
7586
|
+
const raw = new Uint8Array(height * (1 + width * 3));
|
|
7587
|
+
let p = 0;
|
|
7588
|
+
for (let y = 0;y < height; y++) {
|
|
7589
|
+
raw[p++] = 0;
|
|
7590
|
+
for (let x = 0;x < width; x++) {
|
|
7591
|
+
const c = ((x / cell | 0) + (y / cell | 0)) % 2 === 0 ? A : B;
|
|
7592
|
+
raw[p++] = c[0];
|
|
7593
|
+
raw[p++] = c[1];
|
|
7594
|
+
raw[p++] = c[2];
|
|
7595
|
+
}
|
|
7596
|
+
}
|
|
7597
|
+
const ihdr = new Uint8Array(13);
|
|
7598
|
+
const hv = new DataView(ihdr.buffer);
|
|
7599
|
+
hv.setUint32(0, width);
|
|
7600
|
+
hv.setUint32(4, height);
|
|
7601
|
+
ihdr[8] = 8;
|
|
7602
|
+
ihdr[9] = 2;
|
|
7603
|
+
const parts = [
|
|
7604
|
+
new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
7605
|
+
chunk("IHDR", ihdr),
|
|
7606
|
+
chunk("IDAT", new Uint8Array(deflateSync(raw))),
|
|
7607
|
+
chunk("IEND", new Uint8Array(0))
|
|
7608
|
+
];
|
|
7609
|
+
const total = parts.reduce((n, x) => n + x.length, 0);
|
|
7610
|
+
const png = new Uint8Array(total);
|
|
7611
|
+
let o = 0;
|
|
7612
|
+
for (const part of parts) {
|
|
7613
|
+
png.set(part, o);
|
|
7614
|
+
o += part.length;
|
|
7615
|
+
}
|
|
7616
|
+
return png;
|
|
7617
|
+
}
|
|
7618
|
+
|
|
7619
|
+
// src/lib/build-staging.ts
|
|
7552
7620
|
async function shipViewerHtml(htmlFile, outDir) {
|
|
7553
7621
|
await fsp.mkdir(path6.join(outDir, "engine"), { recursive: true });
|
|
7554
7622
|
await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"), path6.join(outDir, "engine/gutterpress-viewer.js"));
|
|
@@ -7557,6 +7625,50 @@ async function shipViewerHtml(htmlFile, outDir) {
|
|
|
7557
7625
|
const html = await fsp.readFile(htmlFile, "utf-8");
|
|
7558
7626
|
await fsp.writeFile(htmlFile, /<\/head>/i.test(html) ? html.replace(/<\/head>/i, tag + "</head>") : tag + html, "utf-8");
|
|
7559
7627
|
}
|
|
7628
|
+
async function stageBookAssets(options) {
|
|
7629
|
+
const { renderDir, outDir, htmlFile, imageRefs, cssAssets, onPlan } = options;
|
|
7630
|
+
const { copies: imageCopies, errors, destinations } = await planImageCopies(renderDir, imageRefs);
|
|
7631
|
+
const copies = [...cssAssets, ...imageCopies];
|
|
7632
|
+
onPlan?.({ unresolved: errors, copyCount: copies.length });
|
|
7633
|
+
const missingPlaceholders = copies.length ? await copyReferencedAssets(copies, outDir) : new Map;
|
|
7634
|
+
let staged = await fsp.readFile(htmlFile, "utf8");
|
|
7635
|
+
if (missingPlaceholders.size > 0) {
|
|
7636
|
+
const rewrites = new Map(missingPlaceholders);
|
|
7637
|
+
for (const [ref, dest] of destinations) {
|
|
7638
|
+
const placeholder = missingPlaceholders.get(dest);
|
|
7639
|
+
if (placeholder)
|
|
7640
|
+
rewrites.set(ref, placeholder);
|
|
7641
|
+
}
|
|
7642
|
+
staged = rewriteMissingImageReferences(staged, rewrites);
|
|
7643
|
+
await fsp.writeFile(htmlFile, staged, "utf8");
|
|
7644
|
+
}
|
|
7645
|
+
if (staged.includes("--gp-shape:")) {
|
|
7646
|
+
await fsp.writeFile(htmlFile, await inlineShapeUrls(staged, outDir), "utf8");
|
|
7647
|
+
}
|
|
7648
|
+
return { missing: [...missingPlaceholders.keys()].sort() };
|
|
7649
|
+
}
|
|
7650
|
+
async function copyReferencedAssets(copies, outDir) {
|
|
7651
|
+
const dirs = new Set(copies.map((c) => path6.dirname(path6.resolve(outDir, c.to))));
|
|
7652
|
+
await Promise.all([...dirs].map((d) => fsp.mkdir(d, { recursive: true })));
|
|
7653
|
+
const missing = new Map;
|
|
7654
|
+
await Promise.all(copies.map(async (c) => {
|
|
7655
|
+
const dest = path6.resolve(outDir, c.to);
|
|
7656
|
+
try {
|
|
7657
|
+
await fsp.copyFile(c.from, dest);
|
|
7658
|
+
} catch (err) {
|
|
7659
|
+
if (err?.code === "ENOENT") {
|
|
7660
|
+
const placeholder = placeholderOutputPath(c.to);
|
|
7661
|
+
const placeholderDest = path6.resolve(outDir, placeholder);
|
|
7662
|
+
await fsp.mkdir(path6.dirname(placeholderDest), { recursive: true });
|
|
7663
|
+
await fsp.writeFile(placeholderDest, placeholderPng());
|
|
7664
|
+
missing.set(c.to, placeholder);
|
|
7665
|
+
return;
|
|
7666
|
+
}
|
|
7667
|
+
throw new BuildError(`Could not copy asset ${c.from} → ${c.to}: ` + (err instanceof Error ? err.message : String(err)), 1);
|
|
7668
|
+
}
|
|
7669
|
+
}));
|
|
7670
|
+
return missing;
|
|
7671
|
+
}
|
|
7560
7672
|
async function createStageRoot() {
|
|
7561
7673
|
return fsp.mkdtemp(path6.join(os.tmpdir(), "gutterpress-stage-"));
|
|
7562
7674
|
}
|
|
@@ -7669,67 +7781,35 @@ async function renderBook(ctx) {
|
|
|
7669
7781
|
}
|
|
7670
7782
|
},
|
|
7671
7783
|
onImageRefs: (refs) => imageRefs.push(...refs),
|
|
7672
|
-
onCssAssets: (
|
|
7784
|
+
onCssAssets: (copies) => cssAssets.push(...copies),
|
|
7673
7785
|
onStyleWarnings: (warnings) => {
|
|
7674
7786
|
for (const w of warnings)
|
|
7675
7787
|
log.warn(` ${w}`);
|
|
7676
7788
|
}
|
|
7677
7789
|
});
|
|
7678
7790
|
log.success(`Wrote ${htmlFile}`);
|
|
7679
|
-
const {
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7791
|
+
const { missing } = await stageBookAssets({
|
|
7792
|
+
renderDir,
|
|
7793
|
+
outDir: workDir,
|
|
7794
|
+
htmlFile,
|
|
7795
|
+
imageRefs,
|
|
7796
|
+
cssAssets,
|
|
7797
|
+
onPlan: ({ unresolved, copyCount }) => {
|
|
7798
|
+
if (unresolved.length > 0) {
|
|
7799
|
+
throw new BuildError(`Cannot resolve ${unresolved.length} image reference(s):
|
|
7800
|
+
` + unresolved.map((e) => ` - ${e}`).join(`
|
|
7683
7801
|
`), 1);
|
|
7684
|
-
}
|
|
7685
|
-
const copies = [...cssAssets, ...imageCopies];
|
|
7686
|
-
let missingPlaceholders = new Map;
|
|
7687
|
-
if (copies.length > 0) {
|
|
7688
|
-
log.info(`Copying ${copies.length} referenced asset(s)`);
|
|
7689
|
-
missingPlaceholders = await copyReferencedAssets(copies, workDir);
|
|
7690
|
-
}
|
|
7691
|
-
let staged = await fsp2.readFile(htmlFile, "utf8");
|
|
7692
|
-
if (missingPlaceholders.size > 0) {
|
|
7693
|
-
const rewrites = new Map(missingPlaceholders);
|
|
7694
|
-
for (const [ref, dest] of destinations) {
|
|
7695
|
-
const placeholder = missingPlaceholders.get(dest);
|
|
7696
|
-
if (placeholder)
|
|
7697
|
-
rewrites.set(ref, placeholder);
|
|
7698
|
-
}
|
|
7699
|
-
staged = rewriteMissingImageReferences(staged, rewrites);
|
|
7700
|
-
await fsp2.writeFile(htmlFile, staged, "utf8");
|
|
7701
|
-
}
|
|
7702
|
-
if (staged.includes("--gp-shape:")) {
|
|
7703
|
-
await fsp2.writeFile(htmlFile, await inlineShapeUrls(staged, workDir), "utf8");
|
|
7704
|
-
}
|
|
7705
|
-
return htmlFile;
|
|
7706
|
-
}
|
|
7707
|
-
async function copyReferencedAssets(copies, outDir) {
|
|
7708
|
-
const dirs = new Set(copies.map((c) => path7.dirname(path7.resolve(outDir, c.to))));
|
|
7709
|
-
await Promise.all([...dirs].map((d) => fsp2.mkdir(d, { recursive: true })));
|
|
7710
|
-
const missing = new Map;
|
|
7711
|
-
await Promise.all(copies.map(async (c) => {
|
|
7712
|
-
const dest = path7.resolve(outDir, c.to);
|
|
7713
|
-
try {
|
|
7714
|
-
await fsp2.copyFile(c.from, dest);
|
|
7715
|
-
} catch (err) {
|
|
7716
|
-
if (err?.code === "ENOENT") {
|
|
7717
|
-
const placeholder = placeholderOutputPath(c.to);
|
|
7718
|
-
const placeholderDest = path7.resolve(outDir, placeholder);
|
|
7719
|
-
await fsp2.mkdir(path7.dirname(placeholderDest), { recursive: true });
|
|
7720
|
-
await fsp2.writeFile(placeholderDest, placeholderPng());
|
|
7721
|
-
missing.set(c.to, placeholder);
|
|
7722
|
-
return;
|
|
7723
7802
|
}
|
|
7724
|
-
|
|
7803
|
+
if (copyCount > 0)
|
|
7804
|
+
log.info(`Copying ${copyCount} referenced asset(s)`);
|
|
7725
7805
|
}
|
|
7726
|
-
})
|
|
7727
|
-
if (missing.
|
|
7728
|
-
log.warn(`${missing.
|
|
7729
|
-
for (const m of
|
|
7806
|
+
});
|
|
7807
|
+
if (missing.length > 0) {
|
|
7808
|
+
log.warn(`${missing.length} referenced image(s) do not exist — a magenta placeholder ` + `was substituted so the build could finish. Each one is a visible hole ` + `in the PDF:`);
|
|
7809
|
+
for (const m of missing)
|
|
7730
7810
|
log.warn(` missing: ${m}`);
|
|
7731
7811
|
}
|
|
7732
|
-
return
|
|
7812
|
+
return htmlFile;
|
|
7733
7813
|
}
|
|
7734
7814
|
async function resolveIccProfile(icc, manifestDir, explicitIccPath) {
|
|
7735
7815
|
const iccCandidates = path7.isAbsolute(icc) ? [icc] : [path7.resolve(manifestDir, icc), path7.resolve(icc)];
|
|
@@ -7827,11 +7907,12 @@ class PdfOutput {
|
|
|
7827
7907
|
const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
|
|
7828
7908
|
await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
|
|
7829
7909
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
7830
|
-
const { buildNativePdf } = await import("./engine-
|
|
7910
|
+
const { buildNativePdf } = await import("./engine-b159tbns.js");
|
|
7831
7911
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
7832
7912
|
title: config.title,
|
|
7833
7913
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
7834
|
-
signature: config.print.signature
|
|
7914
|
+
signature: config.print.signature,
|
|
7915
|
+
allowShrink: opts.allowShrink
|
|
7835
7916
|
}, opts.engineBrowser);
|
|
7836
7917
|
for (const d of engineDiagnostics)
|
|
7837
7918
|
log.warn(d.message);
|