gutterpress 0.10.1 → 0.10.2-alpha.2
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/{audit-p4e7v2m7.js → audit-qz3np6k3.js} +4 -4
- package/dist/{build-h0x2w036.js → build-41eds5kk.js} +5 -5
- package/dist/{cli-eewynen9.js → cli-c2yvktan.js} +88 -7
- package/dist/{cli-4n1p25df.js → cli-daahjr6z.js} +1 -1
- package/dist/{cli-yzf38679.js → cli-pfpb1t3h.js} +1 -1
- package/dist/{cli-bq7vkea9.js → cli-w3spkryk.js} +36 -19
- package/dist/{cli-n79ghfhs.js → cli-wef7qh62.js} +1 -1
- package/dist/cli.js +15 -15
- package/dist/{doctor-y2w1kcqf.js → doctor-dqmz9wr7.js} +2 -2
- package/dist/engine/compiler/build.d.ts +1 -1
- package/dist/engine/shared/margin-box-support.d.ts +18 -1
- package/dist/{engine-57vw8zpd.js → engine-36jts441.js} +1 -1
- package/dist/{engine-8kdwy0jz.js → engine-qg5tdaqv.js} +2 -2
- package/dist/{gutterpress-viewer-tma1qwz2.js → gutterpress-viewer-mfeccbys.js} +12 -1
- package/dist/{index-j6b6cees.js → index-5vfsbjwz.js} +35 -18
- package/dist/{index-p0ebr7dh.js → index-my3v298z.js} +75 -4
- package/dist/{index-7hrvksrb.js → index-wbm6p5j3.js} +16 -5
- package/dist/index.js +3 -3
- package/dist/lib/asset-inline.d.ts +9 -7
- package/dist/lib/build-runner.d.ts +6 -7
- package/dist/lib/cli-args.d.ts +4 -5
- package/dist/lib/engine.d.ts +3 -3
- package/dist/lib/markdown/assemble.d.ts +37 -0
- package/dist/lib/markdown/gutterpress-css.d.ts +5 -10
- package/dist/lib/markdown/markers.d.ts +4 -6
- package/dist/lib/markdown/source-range.d.ts +1 -1
- package/dist/lib/static-serve.d.ts +1 -2
- package/dist/{lint-7wss5p35.js → lint-67xx8zms.js} +4 -4
- package/dist/{manifest.schema-zxgxnbg7.json → manifest.schema-084rxtwp.json} +3 -3
- package/dist/{new-6gz60bbx.js → new-a4052xsg.js} +4 -4
- package/dist/{plugin-7awkmgq0.js → plugin-n4b7b0nx.js} +4 -4
- package/dist/{preflight-3tb7stqb.js → preflight-7201sv7c.js} +4 -4
- package/dist/preview/server-context.d.ts +6 -6
- package/dist/{preview-7a35z1v1.js → preview-ez7yfe8p.js} +5 -5
- package/dist/{preview-interface-2f5v96hd.js → preview-interface-9rwz21yw.js} +2 -2
- package/dist/{preview-shell-kyjq59d9.js → preview-shell-gqnc5xxe.js} +3 -5
- package/dist/{publish-dpx2cebg.js → publish-thxa0cz7.js} +4 -4
- package/dist/render.js +3 -1
- package/dist/schema/manifest.types.d.ts +4 -5
- package/dist/test-helpers/testkit.d.ts +31 -0
- package/dist/types.d.ts +2 -2
- package/dist/{validate-hbe05b26.js → validate-0tbsdxz2.js} +4 -4
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
resolveChromiumExecutable,
|
|
18
18
|
run,
|
|
19
19
|
spawnCapture
|
|
20
|
-
} from "./index-
|
|
20
|
+
} from "./index-wbm6p5j3.js";
|
|
21
21
|
import {
|
|
22
22
|
gitFs,
|
|
23
23
|
gitScopeFor,
|
|
@@ -503,10 +503,10 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
|
|
|
503
503
|
throw new UsageError(`Unknown engine "${String(requestedEngine)}". Expected: paged | native`);
|
|
504
504
|
}
|
|
505
505
|
if (requestedEngine === "paged") {
|
|
506
|
-
warnOnce("engine-paged-removed", "[gutterpress]
|
|
506
|
+
warnOnce("engine-paged-removed", "[gutterpress] The native engine is the only engine. " + '"engine: paged" is ignored — building natively.');
|
|
507
507
|
}
|
|
508
508
|
if ((m.engineStyles?.paged?.length ?? 0) > 0) {
|
|
509
|
-
warnOnce("engine-styles-paged-removed", "[gutterpress] engineStyles.paged is ignored —
|
|
509
|
+
warnOnce("engine-styles-paged-removed", "[gutterpress] engineStyles.paged is ignored — the native engine is the only engine.");
|
|
510
510
|
}
|
|
511
511
|
const engine = "native";
|
|
512
512
|
const rawPlugins = c.plugins ?? m.plugins ?? [];
|
|
@@ -2002,12 +2002,14 @@ ${projectCss.trim()}` : null
|
|
|
2002
2002
|
].filter(Boolean).join(`
|
|
2003
2003
|
|
|
2004
2004
|
`);
|
|
2005
|
+
const preloadTags = (opts.preloadImages ?? []).map((href) => `
|
|
2006
|
+
<link rel="preload" as="image" href="${href}">`).join("");
|
|
2005
2007
|
return `<!DOCTYPE html>
|
|
2006
2008
|
<html lang="en">
|
|
2007
2009
|
<head>
|
|
2008
2010
|
<meta charset="UTF-8">
|
|
2009
2011
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2010
|
-
<title>${title}</title
|
|
2012
|
+
<title>${title}</title>${preloadTags}
|
|
2011
2013
|
<style data-project-css>
|
|
2012
2014
|
${inlineCss}
|
|
2013
2015
|
</style>
|
|
@@ -2127,7 +2129,6 @@ import { readFile as readFile2 } from "node:fs/promises";
|
|
|
2127
2129
|
import path2 from "node:path";
|
|
2128
2130
|
import postcss from "postcss";
|
|
2129
2131
|
var FONT_EXTS = new Set([".woff2", ".woff", ".ttf", ".otf"]);
|
|
2130
|
-
var IMAGE_INLINE_MAX_BYTES = 512 * 1024;
|
|
2131
2132
|
var HASHED_ASSET_DIR = "assets";
|
|
2132
2133
|
var MIME_BY_EXT = {
|
|
2133
2134
|
".woff2": "font/woff2",
|
|
@@ -2247,7 +2248,7 @@ function importTarget(atRule) {
|
|
|
2247
2248
|
return null;
|
|
2248
2249
|
return target;
|
|
2249
2250
|
}
|
|
2250
|
-
async function inlineOne(cssPath,
|
|
2251
|
+
async function inlineOne(cssPath, copies, warnings, seen) {
|
|
2251
2252
|
const abs = path2.resolve(cssPath);
|
|
2252
2253
|
if (seen.has(abs))
|
|
2253
2254
|
return "";
|
|
@@ -2282,11 +2283,7 @@ async function inlineOne(cssPath, projectDir, copies, warnings, seen) {
|
|
|
2282
2283
|
return dataUri(bytes2, ext);
|
|
2283
2284
|
}
|
|
2284
2285
|
const bytes = await readOrThrow(absAsset, "asset", abs);
|
|
2285
|
-
|
|
2286
|
-
return dataUri(bytes, ext);
|
|
2287
|
-
}
|
|
2288
|
-
const projectRel = path2.relative(projectDir, absAsset);
|
|
2289
|
-
const dest = projectRel && !escapesProjectRoot(projectDir, absAsset) ? toPosix(projectRel) : `${HASHED_ASSET_DIR}/${contentHash(bytes)}${ext}`;
|
|
2286
|
+
const dest = `${HASHED_ASSET_DIR}/${contentHash(bytes)}${ext}`;
|
|
2290
2287
|
copies.set(dest, { from: absAsset, to: dest });
|
|
2291
2288
|
return dest;
|
|
2292
2289
|
});
|
|
@@ -2299,7 +2296,7 @@ async function inlineOne(cssPath, projectDir, copies, warnings, seen) {
|
|
|
2299
2296
|
});
|
|
2300
2297
|
for (const { node, target } of imports) {
|
|
2301
2298
|
const importedAbs = path2.resolve(cssDir, stripUrlSuffix(decodeRef(target)));
|
|
2302
|
-
const inlined = await inlineOne(importedAbs,
|
|
2299
|
+
const inlined = await inlineOne(importedAbs, copies, warnings, seen);
|
|
2303
2300
|
node.replaceWith(wrapImportConditions(node, postcss.parse(inlined, { from: importedAbs })));
|
|
2304
2301
|
}
|
|
2305
2302
|
return root.toString();
|
|
@@ -2311,7 +2308,7 @@ async function inlineStyles(projectDir, stylePaths) {
|
|
|
2311
2308
|
const parts = [];
|
|
2312
2309
|
for (const rel of stylePaths) {
|
|
2313
2310
|
const abs = path2.resolve(projectDir, rel);
|
|
2314
|
-
const css = await inlineOne(abs,
|
|
2311
|
+
const css = await inlineOne(abs, copies, warnings, seen);
|
|
2315
2312
|
if (css.trim().length > 0) {
|
|
2316
2313
|
parts.push(`/* ${toPosix(path2.relative(projectDir, abs))} */
|
|
2317
2314
|
${css.trim()}`);
|
|
@@ -2451,6 +2448,7 @@ async function renderChapters(inputDir, opts = {}) {
|
|
|
2451
2448
|
files,
|
|
2452
2449
|
readText: (relPath) => readFile3(join(inputDir, canonicalChapterId(relPath)), "utf-8"),
|
|
2453
2450
|
projectCss: inlined.css,
|
|
2451
|
+
preloadImages: inlined.copies.map((c) => c.to),
|
|
2454
2452
|
title: opts.title,
|
|
2455
2453
|
plugins: opts.plugins,
|
|
2456
2454
|
pluginCss: opts.pluginCss,
|
|
@@ -3980,7 +3978,7 @@ import git from "isomorphic-git";
|
|
|
3980
3978
|
// package.json
|
|
3981
3979
|
var package_default = {
|
|
3982
3980
|
name: "gutterpress",
|
|
3983
|
-
version: "0.10.
|
|
3981
|
+
version: "0.10.2-alpha.2",
|
|
3984
3982
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
3985
3983
|
author: "itlackey",
|
|
3986
3984
|
license: "MPL-2.0",
|
|
@@ -4302,6 +4300,18 @@ var marginBoxAtRuleNames = new Set([
|
|
|
4302
4300
|
"right-bottom"
|
|
4303
4301
|
]);
|
|
4304
4302
|
var marginBoxIgnoredProperties = MARGIN_BOX_IGNORED_PROPERTIES;
|
|
4303
|
+
function isInPageBox(decl) {
|
|
4304
|
+
const page = decl.parent;
|
|
4305
|
+
return !!page && page.type === "atrule" && page.name.toLowerCase() === "page";
|
|
4306
|
+
}
|
|
4307
|
+
function isDroppedPageGradient(decl) {
|
|
4308
|
+
const prop = decl.prop.toLowerCase();
|
|
4309
|
+
if (prop !== "background" && prop !== "background-image")
|
|
4310
|
+
return false;
|
|
4311
|
+
if (!/(^|[\s,(])[\w-]*gradient\(/i.test(decl.value))
|
|
4312
|
+
return false;
|
|
4313
|
+
return isInPageBox(decl);
|
|
4314
|
+
}
|
|
4305
4315
|
function isInPageMarginBox(decl) {
|
|
4306
4316
|
const box = decl.parent;
|
|
4307
4317
|
if (!box || box.type !== "atrule")
|
|
@@ -4402,7 +4412,14 @@ function checkCss(css, from) {
|
|
|
4402
4412
|
root.walkAtRules((at) => reportRemoteUrls(at.params || "", at));
|
|
4403
4413
|
root.walkDecls((decl) => {
|
|
4404
4414
|
const prop = decl.prop.toLowerCase();
|
|
4405
|
-
if (prop
|
|
4415
|
+
if (marginBoxIgnoredProperties.has(prop) && isInPageMarginBox(decl)) {
|
|
4416
|
+
warnings.push({
|
|
4417
|
+
rule: ruleRiskyProps,
|
|
4418
|
+
severity: "warning",
|
|
4419
|
+
message: `Property "${decl.prop}" is not supported in Chromium @page margin boxes and is silently ignored — the chrome renders square/unshadowed. Anything that would paint outside the box (box-shadow, outline, filter) or make it a stacking context (transform, opacity, mix-blend-mode) is dropped; text-shadow, border, border-radius, padding and background gradients all work inside the box.`,
|
|
4420
|
+
...nodeLoc(decl)
|
|
4421
|
+
});
|
|
4422
|
+
} else if (prop === "filter") {
|
|
4406
4423
|
warnings.push({
|
|
4407
4424
|
rule: ruleRiskyProps,
|
|
4408
4425
|
severity: "warning",
|
|
@@ -4416,11 +4433,11 @@ function checkCss(css, from) {
|
|
|
4416
4433
|
message: `Property is high-risk for print/PDF (can force rasterization): ${decl.prop}`,
|
|
4417
4434
|
...nodeLoc(decl)
|
|
4418
4435
|
});
|
|
4419
|
-
} else if (
|
|
4436
|
+
} else if (isDroppedPageGradient(decl)) {
|
|
4420
4437
|
warnings.push({
|
|
4421
4438
|
rule: ruleRiskyProps,
|
|
4422
4439
|
severity: "warning",
|
|
4423
|
-
message: `
|
|
4440
|
+
message: `"${decl.prop}: ${decl.value}" on @page paints nothing in print — Chromium drops a gradient in the @page box's own background, with no error and a valid-looking PDF. A solid colour there paints the whole sheet; the same gradient also works on "html" (it covers the content area, not the sheet) or in a margin box.`,
|
|
4424
4441
|
...nodeLoc(decl)
|
|
4425
4442
|
});
|
|
4426
4443
|
} else if (isUnpairedFitContentWidth(decl)) {
|
|
@@ -8016,7 +8033,7 @@ class PdfOutput {
|
|
|
8016
8033
|
const rawPdf = pdfxMode ? path8.join(stage, "raw.pdf") : path8.resolve(pdfFile);
|
|
8017
8034
|
await fsp2.mkdir(path8.dirname(path8.resolve(pdfFile)), { recursive: true });
|
|
8018
8035
|
log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
|
|
8019
|
-
const { buildNativePdf } = await import("./engine-
|
|
8036
|
+
const { buildNativePdf } = await import("./engine-qg5tdaqv.js");
|
|
8020
8037
|
const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
|
|
8021
8038
|
title: config.title,
|
|
8022
8039
|
author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
launchChromium,
|
|
5
5
|
marginBoxAlign,
|
|
6
6
|
marginBoxRectPt
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-wbm6p5j3.js";
|
|
8
8
|
|
|
9
9
|
// src/engine/compiler/build.ts
|
|
10
10
|
import { readFile } from "node:fs/promises";
|
|
@@ -1383,7 +1383,8 @@ var BUILD_DIAGNOSTIC_CODES = [
|
|
|
1383
1383
|
"engine.multicol.dead-column",
|
|
1384
1384
|
"engine.content.overheight",
|
|
1385
1385
|
"engine.image.low-dpi",
|
|
1386
|
-
"engine.flush.margin-box"
|
|
1386
|
+
"engine.flush.margin-box",
|
|
1387
|
+
"engine.page-background.unreferenced"
|
|
1387
1388
|
];
|
|
1388
1389
|
async function build(opts) {
|
|
1389
1390
|
const log = opts.onProgress ?? (() => {});
|
|
@@ -1410,6 +1411,7 @@ async function build(opts) {
|
|
|
1410
1411
|
deviceScaleFactor: 1,
|
|
1411
1412
|
mobile: false
|
|
1412
1413
|
};
|
|
1414
|
+
await page.send("Emulation.setScrollbarsHidden", { hidden: true });
|
|
1413
1415
|
await page.send("Emulation.setDeviceMetricsOverride", sheetViewport);
|
|
1414
1416
|
await page.send("Emulation.setEmulatedMedia", { media: "print" });
|
|
1415
1417
|
log(`print media emulated for audits and measurement`);
|
|
@@ -1780,13 +1782,77 @@ ${mapCss}`;
|
|
|
1780
1782
|
log(`audit: ${audit.length} print-quality warning(s)`);
|
|
1781
1783
|
}
|
|
1782
1784
|
{
|
|
1783
|
-
const { leaks, multicol, layerTraps } = JSON.parse(await page.evaluate(`(() => {
|
|
1785
|
+
const { leaks, multicol, layerTraps, pageBackgrounds } = JSON.parse(await page.evaluate(`(() => {
|
|
1784
1786
|
const desc = ${DESC_JS};
|
|
1785
1787
|
const leaks = [];
|
|
1786
1788
|
const multicol = [];
|
|
1787
1789
|
const layerTraps = [];
|
|
1788
1790
|
const seenLayerTraps = new Set();
|
|
1789
1791
|
|
|
1792
|
+
// Images referenced ONLY from inside an @page rule. Chromium fetches
|
|
1793
|
+
// them and then paints nothing — see the pass comment above.
|
|
1794
|
+
const pageBackgrounds = (() => {
|
|
1795
|
+
const absolute = (u) => {
|
|
1796
|
+
try { return new URL(u, document.baseURI).href } catch { return u }
|
|
1797
|
+
};
|
|
1798
|
+
const urlsIn = (text) => {
|
|
1799
|
+
const out = [];
|
|
1800
|
+
const re = /url\\(\\s*(['"]?)([^'")]*)\\1\\s*\\)/g;
|
|
1801
|
+
let m;
|
|
1802
|
+
while ((m = re.exec(text))) if (m[2]) out.push(m[2]);
|
|
1803
|
+
return out;
|
|
1804
|
+
};
|
|
1805
|
+
const owned = new Map();
|
|
1806
|
+
// Resolved by something OTHER than an @page rule — a CSS rule
|
|
1807
|
+
// outside @page, or an element's inline style. Unconditionally
|
|
1808
|
+
// protective: the image is a StyleImage the load already resolved.
|
|
1809
|
+
const cssReferenced = new Set();
|
|
1810
|
+
// Named by a <link rel="preload">. Protective, unless an element
|
|
1811
|
+
// src names the same URL (see srcNamed).
|
|
1812
|
+
const preloaded = new Set();
|
|
1813
|
+
// Named by an element's src. NEVER protective — measured, this is
|
|
1814
|
+
// the one reference type that BREAKS the page box, and it breaks
|
|
1815
|
+
// it whether or not a preload is present (12/12, either document
|
|
1816
|
+
// order). An element reference is evidence of the failure, not of
|
|
1817
|
+
// safety.
|
|
1818
|
+
const srcNamed = new Set();
|
|
1819
|
+
for (const el of document.querySelectorAll("[src]"))
|
|
1820
|
+
srcNamed.add(absolute(el.getAttribute("src")));
|
|
1821
|
+
for (const el of document.querySelectorAll("link[rel~=preload][href]"))
|
|
1822
|
+
preloaded.add(absolute(el.getAttribute("href")));
|
|
1823
|
+
for (const el of document.querySelectorAll("[style]"))
|
|
1824
|
+
for (const u of urlsIn(el.getAttribute("style"))) cssReferenced.add(absolute(u));
|
|
1825
|
+
const walk = (rules, owner) => {
|
|
1826
|
+
for (const rule of rules) {
|
|
1827
|
+
let own = owner;
|
|
1828
|
+
if (rule.constructor.name === "CSSPageRule")
|
|
1829
|
+
own = "@page" + (rule.selectorText ? " " + rule.selectorText : "");
|
|
1830
|
+
else if (owner && rule.name) own = owner + " { @" + rule.name + " }";
|
|
1831
|
+
for (const u of urlsIn(rule.style ? rule.style.cssText : "")) {
|
|
1832
|
+
if (!own) cssReferenced.add(absolute(u));
|
|
1833
|
+
else if (!owned.has(absolute(u))) owned.set(absolute(u), { url: u, where: own });
|
|
1834
|
+
}
|
|
1835
|
+
if (rule.cssRules) walk(rule.cssRules, own);
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
for (const sheet of document.styleSheets) {
|
|
1839
|
+
let rules;
|
|
1840
|
+
try { rules = sheet.cssRules } catch { continue }
|
|
1841
|
+
walk(rules, null);
|
|
1842
|
+
}
|
|
1843
|
+
// PROTECTED iff another CSS rule resolves it, OR a preload names
|
|
1844
|
+
// it and no element src names it too. "Something else mentions
|
|
1845
|
+
// this URL" is NOT the test — that inference is inverted for
|
|
1846
|
+
// [src], the very reference type that breaks the page box.
|
|
1847
|
+
return [...owned]
|
|
1848
|
+
.filter(([abs, hit]) =>
|
|
1849
|
+
!/^data:/i.test(hit.url) &&
|
|
1850
|
+
!cssReferenced.has(abs) &&
|
|
1851
|
+
!(preloaded.has(abs) && !srcNamed.has(abs)))
|
|
1852
|
+
.map(([, hit]) => hit)
|
|
1853
|
+
.slice(0, 20);
|
|
1854
|
+
})();
|
|
1855
|
+
|
|
1790
1856
|
const stackingReasons = (el, cs) => {
|
|
1791
1857
|
const reasons = [];
|
|
1792
1858
|
const positioned = cs.position !== "static";
|
|
@@ -1907,7 +1973,7 @@ ${mapCss}`;
|
|
|
1907
1973
|
}
|
|
1908
1974
|
}
|
|
1909
1975
|
}
|
|
1910
|
-
return JSON.stringify({ leaks, multicol, layerTraps });
|
|
1976
|
+
return JSON.stringify({ leaks, multicol, layerTraps, pageBackgrounds });
|
|
1911
1977
|
})()`));
|
|
1912
1978
|
for (const d of leaks)
|
|
1913
1979
|
diagnose("engine.abspos.leak", `${d} uses position: absolute with nothing positioned around it, so it is placed against the whole document rather than the page it sits on in your markdown — it can print on a completely different page.`);
|
|
@@ -1917,6 +1983,10 @@ ${mapCss}`;
|
|
|
1917
1983
|
diagnose("engine.layer.trapped", `${trap.behind} cannot paint behind the page as intended because ancestor ${trap.ancestor} ${trap.detail}. Remove or narrowly scope that containment style around the .gp-behind element.`);
|
|
1918
1984
|
if (layerTraps.length)
|
|
1919
1985
|
log(`audit: ${layerTraps.length} trapped layer warning(s)`);
|
|
1986
|
+
for (const hit of pageBackgrounds)
|
|
1987
|
+
diagnose("engine.page-background.unreferenced", `"${hit.url}" is only referenced from "${hit.where}", and Chromium will not print an image referenced nowhere else — the page prints with its background colour alone, with no error. Gutterpress stages and preloads every image your project stylesheets reference, so this one is outside that: it is remote (a url(https://...), which is never staged), or it comes from CSS that does not pass through your stylesheets, or an element in the document uses the same URL as its src, which drops it on its own. Use a local image, referenced from one of your project stylesheets, and not also used as an <img>.`);
|
|
1988
|
+
if (pageBackgrounds.length)
|
|
1989
|
+
log(`audit: ${pageBackgrounds.length} unreferenced @page background image(s)`);
|
|
1920
1990
|
for (const d of multicol)
|
|
1921
1991
|
diagnose("engine.multicol.dead-column", `${d} runs over more than one page in columns, and only the last page's columns get balanced — earlier pages are left with an empty column. Add column-fill: auto to ${d}.`);
|
|
1922
1992
|
if (multicol.length)
|
|
@@ -2069,6 +2139,7 @@ async function predictPageMap(browser, url, agentScript, viewerScript, args, she
|
|
|
2069
2139
|
let page;
|
|
2070
2140
|
try {
|
|
2071
2141
|
page = await browser.newPage();
|
|
2142
|
+
await page.send("Emulation.setScrollbarsHidden", { hidden: true });
|
|
2072
2143
|
await page.send("Emulation.setDeviceMetricsOverride", sheetViewport);
|
|
2073
2144
|
await page.navigate(url);
|
|
2074
2145
|
await page.evaluate(agentScript);
|
|
@@ -593,22 +593,22 @@ import { fileURLToPath } from "node:url";
|
|
|
593
593
|
var favicon_default = "./favicon-wkbm9cjn.ico";
|
|
594
594
|
|
|
595
595
|
// src/assets/manifest.schema.json
|
|
596
|
-
var manifest_schema_default = "./manifest.schema-
|
|
596
|
+
var manifest_schema_default = "./manifest.schema-084rxtwp.json";
|
|
597
597
|
|
|
598
598
|
// src/assets/preview/scripts/preview-interface.js
|
|
599
|
-
var preview_interface_default = "./preview-interface-
|
|
599
|
+
var preview_interface_default = "./preview-interface-9rwz21yw.js";
|
|
600
600
|
|
|
601
601
|
// src/assets/preview/scripts/preview-bridge.js
|
|
602
602
|
var preview_bridge_default = "./preview-bridge-2nnrs2hy.js";
|
|
603
603
|
|
|
604
604
|
// src/assets/preview/scripts/preview-shell.js
|
|
605
|
-
var preview_shell_default = "./preview-shell-
|
|
605
|
+
var preview_shell_default = "./preview-shell-gqnc5xxe.js";
|
|
606
606
|
|
|
607
607
|
// profiles/CGATS21_CRPC1.icc
|
|
608
608
|
var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
|
|
609
609
|
|
|
610
610
|
// src/assets/engine/gutterpress-viewer.js
|
|
611
|
-
var gutterpress_viewer_default = "./gutterpress-viewer-
|
|
611
|
+
var gutterpress_viewer_default = "./gutterpress-viewer-mfeccbys.js";
|
|
612
612
|
|
|
613
613
|
// src/assets/engine/gutterpress-agent.js
|
|
614
614
|
var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
|
|
@@ -711,7 +711,18 @@ var MARGIN_BOX_IGNORED_PROPERTIES = new Set([
|
|
|
711
711
|
"rotate",
|
|
712
712
|
"translate",
|
|
713
713
|
"scale",
|
|
714
|
-
"
|
|
714
|
+
"perspective",
|
|
715
|
+
"box-shadow",
|
|
716
|
+
"opacity",
|
|
717
|
+
"outline",
|
|
718
|
+
"outline-color",
|
|
719
|
+
"outline-style",
|
|
720
|
+
"outline-width",
|
|
721
|
+
"outline-offset",
|
|
722
|
+
"filter",
|
|
723
|
+
"backdrop-filter",
|
|
724
|
+
"mix-blend-mode",
|
|
725
|
+
"clip-path"
|
|
715
726
|
]);
|
|
716
727
|
function isIgnoredMarginBoxProperty(property) {
|
|
717
728
|
return MARGIN_BOX_IGNORED_PROPERTIES.has(property.toLowerCase());
|
package/dist/index.js
CHANGED
|
@@ -113,15 +113,15 @@ import {
|
|
|
113
113
|
syncProject,
|
|
114
114
|
testRemoteAccess,
|
|
115
115
|
validateProjectPlugins
|
|
116
|
-
} from "./index-
|
|
116
|
+
} from "./index-5vfsbjwz.js";
|
|
117
117
|
import {
|
|
118
118
|
BUILD_DIAGNOSTIC_CODES
|
|
119
|
-
} from "./index-
|
|
119
|
+
} from "./index-my3v298z.js";
|
|
120
120
|
import {
|
|
121
121
|
BuildError,
|
|
122
122
|
DEFAULT_PRINT_OPTS,
|
|
123
123
|
readyProbeExpr
|
|
124
|
-
} from "./index-
|
|
124
|
+
} from "./index-wbm6p5j3.js";
|
|
125
125
|
import {
|
|
126
126
|
AUTO_SNAPSHOT_MESSAGE,
|
|
127
127
|
HISTORY_PAGE_LIMIT,
|
|
@@ -6,16 +6,15 @@
|
|
|
6
6
|
* local `@import` closure) is inlined, so a stylesheet's location is irrelevant
|
|
7
7
|
* to the output and themes/shared design systems need no copying. Fonts become
|
|
8
8
|
* `data:` URIs — which is what guarantees the byte-identical face reaches
|
|
9
|
-
* Chromium, and therefore the PDF.
|
|
9
|
+
* Chromium, and therefore the PDF. Images are files: every CSS image is copied
|
|
10
|
+
* under a content-addressed name, whatever it weighs.
|
|
10
11
|
*
|
|
11
12
|
* A missing stylesheet or font is a build error here, at read time, rather than
|
|
12
|
-
* a 404 during pagination that
|
|
13
|
-
*
|
|
13
|
+
* a 404 during pagination that gets parsed as CSS or silently replaced with a
|
|
14
|
+
* system face.
|
|
14
15
|
*
|
|
15
16
|
* Bundle-safe (CLAUDE.md §1/§3): postcss only.
|
|
16
17
|
*/
|
|
17
|
-
/** Inline images up to this size; copy larger ones (full-bleed page art). */
|
|
18
|
-
export declare const IMAGE_INLINE_MAX_BYTES: number;
|
|
19
18
|
/** One file the build must copy into the output directory. */
|
|
20
19
|
export interface AssetCopy {
|
|
21
20
|
/** Absolute path of the source file. */
|
|
@@ -92,8 +91,11 @@ export declare function inlineStyles(projectDir: string, stylePaths: string[]):
|
|
|
92
91
|
*/
|
|
93
92
|
export declare function collectStyleDependencies(projectDir: string, stylePaths: string[]): Promise<string[]>;
|
|
94
93
|
/**
|
|
95
|
-
* Turn the renderer's recorded image references into a copy plan.
|
|
96
|
-
* their authored relative path, so the author's folder layout is what
|
|
94
|
+
* Turn the renderer's recorded image references into a copy plan. PROSE images
|
|
95
|
+
* keep their authored relative path, so the author's folder layout is what
|
|
96
|
+
* ships for them. CSS images do NOT — `inlineOne` content-addresses every one,
|
|
97
|
+
* deliberately, so a CSS URL and a prose URL can never coincide (see the
|
|
98
|
+
* comment there). A file used both ways therefore ships twice under two names.
|
|
97
99
|
* A `../` reference is rejected rather than silently relocated — it has no
|
|
98
100
|
* representable path under the output root.
|
|
99
101
|
*/
|
|
@@ -39,10 +39,10 @@ export interface BuildRunnerOptions {
|
|
|
39
39
|
keepBrowserAlive?: boolean;
|
|
40
40
|
rawArgs: Record<string, unknown>;
|
|
41
41
|
/**
|
|
42
|
-
* CLI `--engine` override.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* CLI `--engine` override. The native engine is the only engine, so this is
|
|
43
|
+
* a deprecated no-op accepted for backward compatibility only: `"paged"`
|
|
44
|
+
* triggers a one-line warning (`manifest.ts`'s resolution) and the build
|
|
45
|
+
* proceeds natively regardless.
|
|
46
46
|
*/
|
|
47
47
|
engine?: "paged" | "native";
|
|
48
48
|
/**
|
|
@@ -70,9 +70,8 @@ export interface BuildRunnerResult {
|
|
|
70
70
|
/** As {@link htmlPath}: `null` when nothing but the artifact was published. */
|
|
71
71
|
fingerprintPath: string | null;
|
|
72
72
|
/**
|
|
73
|
-
* Author-facing print-quality findings from the render
|
|
74
|
-
*
|
|
75
|
-
* maps these into the Problems panel; the CLI logs them.
|
|
73
|
+
* Author-facing print-quality findings from the render. Empty for a clean
|
|
74
|
+
* build. The desktop maps these into the Problems panel; the CLI logs them.
|
|
76
75
|
*/
|
|
77
76
|
diagnostics: BuildDiagnostic[];
|
|
78
77
|
}
|
package/dist/lib/cli-args.d.ts
CHANGED
|
@@ -42,11 +42,10 @@ export declare function parseFormat(raw: unknown, opts: {
|
|
|
42
42
|
/** Parse `--pdfx-flavor`, which is only valid alongside `--format pdfx`. */
|
|
43
43
|
export declare function parsePdfxFlavor(raw: unknown, format: BuildFormat): PdfxFlavor | undefined;
|
|
44
44
|
/**
|
|
45
|
-
* Parse `--engine`, shared by `build` and `preview`.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* `undefined` means "not passed".
|
|
45
|
+
* Parse `--engine`, shared by `build` and `preview`. The native engine is the
|
|
46
|
+
* only engine, so this flag is an accepted-but-ignored no-op: `--engine paged`
|
|
47
|
+
* produces a one-line warning (`manifest.ts`'s resolution) and the build
|
|
48
|
+
* proceeds natively regardless. `undefined` means "not passed".
|
|
50
49
|
*/
|
|
51
50
|
export declare function parseEngine(raw: unknown): "paged" | "native" | undefined;
|
|
52
51
|
/** Parse `--port`, defaulting to {@link NETWORK.DEFAULT_PORT} (0 = OS-assigned). */
|
package/dist/lib/engine.d.ts
CHANGED
|
@@ -17,9 +17,9 @@ export interface NativePdfOptions {
|
|
|
17
17
|
allowShrink?: boolean;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* Render `htmlFile` to `outPdf` via the Gutterpress engine. No HTTP staging
|
|
21
|
-
*
|
|
22
|
-
* module
|
|
20
|
+
* Render `htmlFile` to `outPdf` via the Gutterpress engine. No HTTP staging —
|
|
21
|
+
* but the pooled/pre-warmed Chromium IS reused (see module doc comment): this
|
|
22
|
+
* module never launches or closes a browser itself.
|
|
23
23
|
*
|
|
24
24
|
* Returns the build's author-facing diagnostics so the caller can surface
|
|
25
25
|
* them (the desktop Problems panel, the CLI's own output). Dropping them here
|
|
@@ -39,6 +39,43 @@ export interface AssembleBookHtmlOptions {
|
|
|
39
39
|
* relocate or lose during staging.
|
|
40
40
|
*/
|
|
41
41
|
projectCss?: string;
|
|
42
|
+
/**
|
|
43
|
+
* SHIM — spec gap #152. Output-relative hrefs of the images the project's
|
|
44
|
+
* stylesheets staged (`inlineStyles`'s copy plan, verbatim), each emitted as
|
|
45
|
+
* one `<link rel="preload" as="image">`.
|
|
46
|
+
*
|
|
47
|
+
* Chromium reaches an `@page`-only `url()` lazily, during the print, and the
|
|
48
|
+
* print path CDP drives never waits for a pending resource — so the sheet
|
|
49
|
+
* comes back with its background colour alone, no error, a valid PDF of
|
|
50
|
+
* blank paper (docs/known-limitations.md §3; mechanism in
|
|
51
|
+
* PR #187's `docs/analysis/why-page-background-drops.md`).
|
|
52
|
+
*
|
|
53
|
+
* What the preload buys is that the fetch STARTS during document load
|
|
54
|
+
* instead of during the print. That is not a timing guarantee: a response
|
|
55
|
+
* slow enough still loses (measured — held 1500 ms server-side, the preload
|
|
56
|
+
* row drops too). On the PDF path the asset is a local file staged beside
|
|
57
|
+
* `book.html`, so there is no server to be slow; a published `--format html`
|
|
58
|
+
* bundle read over a slow network can still lose the race.
|
|
59
|
+
*
|
|
60
|
+
* A second ELEMENT reference is not an alternative. Any `[src]` naming the
|
|
61
|
+
* URL drops the page box (measured 12/12, with or without a preload, in
|
|
62
|
+
* either document order) — which is why `asset-inline.ts` content-addresses
|
|
63
|
+
* every CSS image so no element can name one.
|
|
64
|
+
*
|
|
65
|
+
* WHAT PROVES IT IS STILL NEEDED: the expiry canary,
|
|
66
|
+
* `engine/compiler/page-background-chromium-bug.canary.test.ts`. The day it
|
|
67
|
+
* goes red, Chromium has fixed the bug — delete this option, the `.map()`
|
|
68
|
+
* that feeds it in `markdown/index.ts`, and the canary.
|
|
69
|
+
*
|
|
70
|
+
* The copy plan is the source, NOT a scan of the assembled CSS: `pluginCss`
|
|
71
|
+
* never passes through `inlineStyles`, so a `url()` inside it is never
|
|
72
|
+
* staged and a scan would emit a `<link>` to a file that does not exist.
|
|
73
|
+
* The plan is already deduped (keyed by destination), already excludes fonts
|
|
74
|
+
* (inlined) and remote urls (left alone), and already covers the
|
|
75
|
+
* `--paper: url()` + `var(--paper)` shape, because `walkDecls` sees custom
|
|
76
|
+
* properties like any other declaration.
|
|
77
|
+
*/
|
|
78
|
+
preloadImages?: string[];
|
|
42
79
|
title?: string;
|
|
43
80
|
plugins?: LoadedPlugin[];
|
|
44
81
|
pluginCss?: string;
|
|
@@ -61,17 +61,12 @@
|
|
|
61
61
|
* and a plain `width: 100%` already reaches both edges —
|
|
62
62
|
* with no shrink-to-fit trigger, because nothing out-dents
|
|
63
63
|
* past the content box. MEASURED (Chromium 148, 6x4in
|
|
64
|
-
* sheet, 0.75in margins): the
|
|
65
|
-
* out-dent of the real page margins
|
|
66
|
-
*
|
|
67
|
-
* `--pagedjs-margin-left/right`) shrank the WHOLE document
|
|
68
|
-
* ~10% under native print (text run 204.4pt -> 182.9pt),
|
|
64
|
+
* sheet, 0.75in margins): the obvious alternative — a
|
|
65
|
+
* negative out-dent of the real page margins — shrank the
|
|
66
|
+
* WHOLE document ~10% (text run 204.4pt -> 182.9pt),
|
|
69
67
|
* because the shrink-to-fit trigger is the page CONTENT
|
|
70
|
-
* box, not the sheet
|
|
71
|
-
* page exists.
|
|
72
|
-
* (native-only-migration-plan.md Phase 6), so the out-dent
|
|
73
|
-
* (whose custom properties nothing sets any more, making it
|
|
74
|
-
* a permanent no-op) went with it.
|
|
68
|
+
* box, not the sheet. That failure mode is why the named
|
|
69
|
+
* page exists.
|
|
75
70
|
*
|
|
76
71
|
* KNOWN GAP: on the bleed page, native's running head/folio
|
|
77
72
|
* move onto the trim line (margin boxes are positioned by
|
|
@@ -14,12 +14,10 @@ export default function plugin(md: any, pluginOptions?: {}): void;
|
|
|
14
14
|
* page root that only shrink-wraps its text is a containing block whose
|
|
15
15
|
* `bottom` edge is the end of the PROSE, so `.gp-pin .gp-bottom` (and every
|
|
16
16
|
* hand-written `position: absolute; bottom: 0`) lands under the last
|
|
17
|
-
* paragraph instead of at the page foot.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* page-boundary pinning with it. Both renderers now publish the page
|
|
22
|
-
* CONTENT height as `--gp-content-h` for the page context the element is in
|
|
17
|
+
* paragraph instead of at the page foot. Nothing in a continuous document
|
|
18
|
+
* stretches a page root to the page area on its own, so both renderers
|
|
19
|
+
* publish the page CONTENT height as `--gp-content-h` for the page context
|
|
20
|
+
* the element is in
|
|
23
21
|
* (the viewer on each `.gp-strip`; the compiler on `:root` plus every
|
|
24
22
|
* `page:` assignment selector), and custom properties inherit, so this one
|
|
25
23
|
* rule reaches a page root at any wrapper depth. Undefined var (plain
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* items, blockquotes, table rows, fences, footnote definitions, …).
|
|
13
13
|
* 2. `token.meta.line` — the 1-based marker line threaded onto
|
|
14
14
|
* `layout_*_open` / `layout_page_break` / `layout_column_break` tokens
|
|
15
|
-
* by `
|
|
15
|
+
* by `markers.js` (see that file's header and the
|
|
16
16
|
* `t.meta = …` assignment sites). Converted to the same half-open
|
|
17
17
|
* convention as `[line - 1, line)`.
|
|
18
18
|
*
|
|
@@ -14,8 +14,7 @@ import http from "node:http";
|
|
|
14
14
|
* answering a separate question, "what Content-Type does a data: URI need
|
|
15
15
|
* when embedding this file inline," vs. this table's "what Content-Type does
|
|
16
16
|
* an HTTP response need" — had drifted again, missing `.webp`/`.avif` here.
|
|
17
|
-
* A
|
|
18
|
-
* as a real file and then served as `application/octet-stream` by both
|
|
17
|
+
* A copied WebP/AVIF image was served as `application/octet-stream` by both
|
|
19
18
|
* servers above. Added below; if a third image format shows up, add it to
|
|
20
19
|
* BOTH tables in the same commit (asset-inline.ts is out of scope for this
|
|
21
20
|
* PR — see docs/reviews/duplication-audit-2026-07-28.md).
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MANIFEST_FILENAMES,
|
|
3
3
|
runLint
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-w3spkryk.js";
|
|
5
5
|
import {
|
|
6
6
|
log
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-wef7qh62.js";
|
|
8
8
|
import {
|
|
9
9
|
UsageError,
|
|
10
10
|
rejectExtraPositionals,
|
|
11
11
|
rejectUnknownFlags
|
|
12
|
-
} from "./cli-
|
|
13
|
-
import"./cli-
|
|
12
|
+
} from "./cli-daahjr6z.js";
|
|
13
|
+
import"./cli-c2yvktan.js";
|
|
14
14
|
import"./cli-c41yr7he.js";
|
|
15
15
|
import {
|
|
16
16
|
EXIT_CODES
|
|
@@ -31,18 +31,18 @@
|
|
|
31
31
|
},
|
|
32
32
|
"engine": {
|
|
33
33
|
"type": "string",
|
|
34
|
-
"description": "Pagination engine.
|
|
34
|
+
"description": "Pagination engine. The Gutterpress engine (native Chromium pagination) is the only engine, and \"native\" is the only meaningful value. \"paged\" is accepted for backward-compatible manifest parsing only: it produces a one-line warning and the build proceeds natively regardless. `--engine` on the CLI is likewise an accepted-but-ignored no-op.",
|
|
35
35
|
"enum": ["paged", "native"],
|
|
36
36
|
"default": "native",
|
|
37
37
|
"examples": ["native"]
|
|
38
38
|
},
|
|
39
39
|
"engineStyles": {
|
|
40
40
|
"type": "object",
|
|
41
|
-
"description": "Engine-conditional stylesheets, appended AFTER `styles`. `.native` is the only list that still applies; `.paged` is accepted-but-ignored (a warning fires if it has entries)
|
|
41
|
+
"description": "Engine-conditional stylesheets, appended AFTER `styles`. `.native` is the only list that still applies; `.paged` is accepted-but-ignored (a warning fires if it has entries).",
|
|
42
42
|
"properties": {
|
|
43
43
|
"paged": {
|
|
44
44
|
"type": "array",
|
|
45
|
-
"description": "
|
|
45
|
+
"description": "Accepted for backward-compatible manifest parsing only; entries here are ignored with a warning.",
|
|
46
46
|
"items": { "type": "string" }
|
|
47
47
|
},
|
|
48
48
|
"native": { "type": "array", "items": { "type": "string" } }
|
|
@@ -5,16 +5,16 @@ import {
|
|
|
5
5
|
TARGETS,
|
|
6
6
|
TARGET_IDS,
|
|
7
7
|
scaffoldProject
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-w3spkryk.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveGhostscript
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-wef7qh62.js";
|
|
12
12
|
import {
|
|
13
13
|
UsageError,
|
|
14
14
|
rejectExtraPositionals,
|
|
15
15
|
rejectUnknownFlags
|
|
16
|
-
} from "./cli-
|
|
17
|
-
import"./cli-
|
|
16
|
+
} from "./cli-daahjr6z.js";
|
|
17
|
+
import"./cli-c2yvktan.js";
|
|
18
18
|
import {
|
|
19
19
|
isToolAvailable
|
|
20
20
|
} from "./cli-c41yr7he.js";
|