gutterpress 0.11.20-alpha.3 → 0.11.20-alpha.5
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-a8ycsg80.js → build-8ffhvydf.js} +4 -4
- package/dist/{cli-hn9pk0hp.js → cli-553ce1d4.js} +1 -1
- package/dist/{cli-y9bcya7r.js → cli-6xym15vm.js} +2 -2
- package/dist/{cli-x4p99w8d.js → cli-cyej3p9v.js} +83 -41
- package/dist/{cli-9k2tb493.js → cli-sc5t7c74.js} +116 -43
- package/dist/cli.js +12 -12
- package/dist/{doctor-wbjkrcpc.js → doctor-gyes0hxt.js} +2 -2
- package/dist/engine/compiler/build.d.ts +1 -1
- package/dist/engine/shared/margin-box-support.d.ts +2 -2
- package/dist/engine/shared/pin-edges.d.ts +127 -0
- package/dist/engine/shared/print-area.d.ts +73 -0
- package/dist/{engine-yxnnp3hx.js → engine-4rkhv5ga.js} +1 -1
- package/dist/{engine-fdtv01a2.js → engine-5fw55dfb.js} +2 -2
- package/dist/{ext-pesyrs68.js → ext-x61yptd1.js} +4 -4
- package/dist/{gutterpress-agent-cazqstr1.js → gutterpress-agent-yvd8w8my.js} +79 -13
- package/dist/{gutterpress-viewer-1yse8cx3.js → gutterpress-viewer-w7njnkj7.js} +257 -71
- package/dist/{index-tjvrchxj.js → index-bta8hc2q.js} +225 -152
- package/dist/{index-zagagnwq.js → index-crys9951.js} +3 -3
- package/dist/{index-r87y7wzw.js → index-yw16cfm8.js} +82 -40
- package/dist/index.js +3 -3
- package/dist/lib/extension-manifest.d.ts +13 -0
- package/dist/lib/markdown/assemble.d.ts +13 -3
- package/dist/lib/markdown/gutterpress-css.d.ts +49 -33
- package/dist/lib/markdown/index.d.ts +5 -0
- package/dist/{manifest.schema-c9x8912a.json → manifest.schema-c4wfpg6g.json} +9 -3
- package/dist/{new-8yah8z91.js → new-87dqcxtn.js} +4 -4
- package/dist/{preflight-8ccx4kdc.js → preflight-nb4wndqf.js} +4 -4
- package/dist/preview/file-watcher.d.ts +5 -10
- package/dist/{preview-w7v9w36q.js → preview-7bcr5j2p.js} +4 -4
- package/dist/{publish-g5nh5myh.js → publish-4qfm5ch9.js} +4 -4
- package/dist/render.js +73 -13
- package/dist/schema/manifest.types.d.ts +25 -9
- package/dist/{validate-1yh59x0x.js → validate-dzht9qxk.js} +4 -4
- package/package.json +1 -1
- package/dist/engine/shared/flush.d.ts +0 -59
|
@@ -16,12 +16,12 @@ import {
|
|
|
16
16
|
stripAnnotations,
|
|
17
17
|
transparentPages,
|
|
18
18
|
warn
|
|
19
|
-
} from "./cli-
|
|
19
|
+
} from "./cli-6xym15vm.js";
|
|
20
20
|
import {
|
|
21
21
|
DEBOUNCE,
|
|
22
22
|
UsageError,
|
|
23
23
|
resolvePort
|
|
24
|
-
} from "./cli-
|
|
24
|
+
} from "./cli-553ce1d4.js";
|
|
25
25
|
import {
|
|
26
26
|
blackTextToGray
|
|
27
27
|
} from "./cli-ht1bcqdd.js";
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
getAssetPath,
|
|
31
31
|
getAssetsDir,
|
|
32
32
|
lastPageMode
|
|
33
|
-
} from "./cli-
|
|
33
|
+
} from "./cli-cyej3p9v.js";
|
|
34
34
|
import {
|
|
35
35
|
BuildError,
|
|
36
36
|
EXIT_CODES
|
|
@@ -147,7 +147,8 @@ var DTRPG_PRESET = {
|
|
|
147
147
|
tolerance: 0.5,
|
|
148
148
|
bleed: 9,
|
|
149
149
|
safe: 36,
|
|
150
|
-
artSafe: 18
|
|
150
|
+
artSafe: 18,
|
|
151
|
+
gutter: undefined
|
|
151
152
|
},
|
|
152
153
|
ink: {
|
|
153
154
|
maxTac: 240,
|
|
@@ -234,7 +235,8 @@ var BOOK_PRESET = {
|
|
|
234
235
|
tolerance: 0.5,
|
|
235
236
|
bleed: undefined,
|
|
236
237
|
safe: 18,
|
|
237
|
-
artSafe: undefined
|
|
238
|
+
artSafe: undefined,
|
|
239
|
+
gutter: undefined
|
|
238
240
|
},
|
|
239
241
|
ink: {
|
|
240
242
|
maxTac: 400,
|
|
@@ -592,7 +594,8 @@ function resolveCustomPage(c, m) {
|
|
|
592
594
|
tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5,
|
|
593
595
|
bleed: c.page?.bleed ?? m.page?.bleed,
|
|
594
596
|
safe: c.page?.safe ?? m.page?.safe,
|
|
595
|
-
artSafe: c.page?.artSafe ?? m.page?.artSafe
|
|
597
|
+
artSafe: c.page?.artSafe ?? m.page?.artSafe,
|
|
598
|
+
gutter: c.page?.gutter ?? m.page?.gutter
|
|
596
599
|
};
|
|
597
600
|
}
|
|
598
601
|
function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
@@ -701,6 +704,7 @@ function resolveDeclaredStyles(rawStyles, baseDir, subject) {
|
|
|
701
704
|
// src/lib/extension-manifest.ts
|
|
702
705
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
703
706
|
import path from "node:path";
|
|
707
|
+
import { satisfies, validRange } from "semver";
|
|
704
708
|
var EXTENSION_MANIFEST_FILENAME = "package.json";
|
|
705
709
|
var GUTTERPRESS_KEY = "gutterpress";
|
|
706
710
|
function object(value) {
|
|
@@ -724,6 +728,7 @@ function metaFromPackageJson(raw) {
|
|
|
724
728
|
...str(pkg.description) ? { description: str(pkg.description) } : {},
|
|
725
729
|
...keywords ? { keywords } : {},
|
|
726
730
|
...str(pkg.main) ? { main: str(pkg.main) } : {},
|
|
731
|
+
...str(object(pkg.engines)?.gutterpress) ? { gutterpressRange: str(object(pkg.engines)?.gutterpress) } : {},
|
|
727
732
|
..."preview" in gp ? { preview: str(gp.preview) ?? null } : {},
|
|
728
733
|
...Array.isArray(gp.styles) ? { styles: declaredList(gp.styles) } : {},
|
|
729
734
|
...str(gp.tokensFile) ? { tokensFile: str(gp.tokensFile) } : {},
|
|
@@ -760,6 +765,17 @@ function extensionCarries(meta) {
|
|
|
760
765
|
function pathEscapesFolder(rel) {
|
|
761
766
|
return path.isAbsolute(rel) || rel.split(/[\\/]/).includes("..");
|
|
762
767
|
}
|
|
768
|
+
function assertSupportsThisGutterpress(meta, ref) {
|
|
769
|
+
const range = meta.gutterpressRange;
|
|
770
|
+
if (range === undefined)
|
|
771
|
+
return;
|
|
772
|
+
if (!validRange(range)) {
|
|
773
|
+
throw new Error(`Extension "${ref}" has an invalid engines.gutterpress range "${range}".`);
|
|
774
|
+
}
|
|
775
|
+
if (!satisfies(PACKAGE_VERSION, range, { includePrerelease: true })) {
|
|
776
|
+
throw new Error(`Extension "${ref}" needs Gutterpress ${range} (its package.json engines.gutterpress); ` + `this is Gutterpress ${PACKAGE_VERSION}. Update Gutterpress, or pin an older version of the extension.`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
763
779
|
function assertExtensionContained(meta) {
|
|
764
780
|
if (meta.engineStyles !== undefined) {
|
|
765
781
|
throw new Error("The extension's metadata declares `engineStyles`, which was removed — " + "move its entries to the end of `styles`.");
|
|
@@ -2029,9 +2045,15 @@ var GUTTERPRESS_CSS = `
|
|
|
2029
2045
|
|
|
2030
2046
|
/* sizes — AFTER the flow positions so max-width:100% lifts the floats' 50%
|
|
2031
2047
|
cap at equal specificity */
|
|
2032
|
-
.gp-
|
|
2033
|
-
.gp-
|
|
2034
|
-
.gp-
|
|
2048
|
+
.gp-size-10 { width: 10%; max-width: 100%; }
|
|
2049
|
+
.gp-size-20 { width: 20%; max-width: 100%; }
|
|
2050
|
+
.gp-size-30 { width: 30%; max-width: 100%; }
|
|
2051
|
+
.gp-size-40 { width: 40%; max-width: 100%; }
|
|
2052
|
+
.gp-size-50 { width: 50%; max-width: 100%; }
|
|
2053
|
+
.gp-size-60 { width: 60%; max-width: 100%; }
|
|
2054
|
+
.gp-size-70 { width: 70%; max-width: 100%; }
|
|
2055
|
+
.gp-size-80 { width: 80%; max-width: 100%; }
|
|
2056
|
+
.gp-size-90 { width: 90%; max-width: 100%; }
|
|
2035
2057
|
|
|
2036
2058
|
/* float clearance presets — consumed by var(--gp-gap) in the float rules
|
|
2037
2059
|
above and by .gp-shape's shape-margin below; --gp-gap itself is
|
|
@@ -2089,10 +2111,16 @@ img.gp-shape {
|
|
|
2089
2111
|
}
|
|
2090
2112
|
|
|
2091
2113
|
/* pin — within the nearest positioned ancestor (.page/.spread, rule above).
|
|
2092
|
-
|
|
2114
|
+
The four insets are the pin box: 0 (the text block's edge) unless an
|
|
2115
|
+
engine set --gp-pin-<edge> on the page root for an edge a pin uses (the
|
|
2116
|
+
art-safe line). Non-auto insets and the explicit centers are
|
|
2117
|
+
load-bearing; see doctrine comment. */
|
|
2093
2118
|
.gp-pin {
|
|
2094
2119
|
position: absolute;
|
|
2095
|
-
|
|
2120
|
+
top: var(--gp-pin-top, 0px);
|
|
2121
|
+
right: var(--gp-pin-right, 0px);
|
|
2122
|
+
bottom: var(--gp-pin-bottom, 0px);
|
|
2123
|
+
left: var(--gp-pin-left, 0px);
|
|
2096
2124
|
align-self: center;
|
|
2097
2125
|
justify-self: center;
|
|
2098
2126
|
margin: 0;
|
|
@@ -2107,6 +2135,16 @@ img.gp-shape {
|
|
|
2107
2135
|
.gp-left { justify-self: start; }
|
|
2108
2136
|
.gp-right { justify-self: end; }
|
|
2109
2137
|
|
|
2138
|
+
/* the escape hatch — the paper's edge, past the printer's safe area: the
|
|
2139
|
+
pin box loses its art-safe insets. Set on the pin itself, so it beats the
|
|
2140
|
+
values the page root hands down. */
|
|
2141
|
+
.gp-pin.gp-flush {
|
|
2142
|
+
--gp-pin-top: 0px;
|
|
2143
|
+
--gp-pin-right: 0px;
|
|
2144
|
+
--gp-pin-bottom: 0px;
|
|
2145
|
+
--gp-pin-left: 0px;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2110
2148
|
/* wrapper-margin neutralization (same pattern and rationale as the
|
|
2111
2149
|
.gp-bleed paragraph-margin note in the doctrine comment; for pin, the
|
|
2112
2150
|
emptied paragraph would otherwise leave a phantom margin gap in flow) */
|
|
@@ -2213,9 +2251,15 @@ var GP_CLASSES = new Set([
|
|
|
2213
2251
|
"gp-center",
|
|
2214
2252
|
"gp-full",
|
|
2215
2253
|
"gp-bleed",
|
|
2216
|
-
"gp-
|
|
2217
|
-
"gp-
|
|
2218
|
-
"gp-
|
|
2254
|
+
"gp-size-10",
|
|
2255
|
+
"gp-size-20",
|
|
2256
|
+
"gp-size-30",
|
|
2257
|
+
"gp-size-40",
|
|
2258
|
+
"gp-size-50",
|
|
2259
|
+
"gp-size-60",
|
|
2260
|
+
"gp-size-70",
|
|
2261
|
+
"gp-size-80",
|
|
2262
|
+
"gp-size-90",
|
|
2219
2263
|
"gp-tight",
|
|
2220
2264
|
"gp-loose",
|
|
2221
2265
|
"gp-columns",
|
|
@@ -2232,6 +2276,7 @@ var GP_CLASSES = new Set([
|
|
|
2232
2276
|
"gp-pin",
|
|
2233
2277
|
"gp-top",
|
|
2234
2278
|
"gp-bottom",
|
|
2279
|
+
"gp-flush",
|
|
2235
2280
|
"gp-behind",
|
|
2236
2281
|
"gp-base",
|
|
2237
2282
|
"gp-raised",
|
|
@@ -2245,15 +2290,14 @@ var GP_CLASSES = new Set([
|
|
|
2245
2290
|
"gp-alert-caution",
|
|
2246
2291
|
"gp-page-break",
|
|
2247
2292
|
"gp-column-break",
|
|
2248
|
-
"gp-continued"
|
|
2249
|
-
"gp-flush"
|
|
2293
|
+
"gp-continued"
|
|
2250
2294
|
]);
|
|
2251
2295
|
|
|
2252
2296
|
// src/lib/markdown/gp-pin-scope.js
|
|
2253
|
-
function warn3(env, line, type, message,
|
|
2297
|
+
function warn3(env, line, type, message, severity) {
|
|
2254
2298
|
if (!env.layoutWarnings)
|
|
2255
2299
|
env.layoutWarnings = [];
|
|
2256
|
-
env.layoutWarnings.push({ line, type, message,
|
|
2300
|
+
env.layoutWarnings.push(severity ? { line, type, message, severity } : { line, type, message });
|
|
2257
2301
|
}
|
|
2258
2302
|
function editDistance2(a, b) {
|
|
2259
2303
|
let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
@@ -2283,6 +2327,21 @@ function hasPinClass(token) {
|
|
|
2283
2327
|
const cls = token.attrGet && token.attrGet("class");
|
|
2284
2328
|
return typeof cls === "string" && cls.split(/\s+/).includes("gp-pin");
|
|
2285
2329
|
}
|
|
2330
|
+
var PIN_EDGES = ["gp-top", "gp-right", "gp-bottom", "gp-left"];
|
|
2331
|
+
var FLUSH_ART_MSG = "`.gp-flush` puts this image on the paper's edge, past the printer's safe area. Printers trim up to 0.125in there, so keep it for art that is meant to bleed off the page.";
|
|
2332
|
+
function checkFlushArt(token, env, line) {
|
|
2333
|
+
if (token.type !== "image")
|
|
2334
|
+
return;
|
|
2335
|
+
const cls = token.attrGet && token.attrGet("class");
|
|
2336
|
+
if (typeof cls !== "string")
|
|
2337
|
+
return;
|
|
2338
|
+
const classes = cls.split(/\s+/);
|
|
2339
|
+
if (!classes.includes("gp-pin") || !classes.includes("gp-flush"))
|
|
2340
|
+
return;
|
|
2341
|
+
if (!classes.some((c) => PIN_EDGES.includes(c)))
|
|
2342
|
+
return;
|
|
2343
|
+
warn3(env, line, "flush_art", FLUSH_ART_MSG, "info");
|
|
2344
|
+
}
|
|
2286
2345
|
function elementNameFor(token) {
|
|
2287
2346
|
switch (token.type) {
|
|
2288
2347
|
case "layout_chapter_open":
|
|
@@ -2342,6 +2401,7 @@ function gpPinScope(md) {
|
|
|
2342
2401
|
if (token.type === "inline" && token.children) {
|
|
2343
2402
|
for (const child of token.children) {
|
|
2344
2403
|
checkUnknownGpClasses(child, state.env, line);
|
|
2404
|
+
checkFlushArt(child, state.env, line);
|
|
2345
2405
|
}
|
|
2346
2406
|
}
|
|
2347
2407
|
}
|
|
@@ -3369,6 +3429,7 @@ This folder still has a ${stale}. Gutterpress no longer reads it — ` + `descri
|
|
|
3369
3429
|
async function loadExtensionFromDir(extensionDir, config, pluginRef) {
|
|
3370
3430
|
const meta = await readExtensionMeta(extensionDir);
|
|
3371
3431
|
assertExtensionContained(meta);
|
|
3432
|
+
assertSupportsThisGutterpress(meta, pluginRef);
|
|
3372
3433
|
const resolved = resolveExtension(extensionDir, meta, `Plugin "${pluginRef}"`);
|
|
3373
3434
|
const extensionStyles = resolved.styles ?? [];
|
|
3374
3435
|
const name = config.name ?? meta.name ?? pluginRef;
|
|
@@ -3443,6 +3504,7 @@ async function loadPlugin(config, baseDir) {
|
|
|
3443
3504
|
if (loaded.packageDir) {
|
|
3444
3505
|
const meta = await readExtensionMeta(loaded.packageDir);
|
|
3445
3506
|
assertExtensionContained(meta);
|
|
3507
|
+
assertSupportsThisGutterpress(meta, pluginRef);
|
|
3446
3508
|
const resolved = resolveExtension(loaded.packageDir, { ...meta, main: undefined }, `Plugin "${pluginRef}"`);
|
|
3447
3509
|
if (resolved.markdown && loaded.entryPath && resolve3(resolved.markdown) !== resolve3(loaded.entryPath)) {
|
|
3448
3510
|
throw new Error(`its package.json declares gutterpress.markdown "${meta.markdown}" but the package entry is "${relative(loaded.packageDir, loaded.entryPath)}" — an npm extension's ` + "markdown-it plugin is its package entry, so declare that file or drop the field.");
|
|
@@ -3566,7 +3628,7 @@ import path3 from "node:path";
|
|
|
3566
3628
|
import { Transform } from "node:stream";
|
|
3567
3629
|
import { pipeline } from "node:stream/promises";
|
|
3568
3630
|
import { createGunzip } from "node:zlib";
|
|
3569
|
-
import { maxSatisfying, satisfies, validRange } from "semver";
|
|
3631
|
+
import { maxSatisfying, satisfies as satisfies2, validRange as validRange2 } from "semver";
|
|
3570
3632
|
|
|
3571
3633
|
// src/lib/fetch-timeout.ts
|
|
3572
3634
|
class FetchPassthroughError extends Error {
|
|
@@ -3807,7 +3869,7 @@ async function selectPackage(ctx, name, selector) {
|
|
|
3807
3869
|
version = selector;
|
|
3808
3870
|
} else if (typeof tags[selector] === "string") {
|
|
3809
3871
|
version = tags[selector];
|
|
3810
|
-
} else if (
|
|
3872
|
+
} else if (validRange2(selector)) {
|
|
3811
3873
|
version = maxSatisfying(Object.keys(versions), selector) ?? undefined;
|
|
3812
3874
|
}
|
|
3813
3875
|
if (!version || !isExactNpmVersion(version)) {
|
|
@@ -4025,7 +4087,7 @@ async function installPackage(ctx, name, selector, parentPackagePath, ancestors,
|
|
|
4025
4087
|
}
|
|
4026
4088
|
throw error2;
|
|
4027
4089
|
}
|
|
4028
|
-
const ancestor = ancestors.find((candidate) => candidate.name === name && (candidate.version === selected.version || (selector ?
|
|
4090
|
+
const ancestor = ancestors.find((candidate) => candidate.name === name && (candidate.version === selected.version || (selector ? satisfies2(candidate.version, selector) : false)));
|
|
4029
4091
|
if (ancestor)
|
|
4030
4092
|
return { path: ancestor.path };
|
|
4031
4093
|
if (ctx.records.length >= ctx.limits.totalPackages) {
|
|
@@ -4990,11 +5052,11 @@ var inches = (pt) => `${+(pt / 72).toFixed(2)}in`;
|
|
|
4990
5052
|
var check3 = {
|
|
4991
5053
|
id: "pdf.print.safe-margins",
|
|
4992
5054
|
name: "Safe margins",
|
|
4993
|
-
description: "Fails when text sits closer to the trim than `page.safe` (DriveThruRPG: 0.5in on every edge). Text only: art is judged in the preview's safe-area overlay, not here. Skipped when `page.safe` is not set",
|
|
5055
|
+
description: "Fails when text sits closer to the trim than `page.safe` (DriveThruRPG: 0.5in on every edge), or than `page.gutter` at the binding edge where one is set. Text only: art is judged in the preview's safe-area overlay, not here. Skipped when `page.safe` is not set",
|
|
4994
5056
|
category: "pdf",
|
|
4995
5057
|
phase: "post-build",
|
|
4996
5058
|
async run(ctx) {
|
|
4997
|
-
const { width, height, bleed = 0, safe = 0 } = ctx.config.page;
|
|
5059
|
+
const { width, height, bleed = 0, safe = 0, gutter = safe } = ctx.config.page;
|
|
4998
5060
|
if (!ctx.pdfPath || !safe)
|
|
4999
5061
|
return [];
|
|
5000
5062
|
const doc = await loadPdf(ctx.pdfPath);
|
|
@@ -5032,8 +5094,7 @@ var check3 = {
|
|
|
5032
5094
|
["bottom", Math.min(...ys) - trim.bottom],
|
|
5033
5095
|
["top", trim.top - Math.max(...ys)]
|
|
5034
5096
|
];
|
|
5035
|
-
const [edge,
|
|
5036
|
-
const shortfall = safe - gap;
|
|
5097
|
+
const [edge, shortfall] = gaps.map(([name, gap]) => [name, (name === "binding" ? gutter : safe) - gap]).reduce((worst, g) => g[1] > worst[1] ? g : worst);
|
|
5037
5098
|
if (shortfall <= TOLERANCE_PT)
|
|
5038
5099
|
continue;
|
|
5039
5100
|
const text = item.str.trim();
|
|
@@ -5041,7 +5102,7 @@ var check3 = {
|
|
|
5041
5102
|
severity: "error",
|
|
5042
5103
|
message: `Page ${pageNum}: "${text.slice(0, 40)}${text.length > 40 ? "…" : ""}" is ${inches(shortfall)} too close to the ${edge} edge.`,
|
|
5043
5104
|
file: ctx.pdfPath,
|
|
5044
|
-
detail: `${rule}: text must stay at least ${inches(safe)} inside the trim on every edge.`,
|
|
5105
|
+
detail: gutter === safe ? `${rule}: text must stay at least ${inches(safe)} inside the trim on every edge.` : `${rule}: text must stay at least ${inches(safe)} inside the trim, ${inches(gutter)} at the binding.`,
|
|
5045
5106
|
code: "text-outside-safe-margin",
|
|
5046
5107
|
data: { page: pageNum, edge, shortfallPt: +shortfall.toFixed(2) }
|
|
5047
5108
|
}));
|
|
@@ -8885,6 +8946,21 @@ function canonicalChapterId(p) {
|
|
|
8885
8946
|
return s;
|
|
8886
8947
|
}
|
|
8887
8948
|
|
|
8949
|
+
// src/engine/shared/print-area.ts
|
|
8950
|
+
function printAreaMeta(page) {
|
|
8951
|
+
const area = JSON.stringify({
|
|
8952
|
+
width: page.width,
|
|
8953
|
+
height: page.height,
|
|
8954
|
+
bleed: page.bleed ?? 0,
|
|
8955
|
+
safe: page.safe ?? 0,
|
|
8956
|
+
artSafe: page.artSafe,
|
|
8957
|
+
gutter: page.gutter,
|
|
8958
|
+
preset: page.preset,
|
|
8959
|
+
spec: page.spec
|
|
8960
|
+
});
|
|
8961
|
+
return `<meta name="gp-print-area" content='${area.replace(/&/g, "&").replace(/'/g, "'")}'>`;
|
|
8962
|
+
}
|
|
8963
|
+
|
|
8888
8964
|
// src/lib/markdown/assemble.ts
|
|
8889
8965
|
async function assembleBookHtml(opts) {
|
|
8890
8966
|
const title = opts.title ?? "Document";
|
|
@@ -8950,7 +9026,8 @@ ${projectCss.trim()}` : null
|
|
|
8950
9026
|
<html lang="en"${printAttrs}>
|
|
8951
9027
|
<head>
|
|
8952
9028
|
<meta charset="UTF-8">
|
|
8953
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"
|
|
9029
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">${opts.printArea ? `
|
|
9030
|
+
${printAreaMeta(opts.printArea)}` : ""}
|
|
8954
9031
|
<title>${title}</title>${preloadTags}
|
|
8955
9032
|
<style data-project-css>
|
|
8956
9033
|
${inlineCss}
|
|
@@ -9010,6 +9087,7 @@ ${css.trim()}
|
|
|
9010
9087
|
preloadImages: cssAssetCopies.map((c) => c.to),
|
|
9011
9088
|
title: opts.title,
|
|
9012
9089
|
print: opts.print,
|
|
9090
|
+
printArea: opts.printArea,
|
|
9013
9091
|
plugins: opts.plugins,
|
|
9014
9092
|
pluginCss,
|
|
9015
9093
|
annotateSourceChapters: opts.annotateSourceChapters,
|
|
@@ -9022,6 +9100,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
|
|
|
9022
9100
|
const html = await renderChapters(inputDir, {
|
|
9023
9101
|
title: opts.title,
|
|
9024
9102
|
print: opts.print,
|
|
9103
|
+
printArea: opts.printArea,
|
|
9025
9104
|
styles: opts.styles,
|
|
9026
9105
|
files: opts.files,
|
|
9027
9106
|
plugins: opts.plugins,
|
|
@@ -9899,6 +9978,7 @@ async function renderBook(ctx) {
|
|
|
9899
9978
|
const htmlFile = await renderChaptersToFile(renderDir, workDir, {
|
|
9900
9979
|
title: config.title,
|
|
9901
9980
|
print: config.print,
|
|
9981
|
+
printArea: { ...config.page, preset: config.preset, spec: config.spec },
|
|
9902
9982
|
styles: config.styles,
|
|
9903
9983
|
files: config.source.files,
|
|
9904
9984
|
plugins,
|
|
@@ -10051,7 +10131,7 @@ class PdfOutput {
|
|
|
10051
10131
|
const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
|
|
10052
10132
|
await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
|
|
10053
10133
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
10054
|
-
const { buildNativePdf } = await import("./engine-
|
|
10134
|
+
const { buildNativePdf } = await import("./engine-4rkhv5ga.js");
|
|
10055
10135
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
10056
10136
|
title: config.title,
|
|
10057
10137
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
@@ -10354,6 +10434,7 @@ async function renderPreviewBook(inputPath, config, opts) {
|
|
|
10354
10434
|
return renderChapters(inputPath, {
|
|
10355
10435
|
title: config.title ?? "Document",
|
|
10356
10436
|
print: config.print,
|
|
10437
|
+
printArea: config.printArea,
|
|
10357
10438
|
styles: config.styles,
|
|
10358
10439
|
files: opts.files,
|
|
10359
10440
|
plugins,
|
|
@@ -10368,19 +10449,8 @@ async function renderPreviewBook(inputPath, config, opts) {
|
|
|
10368
10449
|
}
|
|
10369
10450
|
});
|
|
10370
10451
|
}
|
|
10371
|
-
function injectPreviewScripts(html
|
|
10372
|
-
const
|
|
10373
|
-
width: page.width,
|
|
10374
|
-
height: page.height,
|
|
10375
|
-
bleed: page.bleed ?? 0,
|
|
10376
|
-
safe: page.safe ?? 0,
|
|
10377
|
-
artSafe: page.artSafe,
|
|
10378
|
-
preset: page.preset,
|
|
10379
|
-
spec: page.spec
|
|
10380
|
-
}) : "";
|
|
10381
|
-
const meta = area ? ` <meta name="gp-print-area" content='${area.replace(/&/g, "&").replace(/'/g, "'")}'>
|
|
10382
|
-
` : "";
|
|
10383
|
-
const scripts = meta + ` <script src="/engine/gutterpress-viewer.js"></script>
|
|
10452
|
+
function injectPreviewScripts(html) {
|
|
10453
|
+
const scripts = ` <script src="/engine/gutterpress-viewer.js"></script>
|
|
10384
10454
|
` + `<script src="/preview/scripts/preview-interface.js"></script>
|
|
10385
10455
|
` + `<script src="/preview/scripts/preview-bridge.js"></script>
|
|
10386
10456
|
`;
|
|
@@ -10395,7 +10465,10 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
|
|
|
10395
10465
|
}
|
|
10396
10466
|
const nextAssets = new Map;
|
|
10397
10467
|
const nextImageRefs = new Set;
|
|
10398
|
-
const html = await renderPreviewBook(inputPath,
|
|
10468
|
+
const html = await renderPreviewBook(inputPath, {
|
|
10469
|
+
...config,
|
|
10470
|
+
printArea: config.page && { ...config.page, preset: config.preset, spec: config.spec }
|
|
10471
|
+
}, {
|
|
10399
10472
|
files: config.source?.files ?? null,
|
|
10400
10473
|
onCssAssets: (copies) => {
|
|
10401
10474
|
for (const copy of copies)
|
|
@@ -10414,7 +10487,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
|
|
|
10414
10487
|
for (const ref of nextImageRefs)
|
|
10415
10488
|
imageRefs.add(ref);
|
|
10416
10489
|
}
|
|
10417
|
-
await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html
|
|
10490
|
+
await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
|
|
10418
10491
|
}
|
|
10419
10492
|
function isDotPathUnderRoot(candidatePath, root) {
|
|
10420
10493
|
const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
|
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-553ce1d4.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-87dqcxtn.js").then((m) => m.default),
|
|
22
|
+
preview: () => import("./preview-7bcr5j2p.js").then((m) => m.default),
|
|
23
|
+
build: () => import("./build-8ffhvydf.js").then((m) => m.default),
|
|
24
|
+
publish: () => import("./publish-4qfm5ch9.js").then((m) => m.default),
|
|
25
|
+
validate: () => import("./validate-dzht9qxk.js").then((m) => m.default),
|
|
26
|
+
preflight: () => import("./preflight-nb4wndqf.js").then((m) => m.default),
|
|
27
|
+
doctor: () => import("./doctor-gyes0hxt.js").then((m) => m.default),
|
|
28
|
+
ext: () => import("./ext-x61yptd1.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-87dqcxtn.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-nb4wndqf.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-x61yptd1.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-6xym15vm.js";
|
|
5
5
|
import {
|
|
6
6
|
UsageError,
|
|
7
7
|
rejectExtraPositionals,
|
|
8
8
|
rejectUnknownFlags
|
|
9
|
-
} from "./cli-
|
|
9
|
+
} from "./cli-553ce1d4.js";
|
|
10
10
|
import"./cli-46ycxe6r.js";
|
|
11
11
|
import"./cli-asap4t1z.js";
|
|
12
12
|
import"./cli-37x76zdn.js";
|
|
@@ -8,7 +8,7 @@ import { type PostprocessResult } from "./postprocess.ts";
|
|
|
8
8
|
* set is closed here so a surface's label table can be asserted complete
|
|
9
9
|
* against it rather than drifting silently as checks are added.
|
|
10
10
|
*/
|
|
11
|
-
export type BuildDiagnosticCode = "engine.width.overflow" | "engine.width.intrinsic" | "engine.xref.broken" | "engine.abspos.leak" | "engine.layer.trapped" | "engine.multicol.dead-column" | "engine.content.overheight" | "engine.image.low-dpi" | "engine.
|
|
11
|
+
export type BuildDiagnosticCode = "engine.width.overflow" | "engine.width.intrinsic" | "engine.xref.broken" | "engine.abspos.leak" | "engine.layer.trapped" | "engine.multicol.dead-column" | "engine.content.overheight" | "engine.image.low-dpi" | "engine.pin.margin-box" | "engine.page-background.unreferenced" | "engine.hyphenation.unsupported";
|
|
12
12
|
export declare const BUILD_DIAGNOSTIC_CODES: readonly BuildDiagnosticCode[];
|
|
13
13
|
export interface BuildDiagnostic {
|
|
14
14
|
code: BuildDiagnosticCode;
|
|
@@ -31,8 +31,8 @@ export declare function isIgnoredMarginBoxProperty(property: string): boolean;
|
|
|
31
31
|
* Geometry of each of the 16 margin boxes, per CSS Paged Media §5.3, in pt
|
|
32
32
|
* SHEET coordinates (origin at the sheet's top-left). One implementation for
|
|
33
33
|
* both furniture painters: the viewer's `drawMarginBoxes` overlays and the
|
|
34
|
-
* compiler's
|
|
35
|
-
* spot or preview and print disagree about where the folio sits.
|
|
34
|
+
* compiler's relocation of a pinned edge's furniture must place a box at the
|
|
35
|
+
* same spot or preview and print disagree about where the folio sits.
|
|
36
36
|
*/
|
|
37
37
|
export declare function marginBoxRectPt(name: string, g: {
|
|
38
38
|
width: number;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pinned-art policy — shared by the compiler and the viewer, like every rule
|
|
3
|
+
* that DECIDES something (see synthesis.ts's header).
|
|
4
|
+
*
|
|
5
|
+
* THE MODEL: a pin box the text never shares. A `.gp-pin` with an edge word
|
|
6
|
+
* (`.gp-top`, `.gp-right`, `.gp-bottom`, `.gp-left`) positions itself inside
|
|
7
|
+
* its page root (`.page`/`.spread`, `position: relative` in MARKER_CSS), and
|
|
8
|
+
* where it sits has no effect on the text:
|
|
9
|
+
*
|
|
10
|
+
* 1. The page MARGIN on each pinned edge is freed. Chromium's printable area
|
|
11
|
+
* IS the page area — MEASURED, a pinned box pulled into the margin with
|
|
12
|
+
* negative insets fragments onto the NEXT sheet, one moved there with a
|
|
13
|
+
* transform is clipped away entirely, and a margin box in a ~zero margin
|
|
14
|
+
* (1px, with padding/height compensation) simply does not render. So the
|
|
15
|
+
* only way art reaches the paper is page GEOMETRY, per page:
|
|
16
|
+
* - the COMPILER aliases the root's own page context under a generated
|
|
17
|
+
* name (verbatim rule copies — never a resolved flatten, which is the
|
|
18
|
+
* "re-implement the @page cascade" trap tier2's history records),
|
|
19
|
+
* zeroes the pinned margins on the alias, and assigns the root to it;
|
|
20
|
+
* - the VIEWER keeps the author's page context and frees the margins in
|
|
21
|
+
* the run's strip geometry, from these same functions.
|
|
22
|
+
* 2. The root gets that same margin back as PADDING (`box-decoration-break:
|
|
23
|
+
* clone`, so every fragment of a root that spans pages keeps it), added
|
|
24
|
+
* to any padding the root has of its own. Its content box is therefore
|
|
25
|
+
* the author's content box — same width, same height, same place, same
|
|
26
|
+
* line and page breaks — whatever is pinned where. That is the whole
|
|
27
|
+
* point: freeing the margin once let the text grow into it. (What the
|
|
28
|
+
* root paints itself — a background, a border — does grow with it: the
|
|
29
|
+
* root's box is the page area, and that area reaches the paper.)
|
|
30
|
+
* 3. The pin box is the root's padding box — which now reaches the paper on
|
|
31
|
+
* the pinned edges — inset by the printer's ART-SAFE line: `--gp-pin-<edge>`
|
|
32
|
+
* on the root, read by `.gp-pin`'s four insets (GUTTERPRESS_CSS). On the
|
|
33
|
+
* top, bottom and outside edge that is the bleed plus `page.artSafe`; at
|
|
34
|
+
* the binding edge `page.gutter` (no bleed there) — the manifest's print
|
|
35
|
+
* area (print-area.ts), so a book with none sets 0, the paper's edge.
|
|
36
|
+
* 4. `.gp-flush` on the pin zeroes the four properties in plain CSS: the
|
|
37
|
+
* opt-in escape hatch to the paper's edge (the markdown pipeline notes it).
|
|
38
|
+
* Nothing else here knows about it.
|
|
39
|
+
*
|
|
40
|
+
* Sides. Recto values are the defaults (`@page :right`'s margins, the
|
|
41
|
+
* binding on the left). Mirrored `:left`/`:right` margins agree on the content
|
|
42
|
+
* SIZE, so the viewer paginates identically either way; each engine corrects a
|
|
43
|
+
* verso root once it knows the side — the viewer after it measures its
|
|
44
|
+
* strips, the compiler in its synthesis step (`setPinGeometry`).
|
|
45
|
+
*
|
|
46
|
+
* Margin boxes live IN the margin, so a freed edge's furniture (folio, running
|
|
47
|
+
* head) cannot render natively there — MEASURED, no compensation trick
|
|
48
|
+
* survives. Both renderers therefore keep painting that furniture themselves
|
|
49
|
+
* at its ORIGINAL coordinates: the viewer already synthesizes all furniture as
|
|
50
|
+
* DOM; the compiler injects the freed edge's boxes into the page root as
|
|
51
|
+
* positioned elements with engine-resolved text. Nothing the author declared
|
|
52
|
+
* is lost — that is the contract this module's box map exists to keep.
|
|
53
|
+
*
|
|
54
|
+
* Everything the author's document gets is standard CSS: a padding, a custom
|
|
55
|
+
* property per edge, absolute insets, and (print only) a named page.
|
|
56
|
+
*/
|
|
57
|
+
import type { PageGeometry } from "./gcpm-extract.ts";
|
|
58
|
+
import type { PageSide, PrintArea } from "./print-area.ts";
|
|
59
|
+
export declare const PIN_EDGES: readonly ["top", "right", "bottom", "left"];
|
|
60
|
+
export type PinEdge = (typeof PIN_EDGES)[number];
|
|
61
|
+
/** Every pin that names an edge. */
|
|
62
|
+
export declare const EDGE_PIN_SELECTOR: string;
|
|
63
|
+
/** Minimal DOM shapes `pinEdgesIn` needs — keeps this module node-safe. */
|
|
64
|
+
interface PinElement {
|
|
65
|
+
/** an HTML element's containing block for abspos (`HTMLElement.offsetParent`) */
|
|
66
|
+
readonly offsetParent?: unknown;
|
|
67
|
+
readonly classList: {
|
|
68
|
+
contains(token: string): boolean;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
interface QueryRoot {
|
|
72
|
+
querySelectorAll(selector: string): ArrayLike<PinElement>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The edges the pins of page root `root` use, in canonical t/r/b/l order — a
|
|
76
|
+
* pin counts when `root` is its containing block (its `offsetParent`): a pin
|
|
77
|
+
* in a nested page root belongs to that root, and one a positioned card
|
|
78
|
+
* frames belongs to the card, so neither frees this root's margins. A pin
|
|
79
|
+
* without an edge word frees nothing and centres in the text block.
|
|
80
|
+
*/
|
|
81
|
+
export declare function pinEdgesIn(root: QueryRoot): PinEdge[];
|
|
82
|
+
/** Canonical short key for an edge set: "b", "rb", "trbl"… (t/r/b/l order). */
|
|
83
|
+
export declare function pinKey(edges: readonly PinEdge[]): string;
|
|
84
|
+
/**
|
|
85
|
+
* Generated page name for (author page context, edge set). The `gp--` double
|
|
86
|
+
* dash marks an engine-generated page (the `gp--blank` convention); these
|
|
87
|
+
* exist only in builds that actually contain an edge pin, which is why the
|
|
88
|
+
* width check may treat them as real author contexts.
|
|
89
|
+
*/
|
|
90
|
+
export declare function pinPageName(authorPage: string | undefined, edges: readonly PinEdge[]): string;
|
|
91
|
+
/** The page's margins with the pinned edges freed. */
|
|
92
|
+
export declare function flushMargins(margin: PageGeometry["margin"], edges: readonly PinEdge[]): PageGeometry["margin"];
|
|
93
|
+
/**
|
|
94
|
+
* The art-safe line's distance from the sheet edge on each pinned edge, in pt:
|
|
95
|
+
* the bleed plus `artSafe` on the top, bottom and outside edge, `gutter` on the
|
|
96
|
+
* binding edge (the left of a recto, the right of a verso), 0 on an edge the
|
|
97
|
+
* pins do not use (the pin box's edge is the text block's there). No print
|
|
98
|
+
* area means 0 everywhere: the paper's edge.
|
|
99
|
+
*/
|
|
100
|
+
export declare function pinInsets(area: Pick<PrintArea, "bleed" | "artSafe" | "gutter"> | null, side: PageSide, edges: readonly PinEdge[]): Record<PinEdge, number>;
|
|
101
|
+
/** No padding of the root's own. */
|
|
102
|
+
export declare const NO_PADDING: Readonly<Record<PinEdge, number>>;
|
|
103
|
+
/**
|
|
104
|
+
* A pin root's own declarations for one side, in px (pt × 96/72, like the
|
|
105
|
+
* strip variables): the freed margins back as padding, cloned onto every
|
|
106
|
+
* fragment, and the four inset properties. `margin` is the ORIGINAL margin of
|
|
107
|
+
* the page context the root lands on; `padding` is the root's own (author)
|
|
108
|
+
* padding in px, which the freed margin is added to rather than replacing,
|
|
109
|
+
* so a padded root's text stays put too. All four properties are set, so a
|
|
110
|
+
* pin never reads another root's values.
|
|
111
|
+
*/
|
|
112
|
+
export declare function pinRootStyle(margin: PageGeometry["margin"], area: Pick<PrintArea, "bleed" | "artSafe" | "gutter"> | null, side: PageSide, edges: readonly PinEdge[], padding?: Readonly<Record<PinEdge, number>>): Record<string, string>;
|
|
113
|
+
/** A computed style's four paddings, in px. */
|
|
114
|
+
export declare function paddingOf(cs: {
|
|
115
|
+
paddingTop: string;
|
|
116
|
+
paddingRight: string;
|
|
117
|
+
paddingBottom: string;
|
|
118
|
+
paddingLeft: string;
|
|
119
|
+
}): Record<PinEdge, number>;
|
|
120
|
+
/**
|
|
121
|
+
* Margin boxes that live (wholly or partly) in a freed edge's margin area —
|
|
122
|
+
* the set both renderers must keep painting themselves. A corner box belongs
|
|
123
|
+
* to BOTH of its edges: `bottom-left-corner` dies when either the bottom or
|
|
124
|
+
* the left margin is freed.
|
|
125
|
+
*/
|
|
126
|
+
export declare function marginBoxesOnEdges(edges: readonly PinEdge[]): string[];
|
|
127
|
+
export {};
|