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
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getAssetPath,
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
isIgnoredMarginBoxProperty,
|
|
4
|
+
launchChromium,
|
|
5
|
+
marginBoxAlign,
|
|
6
|
+
marginBoxRectPt
|
|
7
|
+
} from "./index-ycpvr0am.js";
|
|
5
8
|
|
|
6
9
|
// src/engine/compiler/build.ts
|
|
7
10
|
import { readFile } from "node:fs/promises";
|
|
@@ -568,6 +571,45 @@ function specificity(r) {
|
|
|
568
571
|
return (r.name ? 2 : 0) + r.pseudos.length;
|
|
569
572
|
}
|
|
570
573
|
|
|
574
|
+
// src/engine/shared/flush.ts
|
|
575
|
+
var FLUSH_EDGES = ["top", "right", "bottom", "left"];
|
|
576
|
+
function flushKey(edges) {
|
|
577
|
+
return FLUSH_EDGES.filter((e) => edges.includes(e)).map((e) => e[0]).join("");
|
|
578
|
+
}
|
|
579
|
+
function flushPageName(authorPage, edges) {
|
|
580
|
+
const safe = authorPage ? authorPage.replace(/[^A-Za-z0-9_-]/g, "_") : "";
|
|
581
|
+
return `gp--flush${safe ? `-${safe}` : ""}-${flushKey(edges)}`;
|
|
582
|
+
}
|
|
583
|
+
function flushMargins(margin, edges) {
|
|
584
|
+
return {
|
|
585
|
+
top: edges.includes("top") ? 0 : margin.top,
|
|
586
|
+
right: edges.includes("right") ? 0 : margin.right,
|
|
587
|
+
bottom: edges.includes("bottom") ? 0 : margin.bottom,
|
|
588
|
+
left: edges.includes("left") ? 0 : margin.left
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function marginBoxesOnEdges(edges) {
|
|
592
|
+
const owners = {
|
|
593
|
+
"top-left-corner": ["top", "left"],
|
|
594
|
+
"top-left": ["top"],
|
|
595
|
+
"top-center": ["top"],
|
|
596
|
+
"top-right": ["top"],
|
|
597
|
+
"top-right-corner": ["top", "right"],
|
|
598
|
+
"bottom-left-corner": ["bottom", "left"],
|
|
599
|
+
"bottom-left": ["bottom"],
|
|
600
|
+
"bottom-center": ["bottom"],
|
|
601
|
+
"bottom-right": ["bottom"],
|
|
602
|
+
"bottom-right-corner": ["bottom", "right"],
|
|
603
|
+
"left-top": ["left"],
|
|
604
|
+
"left-middle": ["left"],
|
|
605
|
+
"left-bottom": ["left"],
|
|
606
|
+
"right-top": ["right"],
|
|
607
|
+
"right-middle": ["right"],
|
|
608
|
+
"right-bottom": ["right"]
|
|
609
|
+
};
|
|
610
|
+
return Object.entries(owners).filter(([, own]) => own.some((e) => edges.includes(e))).map(([name]) => name);
|
|
611
|
+
}
|
|
612
|
+
|
|
571
613
|
// src/engine/shared/synthesis.ts
|
|
572
614
|
var RECTO_VERSO_VALUES = /^(right|recto|left|verso)$/;
|
|
573
615
|
function isRectoVersoBreak(decl) {
|
|
@@ -1284,7 +1326,8 @@ var BUILD_DIAGNOSTIC_CODES = [
|
|
|
1284
1326
|
"engine.layer.trapped",
|
|
1285
1327
|
"engine.multicol.dead-column",
|
|
1286
1328
|
"engine.content.overheight",
|
|
1287
|
-
"engine.image.low-dpi"
|
|
1329
|
+
"engine.image.low-dpi",
|
|
1330
|
+
"engine.flush.margin-box"
|
|
1288
1331
|
];
|
|
1289
1332
|
async function build(opts) {
|
|
1290
1333
|
const log = opts.onProgress ?? (() => {});
|
|
@@ -1303,8 +1346,7 @@ async function build(opts) {
|
|
|
1303
1346
|
await page.evaluate(AGENT);
|
|
1304
1347
|
await page.waitForReady();
|
|
1305
1348
|
const cssText = await page.evaluate(`window.__gp.collectCss()`);
|
|
1306
|
-
|
|
1307
|
-
const { tier3Reasons } = classify(model);
|
|
1349
|
+
let model = extract(cssText);
|
|
1308
1350
|
const baseGeom = resolvePage(model).geometry;
|
|
1309
1351
|
const sheetViewport = {
|
|
1310
1352
|
width: Math.max(1, Math.round(baseGeom.width * 96 / 72)),
|
|
@@ -1315,6 +1357,97 @@ async function build(opts) {
|
|
|
1315
1357
|
await page.send("Emulation.setDeviceMetricsOverride", sheetViewport);
|
|
1316
1358
|
await page.send("Emulation.setEmulatedMedia", { media: "print" });
|
|
1317
1359
|
log(`print media emulated for audits and measurement`);
|
|
1360
|
+
const flushRoots = await page.evaluate(`window.__gp.flushRoots()`);
|
|
1361
|
+
const flushGroups = new Map;
|
|
1362
|
+
for (const root of flushRoots) {
|
|
1363
|
+
if (flushGroups.has(root.key))
|
|
1364
|
+
continue;
|
|
1365
|
+
const authorPage = root.page === "auto" ? undefined : root.page;
|
|
1366
|
+
flushGroups.set(root.key, {
|
|
1367
|
+
authorPage,
|
|
1368
|
+
edges: root.edges,
|
|
1369
|
+
genName: flushPageName(authorPage, root.edges),
|
|
1370
|
+
key: root.key
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
if (flushGroups.size) {
|
|
1374
|
+
const lines = [];
|
|
1375
|
+
for (const group of flushGroups.values()) {
|
|
1376
|
+
const relocated = new Set(marginBoxesOnEdges(group.edges));
|
|
1377
|
+
const pseudoSets = [[]];
|
|
1378
|
+
if (group.authorPage) {
|
|
1379
|
+
for (const rule of model.pageRules) {
|
|
1380
|
+
if (rule.name !== group.authorPage)
|
|
1381
|
+
continue;
|
|
1382
|
+
const pseudo = rule.pseudos.length ? `:${rule.pseudos.join(":")}` : "";
|
|
1383
|
+
if (rule.pseudos.length && !pseudoSets.some((ps) => ps.length === rule.pseudos.length && rule.pseudos.every((x) => ps.includes(x))))
|
|
1384
|
+
pseudoSets.push(rule.pseudos);
|
|
1385
|
+
const body = [];
|
|
1386
|
+
for (const [prop, value] of Object.entries(rule.decls))
|
|
1387
|
+
body.push(` ${prop}: ${value};`);
|
|
1388
|
+
for (const [box, decls] of Object.entries(rule.marginBoxes)) {
|
|
1389
|
+
if (relocated.has(box.slice(1)))
|
|
1390
|
+
continue;
|
|
1391
|
+
body.push(` ${box} {`);
|
|
1392
|
+
for (const [prop, value] of Object.entries(decls))
|
|
1393
|
+
body.push(` ${prop}: ${value};`);
|
|
1394
|
+
body.push(` }`);
|
|
1395
|
+
}
|
|
1396
|
+
lines.push(`@page ${group.genName}${pseudo} {
|
|
1397
|
+
${body.join(`
|
|
1398
|
+
`)}
|
|
1399
|
+
}`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
for (const pseudos of pseudoSets) {
|
|
1403
|
+
const pseudo = pseudos.length ? `:${pseudos.join(":")}` : "";
|
|
1404
|
+
const body = [];
|
|
1405
|
+
for (const edge of group.edges)
|
|
1406
|
+
body.push(` margin-${edge}: 0;`);
|
|
1407
|
+
for (const box of relocated)
|
|
1408
|
+
body.push(` @${box} { content: none; }`);
|
|
1409
|
+
lines.push(`@page ${group.genName}${pseudo} {
|
|
1410
|
+
${body.join(`
|
|
1411
|
+
`)}
|
|
1412
|
+
}`);
|
|
1413
|
+
}
|
|
1414
|
+
lines.push(`:where(.page, .spread)[data-gp-flush="${group.key}"][data-gp-flush][data-gp-flush][data-gp-flush] { page: ${group.genName}; }`);
|
|
1415
|
+
}
|
|
1416
|
+
const flushCss = lines.join(`
|
|
1417
|
+
`);
|
|
1418
|
+
await page.evaluate(`window.__gp.addCss("gp-flush-css", ${JSON.stringify(flushCss)})`);
|
|
1419
|
+
model = extract(`${cssText}
|
|
1420
|
+
${flushCss}`);
|
|
1421
|
+
log(`.gp-flush: ${flushRoots.length} pinned root(s) -> ${flushGroups.size} generated page context(s)`);
|
|
1422
|
+
}
|
|
1423
|
+
const { tier3Reasons } = classify(model);
|
|
1424
|
+
const pseudoContexts = pagePseudoContexts(model);
|
|
1425
|
+
const contentHeightPt = (name) => Math.min(...pseudoContexts.map((pseudos) => {
|
|
1426
|
+
const geometry = resolvePage(model, { name, pseudos }).geometry;
|
|
1427
|
+
return geometry.height - geometry.margin.top - geometry.margin.bottom;
|
|
1428
|
+
}));
|
|
1429
|
+
const contentHeightPx = contentHeightPt() * 96 / 72;
|
|
1430
|
+
const namedContentHeightsPx = Object.fromEntries(model.pageNames.map((name) => [name, contentHeightPt(name) * 96 / 72]));
|
|
1431
|
+
const minContentHeightPx = Math.min(contentHeightPt(), ...model.pageNames.map((name) => contentHeightPt(name))) * 96 / 72;
|
|
1432
|
+
const pageVarsCss = [
|
|
1433
|
+
`:root { --gp-content-h: ${contentHeightPx}px; }`,
|
|
1434
|
+
...model.pageAssignments.map((a) => `${a.selector} { --gp-content-h: ${namedContentHeightsPx[a.page] ?? contentHeightPx}px; }`)
|
|
1435
|
+
].join(`
|
|
1436
|
+
`);
|
|
1437
|
+
await page.evaluate(`window.__gp.addCss("gp-page-vars", ${JSON.stringify(pageVarsCss)})`);
|
|
1438
|
+
log(`page geometry published (--gp-content-h: ${Math.round(contentHeightPx)}px)`);
|
|
1439
|
+
const relocationBoxes = (group, pseudos) => {
|
|
1440
|
+
const ctx = resolvePage(model, { name: group.authorPage, pseudos });
|
|
1441
|
+
return marginBoxesOnEdges(group.edges).map((name) => [name, ctx.marginBoxes[`@${name}`]]).filter((pair) => {
|
|
1442
|
+
const c = pair[1]?.content?.trim();
|
|
1443
|
+
return !!c && c !== "none" && c !== "normal";
|
|
1444
|
+
});
|
|
1445
|
+
};
|
|
1446
|
+
const furnitureRoots = flushRoots.filter((root) => {
|
|
1447
|
+
const group = flushGroups.get(root.key);
|
|
1448
|
+
return pseudoContexts.some((pseudos) => relocationBoxes(group, pseudos).length > 0);
|
|
1449
|
+
});
|
|
1450
|
+
const flushDiagnosed = new Set;
|
|
1318
1451
|
const tier2 = synthesize({
|
|
1319
1452
|
model,
|
|
1320
1453
|
marks: opts.marks,
|
|
@@ -1334,11 +1467,15 @@ async function build(opts) {
|
|
|
1334
1467
|
}).join(`
|
|
1335
1468
|
`);
|
|
1336
1469
|
if (widthOffenders.boxes.length) {
|
|
1337
|
-
const
|
|
1470
|
+
const scale = shrinkScale(widthOffenders.boxes, widthOffenders.limitPx);
|
|
1471
|
+
const headline = scale === null ? `content outside the page content box risks Chromium print ` + `shrink-to-fit (the WHOLE book scales down, silently)` : `content wider than the page content box: Chromium print shrink-to-fit ` + `scales the WHOLE document — every page, every measurement — to about ` + `${scale.toFixed(2)}x its declared size (12pt type prints at ` + `${(12 * scale).toFixed(1)}pt). The page size and page count do not ` + `change, so the shrink is invisible in the PDF`;
|
|
1472
|
+
const msg = `${headline}:
|
|
1338
1473
|
${describe(widthOffenders.boxes)}`;
|
|
1339
1474
|
if (opts.allowShrink) {
|
|
1475
|
+
if (scale !== null)
|
|
1476
|
+
diagnose("engine.width.overflow", `${headline}.`);
|
|
1340
1477
|
for (const o of widthOffenders.boxes)
|
|
1341
|
-
diagnose("engine.width.overflow", `${o.desc} is
|
|
1478
|
+
diagnose("engine.width.overflow", `${o.desc} is ${Math.round(o.px)}px wide, past the ${Math.round(widthOffenders.limitPx)}px page content box. ${o.left < -1 ? "Keep it inside the page content box." : "Give it an explicit width that fits."}`);
|
|
1342
1479
|
log(`WARNING: ${msg}`);
|
|
1343
1480
|
} else {
|
|
1344
1481
|
throw new Error(`${msg}
|
|
@@ -1353,7 +1490,7 @@ ${describe(widthOffenders.intrinsics)}`;
|
|
|
1353
1490
|
log(`WARNING: ${msg}`);
|
|
1354
1491
|
}
|
|
1355
1492
|
const rectoDecls = model.breaks.filter(isRectoVersoBreak);
|
|
1356
|
-
const needsMeasure = tier3Reasons.length > 0 || consumedStrings(model).size > 0 || rectoDecls.length > 0 || model.counterResets.length > 0;
|
|
1493
|
+
const needsMeasure = tier3Reasons.length > 0 || consumedStrings(model).size > 0 || rectoDecls.length > 0 || model.counterResets.length > 0 || furnitureRoots.length > 0;
|
|
1357
1494
|
let tier = tier2.geometry.bleed > 0 || tier2.geometry.slug > 0 ? 2 : 1;
|
|
1358
1495
|
let passes = 1;
|
|
1359
1496
|
let pageMap = {};
|
|
@@ -1381,6 +1518,8 @@ ${describe(widthOffenders.intrinsics)}`;
|
|
|
1381
1518
|
targets.add(s.id);
|
|
1382
1519
|
for (const s of resetSites)
|
|
1383
1520
|
targets.add(s.id);
|
|
1521
|
+
for (const r of furnitureRoots)
|
|
1522
|
+
targets.add(r.id);
|
|
1384
1523
|
await page.evaluate(`window.__gp.instrument(${JSON.stringify([...targets])})`);
|
|
1385
1524
|
const targetText = await page.evaluate(`window.__gp.targetTexts(${JSON.stringify([...targets])})`);
|
|
1386
1525
|
const brokenXrefs = findBrokenXrefRefs(sites, targetText);
|
|
@@ -1465,6 +1604,73 @@ ${describe(widthOffenders.intrinsics)}`;
|
|
|
1465
1604
|
/* Tier 3 */
|
|
1466
1605
|
${mapCss}`;
|
|
1467
1606
|
}
|
|
1607
|
+
if (furnitureRoots.length) {
|
|
1608
|
+
const pageAt = (id) => map[id] ?? predictedForResult?.pageMap[id];
|
|
1609
|
+
const byName = new Map;
|
|
1610
|
+
for (const s of sources) {
|
|
1611
|
+
const p = pageAt(s.id);
|
|
1612
|
+
if (!p)
|
|
1613
|
+
continue;
|
|
1614
|
+
const list = byName.get(s.name) ?? [];
|
|
1615
|
+
list.push({ page: p, value: s.text });
|
|
1616
|
+
byName.set(s.name, list);
|
|
1617
|
+
}
|
|
1618
|
+
for (const entries of byName.values())
|
|
1619
|
+
entries.sort((a, b) => a.page - b.page);
|
|
1620
|
+
const PXPT = 96 / 72;
|
|
1621
|
+
const items = [];
|
|
1622
|
+
for (const root of furnitureRoots) {
|
|
1623
|
+
const physical = pageAt(root.id);
|
|
1624
|
+
if (!physical)
|
|
1625
|
+
continue;
|
|
1626
|
+
const group = flushGroups.get(root.key);
|
|
1627
|
+
const pseudos = [physical % 2 === 1 ? "right" : "left"];
|
|
1628
|
+
if (physical === 1)
|
|
1629
|
+
pseudos.push("first");
|
|
1630
|
+
const ctx = resolvePage(model, { name: group.authorPage, pseudos });
|
|
1631
|
+
const g = ctx.geometry;
|
|
1632
|
+
const eff = flushMargins(g.margin, group.edges);
|
|
1633
|
+
const boxes = [];
|
|
1634
|
+
for (const [name, decls] of relocationBoxes(group, pseudos)) {
|
|
1635
|
+
const text = evaluate(decls.content, {
|
|
1636
|
+
page: toFolioPage(physical, pageValues),
|
|
1637
|
+
pages: pageCount,
|
|
1638
|
+
strings: (n, w) => stringValueAt(byName.get(n) ?? [], physical, parseWhich(w)),
|
|
1639
|
+
targetPage: (u) => {
|
|
1640
|
+
const p = pageAt(u.replace(/^#/, ""));
|
|
1641
|
+
return p === undefined ? undefined : toFolioPage(p, pageValues);
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
if (!text) {
|
|
1645
|
+
if (!flushDiagnosed.has(name)) {
|
|
1646
|
+
flushDiagnosed.add(name);
|
|
1647
|
+
diagnose("engine.flush.margin-box", `@${name} sits on an edge a .gp-flush pin frees, and its content could not be ` + `re-homed into the page (unsupported content value: ${decls.content}). It will ` + `not print on that page — simplify the box's content, or drop .gp-flush there.`);
|
|
1648
|
+
}
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
const r = marginBoxRectPt(name, g);
|
|
1652
|
+
const outDecls = {};
|
|
1653
|
+
for (const [prop, value] of Object.entries(decls)) {
|
|
1654
|
+
if (prop.toLowerCase() === "content" || isIgnoredMarginBoxProperty(prop))
|
|
1655
|
+
continue;
|
|
1656
|
+
outDecls[prop] = value;
|
|
1657
|
+
}
|
|
1658
|
+
boxes.push({
|
|
1659
|
+
box: name,
|
|
1660
|
+
x: (r.x - eff.left) * PXPT,
|
|
1661
|
+
y: (r.y - eff.top) * PXPT,
|
|
1662
|
+
w: r.w * PXPT,
|
|
1663
|
+
h: r.h * PXPT,
|
|
1664
|
+
align: marginBoxAlign(name),
|
|
1665
|
+
text,
|
|
1666
|
+
decls: outDecls
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
items.push({ id: root.id, boxes });
|
|
1670
|
+
}
|
|
1671
|
+
if (items.length)
|
|
1672
|
+
await page.evaluate(`window.__gp.setFlushFurniture(${JSON.stringify(items)})`);
|
|
1673
|
+
}
|
|
1468
1674
|
};
|
|
1469
1675
|
const predicted = await predictPageMap(browser, url, AGENT, VIEWER, {
|
|
1470
1676
|
stringSets: model.stringSets.map((s) => ({ selector: s.selector, name: s.name, value: s.value })),
|
|
@@ -1503,14 +1709,6 @@ ${mapCss}`;
|
|
|
1503
1709
|
log(`tier 3: NOT converged after ${passes} passes`);
|
|
1504
1710
|
}
|
|
1505
1711
|
}
|
|
1506
|
-
const pseudoContexts = pagePseudoContexts(model);
|
|
1507
|
-
const contentHeightPt = (name) => Math.min(...pseudoContexts.map((pseudos) => {
|
|
1508
|
-
const geometry = resolvePage(model, { name, pseudos }).geometry;
|
|
1509
|
-
return geometry.height - geometry.margin.top - geometry.margin.bottom;
|
|
1510
|
-
}));
|
|
1511
|
-
const contentHeightPx = contentHeightPt() * 96 / 72;
|
|
1512
|
-
const namedContentHeightsPx = Object.fromEntries(model.pageNames.map((name) => [name, contentHeightPt(name) * 96 / 72]));
|
|
1513
|
-
const minContentHeightPx = Math.min(contentHeightPt(), ...model.pageNames.map((name) => contentHeightPt(name))) * 96 / 72;
|
|
1514
1712
|
{
|
|
1515
1713
|
const audit = await page.evaluate(`window.__gp.auditContent(${JSON.stringify({
|
|
1516
1714
|
default: contentHeightPx,
|
|
@@ -1564,6 +1762,26 @@ ${mapCss}`;
|
|
|
1564
1762
|
return reasons;
|
|
1565
1763
|
};
|
|
1566
1764
|
|
|
1765
|
+
// Does this box establish the containing block for an absolutely
|
|
1766
|
+
// positioned descendant? Overflow clipping binds an abspos
|
|
1767
|
+
// .gp-pin only from its containing block outward — MEASURED: a
|
|
1768
|
+
// pin whose box lay entirely outside a STATIC overflow:hidden
|
|
1769
|
+
// (and overflow:clip) wrapper printed complete and still behind
|
|
1770
|
+
// the page text; the wrapper's clip never touched it.
|
|
1771
|
+
const establishesAbsContainingBlock = (cs) => {
|
|
1772
|
+
if (cs.position !== "static") return true;
|
|
1773
|
+
for (const prop of [
|
|
1774
|
+
"transform", "translate", "rotate", "scale", "perspective",
|
|
1775
|
+
"filter", "backdropFilter",
|
|
1776
|
+
]) {
|
|
1777
|
+
const value = cs[prop];
|
|
1778
|
+
if (value && value !== "none") return true;
|
|
1779
|
+
}
|
|
1780
|
+
if (/\\b(layout|paint|strict|content)\\b/.test(cs.contain)) return true;
|
|
1781
|
+
if (cs.containerType && cs.containerType !== "normal") return true;
|
|
1782
|
+
return /\\b(transform|translate|rotate|scale|perspective|filter)\\b/.test(cs.willChange);
|
|
1783
|
+
};
|
|
1784
|
+
|
|
1567
1785
|
for (const el of document.querySelectorAll("*")) {
|
|
1568
1786
|
const cs = getComputedStyle(el);
|
|
1569
1787
|
if (leaks.length < 20) {
|
|
@@ -1604,20 +1822,58 @@ ${mapCss}`;
|
|
|
1604
1822
|
) {
|
|
1605
1823
|
const boundary = el.closest(".page, .spread");
|
|
1606
1824
|
if (boundary) {
|
|
1825
|
+
const elRect = el.getBoundingClientRect();
|
|
1826
|
+
// Clip binding: an in-flow .gp-behind is bound by every
|
|
1827
|
+
// ancestor's overflow, but an abspos one only from its
|
|
1828
|
+
// containing block outward — static wrappers in between
|
|
1829
|
+
// never clip it (measured; see the pass comment above).
|
|
1830
|
+
let clipBinds = cs.position !== "absolute" && cs.position !== "fixed";
|
|
1607
1831
|
for (let ancestor = el.parentElement; ancestor; ancestor = ancestor.parentElement) {
|
|
1608
1832
|
const ancestorStyle = getComputedStyle(ancestor);
|
|
1833
|
+
if (!clipBinds && establishesAbsContainingBlock(ancestorStyle)) clipBinds = true;
|
|
1609
1834
|
const reasons = stackingReasons(ancestor, ancestorStyle);
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1835
|
+
// Clipping never reorders layers — it can only CUT the
|
|
1836
|
+
// art (measured, pass comment above): warn only where the
|
|
1837
|
+
// border box crosses a binding ancestor's clip edge on an
|
|
1838
|
+
// axis whose overflow is not \`visible\`. The clip edge is
|
|
1839
|
+
// the padding box, grown by overflow-clip-margin where
|
|
1840
|
+
// that axis's value is \`clip\` (px values only; keyword
|
|
1841
|
+
// forms parse NaN -> 0, i.e. the ungrown padding box).
|
|
1842
|
+
// 1px epsilon: the measured cut lands exactly at the
|
|
1843
|
+
// edge, and sub-pixel layout rounding is not an overhang.
|
|
1844
|
+
const cuts = [];
|
|
1845
|
+
if (clipBinds && (ancestorStyle.overflowX !== "visible" || ancestorStyle.overflowY !== "visible")) {
|
|
1846
|
+
const r = ancestor.getBoundingClientRect();
|
|
1847
|
+
const clipMargin = parseFloat(ancestorStyle.overflowClipMargin) || 0;
|
|
1848
|
+
if (ancestorStyle.overflowX !== "visible") {
|
|
1849
|
+
const grow = ancestorStyle.overflowX === "clip" ? clipMargin : 0;
|
|
1850
|
+
const left = r.left + parseFloat(ancestorStyle.borderLeftWidth) - grow;
|
|
1851
|
+
const right = r.right - parseFloat(ancestorStyle.borderRightWidth) + grow;
|
|
1852
|
+
if (elRect.left < left - 1)
|
|
1853
|
+
cuts.push(Math.round(left - elRect.left) + "px past its left clip edge");
|
|
1854
|
+
if (elRect.right > right + 1)
|
|
1855
|
+
cuts.push(Math.round(elRect.right - right) + "px past its right clip edge");
|
|
1856
|
+
}
|
|
1857
|
+
if (ancestorStyle.overflowY !== "visible") {
|
|
1858
|
+
const grow = ancestorStyle.overflowY === "clip" ? clipMargin : 0;
|
|
1859
|
+
const top = r.top + parseFloat(ancestorStyle.borderTopWidth) - grow;
|
|
1860
|
+
const bottom = r.bottom - parseFloat(ancestorStyle.borderBottomWidth) + grow;
|
|
1861
|
+
if (elRect.top < top - 1)
|
|
1862
|
+
cuts.push(Math.round(top - elRect.top) + "px past its top clip edge");
|
|
1863
|
+
if (elRect.bottom > bottom + 1)
|
|
1864
|
+
cuts.push(Math.round(elRect.bottom - bottom) + "px past its bottom clip edge");
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
if ((reasons.length || cuts.length) && !seenLayerTraps.has(ancestor)) {
|
|
1613
1868
|
seenLayerTraps.add(ancestor);
|
|
1614
1869
|
const effects = [];
|
|
1615
1870
|
if (reasons.length)
|
|
1616
1871
|
effects.push("creates a stacking context (" + reasons.join(", ") + ")");
|
|
1617
|
-
if (
|
|
1872
|
+
if (cuts.length)
|
|
1618
1873
|
effects.push(
|
|
1619
|
-
"clips
|
|
1620
|
-
", overflow-y: " + ancestorStyle.overflowY +
|
|
1874
|
+
"clips it (overflow-x: " + ancestorStyle.overflowX +
|
|
1875
|
+
", overflow-y: " + ancestorStyle.overflowY +
|
|
1876
|
+
") — the art extends " + cuts.join(" and ") + " and is cut off there"
|
|
1621
1877
|
);
|
|
1622
1878
|
layerTraps.push({
|
|
1623
1879
|
behind: desc(el),
|
|
@@ -1685,10 +1941,17 @@ async function printPdf(page) {
|
|
|
1685
1941
|
await page.waitForReady();
|
|
1686
1942
|
return page.printToPDF();
|
|
1687
1943
|
}
|
|
1944
|
+
var MAX_SHRINK = 1.5;
|
|
1945
|
+
function shrinkScale(boxes, limitPx) {
|
|
1946
|
+
const maxRight = Math.max(...boxes.map((o) => o.px + Math.min(0, o.left)));
|
|
1947
|
+
if (!(maxRight > limitPx))
|
|
1948
|
+
return null;
|
|
1949
|
+
return Math.max(1 / MAX_SHRINK, limitPx / maxRight);
|
|
1950
|
+
}
|
|
1688
1951
|
async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreViewport) {
|
|
1689
1952
|
const contexts = [
|
|
1690
1953
|
resolvePage(model),
|
|
1691
|
-
...model.pageNames.filter((n) => !n.startsWith("gp-")).map((n) => resolvePage(model, { name: n }))
|
|
1954
|
+
...model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--flush-")).map((n) => resolvePage(model, { name: n }))
|
|
1692
1955
|
];
|
|
1693
1956
|
const maxContentPt = Math.max(...contexts.map((c) => c.geometry.width - c.geometry.margin.left - c.geometry.margin.right)) + bleedSlugExtensionPt;
|
|
1694
1957
|
const limitPx = maxContentPt * 96 / 72;
|
|
@@ -1775,6 +2038,7 @@ async function predictPageMap(browser, url, agentScript, viewerScript, args, she
|
|
|
1775
2038
|
await page.waitForReady();
|
|
1776
2039
|
await page.evaluate(`window.__GP_MANUAL__ = true;`);
|
|
1777
2040
|
await page.evaluate(viewerScript);
|
|
2041
|
+
await page.evaluate(`window.__gp.flushRoots()`);
|
|
1778
2042
|
await page.evaluate(`window.__gp.stringSources(${JSON.stringify(args.stringSets)})`);
|
|
1779
2043
|
if (args.rectoDecls.length)
|
|
1780
2044
|
await page.evaluate(`window.__gp.forcedBreakSites(${JSON.stringify(args.rectoDecls)})`);
|
|
@@ -596,7 +596,7 @@ var favicon_default = "./favicon-wkbm9cjn.ico";
|
|
|
596
596
|
var manifest_schema_default = "./manifest.schema-zxgxnbg7.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-0ssk8bmm.js";
|
|
600
600
|
|
|
601
601
|
// src/assets/preview/scripts/preview-bridge.js
|
|
602
602
|
var preview_bridge_default = "./preview-bridge-fz7vpk8m.js";
|
|
@@ -608,10 +608,10 @@ var preview_shell_default = "./preview-shell-c5mfa3q0.js";
|
|
|
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-te8g5grx.js";
|
|
612
612
|
|
|
613
613
|
// src/assets/engine/gutterpress-agent.js
|
|
614
|
-
var gutterpress_agent_default = "./gutterpress-agent-
|
|
614
|
+
var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
|
|
615
615
|
|
|
616
616
|
// src/assets/templates/book/manifest.yaml
|
|
617
617
|
var manifest_default = "./manifest-n1gh3qw5.yaml";
|
|
@@ -705,4 +705,47 @@ async function getAssetPath(relPath) {
|
|
|
705
705
|
return join3(root, relPath);
|
|
706
706
|
}
|
|
707
707
|
|
|
708
|
-
|
|
708
|
+
// src/engine/shared/margin-box-support.ts
|
|
709
|
+
var MARGIN_BOX_IGNORED_PROPERTIES = new Set([
|
|
710
|
+
"transform",
|
|
711
|
+
"rotate",
|
|
712
|
+
"translate",
|
|
713
|
+
"scale",
|
|
714
|
+
"box-shadow"
|
|
715
|
+
]);
|
|
716
|
+
function isIgnoredMarginBoxProperty(property) {
|
|
717
|
+
return MARGIN_BOX_IGNORED_PROPERTIES.has(property.toLowerCase());
|
|
718
|
+
}
|
|
719
|
+
function marginBoxRectPt(name, g) {
|
|
720
|
+
const { top, right, bottom, left } = g.margin;
|
|
721
|
+
const cw = g.width - left - right;
|
|
722
|
+
const ch = g.height - top - bottom;
|
|
723
|
+
const third = (n) => n / 3;
|
|
724
|
+
const T = {
|
|
725
|
+
"top-left-corner": [0, 0, left, top],
|
|
726
|
+
"top-left": [left, 0, third(cw), top],
|
|
727
|
+
"top-center": [left + third(cw), 0, third(cw), top],
|
|
728
|
+
"top-right": [left + 2 * third(cw), 0, third(cw), top],
|
|
729
|
+
"top-right-corner": [g.width - right, 0, right, top],
|
|
730
|
+
"bottom-left-corner": [0, g.height - bottom, left, bottom],
|
|
731
|
+
"bottom-left": [left, g.height - bottom, third(cw), bottom],
|
|
732
|
+
"bottom-center": [left + third(cw), g.height - bottom, third(cw), bottom],
|
|
733
|
+
"bottom-right": [left + 2 * third(cw), g.height - bottom, third(cw), bottom],
|
|
734
|
+
"bottom-right-corner": [g.width - right, g.height - bottom, right, bottom],
|
|
735
|
+
"left-top": [0, top, left, third(ch)],
|
|
736
|
+
"left-middle": [0, top + third(ch), left, third(ch)],
|
|
737
|
+
"left-bottom": [0, top + 2 * third(ch), left, third(ch)],
|
|
738
|
+
"right-top": [g.width - right, top, right, third(ch)],
|
|
739
|
+
"right-middle": [g.width - right, top + third(ch), right, third(ch)],
|
|
740
|
+
"right-bottom": [g.width - right, top + 2 * third(ch), right, third(ch)]
|
|
741
|
+
};
|
|
742
|
+
const [x, y, w, h] = T[name] ?? [0, 0, 0, 0];
|
|
743
|
+
return { x, y, w, h };
|
|
744
|
+
}
|
|
745
|
+
function marginBoxAlign(name) {
|
|
746
|
+
if (name.includes("center") || name.includes("middle"))
|
|
747
|
+
return "center";
|
|
748
|
+
return /right/.test(name) ? "end" : "start";
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export { EXIT_CODES, BuildError, run, spawnCapture, execCapture, isToolAvailable, findTool, INSTALL_HINTS, fullInstallHint, resolveChromiumExecutable, requireChromiumExecutable, RENDER_TIMEOUT_MS, prewarmBrowser, getBrowser, closeBrowser, getAssetPath, MARGIN_BOX_IGNORED_PROPERTIES, isIgnoredMarginBoxProperty, marginBoxRectPt, marginBoxAlign, REQUIRED_MILESTONE, assertMilestone, readyProbeExpr, DEFAULT_PRINT_OPTS, launchChromium, connectChromium };
|
package/dist/index.js
CHANGED
|
@@ -140,15 +140,15 @@ import {
|
|
|
140
140
|
validateProjectPlugins,
|
|
141
141
|
verifyRepoReadable,
|
|
142
142
|
writeAppHeartbeat
|
|
143
|
-
} from "./index-
|
|
143
|
+
} from "./index-ge7q9xj3.js";
|
|
144
144
|
import {
|
|
145
145
|
BUILD_DIAGNOSTIC_CODES
|
|
146
|
-
} from "./index-
|
|
146
|
+
} from "./index-wq3r5pj7.js";
|
|
147
147
|
import {
|
|
148
148
|
BuildError,
|
|
149
149
|
DEFAULT_PRINT_OPTS,
|
|
150
150
|
readyProbeExpr
|
|
151
|
-
} from "./index-
|
|
151
|
+
} from "./index-ycpvr0am.js";
|
|
152
152
|
import {
|
|
153
153
|
AUTO_SNAPSHOT_MESSAGE,
|
|
154
154
|
HISTORY_PAGE_LIMIT,
|
|
@@ -22,6 +22,13 @@ export interface BuildRunnerOptions {
|
|
|
22
22
|
skipLint?: boolean;
|
|
23
23
|
skipPreValidate?: boolean;
|
|
24
24
|
skipPostValidate?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Proceed past the engine's over-wide-content check (pdf/pdfx only), which
|
|
27
|
+
* otherwise hard-errors because Chromium silently scales the WHOLE book down
|
|
28
|
+
* to fit the offending box. Each offender is still reported as a warning +
|
|
29
|
+
* diagnostic — this buys an eyes-open build, not a clean one.
|
|
30
|
+
*/
|
|
31
|
+
allowShrink?: boolean;
|
|
25
32
|
/**
|
|
26
33
|
* Keep the pooled headless browser alive after the build returns. A one-shot
|
|
27
34
|
* CLI build leaves this false so the process can exit; a long-lived
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AssetCopy } from "./asset-inline";
|
|
1
2
|
/**
|
|
2
3
|
* `--format html`: ship the self-contained `book.html` (already fully
|
|
3
4
|
* inlined — see `lib/asset-inline.ts`) alongside a copy of the native engine's
|
|
@@ -8,6 +9,56 @@
|
|
|
8
9
|
* in the migration plan).
|
|
9
10
|
*/
|
|
10
11
|
export declare function shipViewerHtml(htmlFile: string, outDir: string): Promise<void>;
|
|
12
|
+
/** The copy plan, handed to `onPlan` before a single byte is written. */
|
|
13
|
+
export interface StagingPlan {
|
|
14
|
+
/** Refs that name no in-project file at all (absolute, or outside the book). */
|
|
15
|
+
unresolved: string[];
|
|
16
|
+
/** How many files the staging is about to copy. */
|
|
17
|
+
copyCount: number;
|
|
18
|
+
}
|
|
19
|
+
/** What `stageBookAssets` could not stage, for the caller to report. */
|
|
20
|
+
export interface StagedAssets {
|
|
21
|
+
/** Output-relative paths whose source file does not exist; a placeholder shipped. */
|
|
22
|
+
missing: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Turn a freshly rendered `book.html` plus its reported asset references into a
|
|
26
|
+
* COMPLETE, self-contained staged book in `outDir` — the exact document both
|
|
27
|
+
* renderers must paginate.
|
|
28
|
+
*
|
|
29
|
+
* THE one implementation: `renderBook` (every real build/export) and the
|
|
30
|
+
* preview/print parity gate both call this, so the gate can never measure a
|
|
31
|
+
* document the build would not have produced. It previously hand-rolled a bare
|
|
32
|
+
* `copyFile` loop, which (a) hard-crashed with a raw `ENOENT` on any book
|
|
33
|
+
* carrying a stale image path — i.e. the tool that enforces preview↔print
|
|
34
|
+
* parity could not run on the real books that need it — and (b) skipped
|
|
35
|
+
* `inlineShapeUrls`, so `.gp-shape` wrapping silently differed from the build.
|
|
36
|
+
*
|
|
37
|
+
* A missing image is NOT fatal: the same magenta placeholder the build ships
|
|
38
|
+
* (see missing-asset-placeholder.ts) is substituted and every reference
|
|
39
|
+
* rewritten to it, so the staged layout matches the build's. What could not be
|
|
40
|
+
* staged is returned, never swallowed — callers decide how loud to be.
|
|
41
|
+
*
|
|
42
|
+
* A ref that resolves to no in-project file at all is a different matter, and
|
|
43
|
+
* the two callers disagree about it: a real build refuses to ship (it throws
|
|
44
|
+
* from `onPlan`, before any bytes are copied), while the gate reports it and
|
|
45
|
+
* measures the book anyway. That is why the plan is handed out rather than
|
|
46
|
+
* judged here.
|
|
47
|
+
*/
|
|
48
|
+
export declare function stageBookAssets(options: {
|
|
49
|
+
/** Project dir every image ref resolves against. */
|
|
50
|
+
renderDir: string;
|
|
51
|
+
/** Directory the staged book is being assembled in. */
|
|
52
|
+
outDir: string;
|
|
53
|
+
/** The rendered `book.html`, rewritten in place. */
|
|
54
|
+
htmlFile: string;
|
|
55
|
+
/** Image `src` values the render reported. */
|
|
56
|
+
imageRefs: Iterable<string>;
|
|
57
|
+
/** CSS images too large to inline, already planned by the render. */
|
|
58
|
+
cssAssets: AssetCopy[];
|
|
59
|
+
/** Called once with the copy plan; throw here to abort before copying. */
|
|
60
|
+
onPlan?: (plan: StagingPlan) => void;
|
|
61
|
+
}): Promise<StagedAssets>;
|
|
11
62
|
/**
|
|
12
63
|
* Create a unique scratch directory under the OS temp dir. Used only for
|
|
13
64
|
* PDF/X intermediates (`raw.pdf`, Ghostscript work files) — never for staging
|
package/dist/lib/engine.d.ts
CHANGED
|
@@ -6,6 +6,15 @@ export interface NativePdfOptions {
|
|
|
6
6
|
title?: string;
|
|
7
7
|
author?: string;
|
|
8
8
|
signature?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Downgrade the engine's over-wide-content hard error to a warning +
|
|
11
|
+
* diagnostic. The engine's message tells the author to "pass allowShrink to
|
|
12
|
+
* build anyway"; without this the advice is unreachable from every product
|
|
13
|
+
* path (only a test and the parity gate could set it). The book still prints
|
|
14
|
+
* at Chromium's mystery shrink scale, which is why this is opt-in per build
|
|
15
|
+
* and never a config default.
|
|
16
|
+
*/
|
|
17
|
+
allowShrink?: boolean;
|
|
9
18
|
}
|
|
10
19
|
/**
|
|
11
20
|
* Render `htmlFile` to `outPdf` via the Gutterpress engine. No HTTP staging,
|
|
@@ -41,6 +41,14 @@
|
|
|
41
41
|
* .gp-pin — pins within the nearest @page/@spread container;
|
|
42
42
|
* centered on both axes unless combined with the edge
|
|
43
43
|
* modifiers .gp-top/.gp-bottom/.gp-left/.gp-right.
|
|
44
|
+
* .gp-flush — with .gp-pin + an edge, the art sits on the PAPER's
|
|
45
|
+
* edge rather than on the text block's. No CSS rule here:
|
|
46
|
+
* the class is a marker both ENGINES implement (see
|
|
47
|
+
* engine/shared/flush.ts), because reaching the paper
|
|
48
|
+
* requires freeing that page's margin — per page — and
|
|
49
|
+
* relocating the furniture that lived in it, neither of
|
|
50
|
+
* which a stylesheet can do. Inert without .gp-pin + an
|
|
51
|
+
* edge word, and inert under plain markdown-it.
|
|
44
52
|
* .gp-bleed — forces its own page (break-before) and spans it
|
|
45
53
|
* edge-to-edge horizontally. This does NOT cancel the
|
|
46
54
|
* top/bottom margins, extend past the trim into printer
|
|
@@ -122,4 +130,4 @@
|
|
|
122
130
|
* carrying gp-left could start shifting; that is standards-tracking per
|
|
123
131
|
* CLAUDE.md ("Chrome wins once it ships"), not a bug in the author's book.
|
|
124
132
|
*/
|
|
125
|
-
export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary. One vocabulary, gp-* only \u2014 the\n pre-vocabulary utility names (.center/.float-left/.float-right/\n .full-width/.full-bleed) were REMOVED when gp-* shipped; books rename\n the classes in their markdown (see the migration note). Source ORDER is\n the contract \u2014 see the doctrine comment above. */\n\n/* flow positions */\n.gp-left {\n float: left;\n margin: 0 var(--gp-gap, 1em) var(--gp-gap, 1em) 0;\n max-width: 50%;\n}\n.gp-right {\n float: right;\n margin: 0 0 var(--gp-gap, 1em) var(--gp-gap, 1em);\n max-width: 50%;\n}\n.gp-center {\n display: block;\n float: none;\n margin-left: auto;\n margin-right: auto;\n max-width: 100%;\n}\n.gp-full {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n@page gp-full-bleed { margin-left: 0; margin-right: 0; }\n.gp-bleed {\n display: block;\n float: none;\n break-before: page;\n page: gp-full-bleed;\n max-width: none;\n width: 100%;\n margin-left: 0;\n margin-right: 0;\n}\n\n/* sizes \u2014 AFTER the flow positions so max-width:100% lifts the floats' 50%\n cap at equal specificity */\n.gp-small { width: 25%; max-width: 100%; }\n.gp-medium { width: 50%; max-width: 100%; }\n.gp-large { width: 75%; max-width: 100%; }\n\n/* float clearance presets \u2014 consumed by var(--gp-gap) in the float rules\n above and by .gp-shape's shape-margin below; --gp-gap itself is\n author-settable CSS */\n.gp-tight { --gp-gap: 0.5em; }\n.gp-loose { --gp-gap: 2em; }\n\n/* column runs \u2014 plain CSS Multi-column, exposed as author vocabulary so\n \"put this in two columns\" does not require borrowing a styled container\n from the book's own component layer. That borrowing is what this exists\n to prevent: a book whose theme paints .section chrome by default gives\n every author who opens a section just to start a column run a panel they\n did not ask for, and the book then needs a reset rule to take it back.\n With a neutral primitive the author opts into columns and nothing else.\n\n Permanent vocabulary, not a shim: Chromium implements multicol natively\n and these rules are the standard properties verbatim, so there is no\n spec gap here to remove later. Deliberately minimal \u2014 column-fill is\n NOT set, because the correct value depends on whether the run fragments\n across pages (auto packs each page's columns; the CSS initial balance is\n right for a run that fits on one page) and only the author knows which.\n --gp-column-gap is author-settable. */\n.gp-columns-2 { columns: 2; column-gap: var(--gp-column-gap, 1.5em); }\n.gp-columns-3 { columns: 3; column-gap: var(--gp-column-gap, 1.5em); }\n\n/* shape wrap \u2014 text follows the image's alpha silhouette instead of its\n rectangular box. shape-outside only applies to floats, so this is inert\n without .gp-left/.gp-right (and under .gp-pin, which un-floats). The\n shape URL cannot be written in CSS (url() contexts can't read attr()),\n so the image renderer rule (images.ts) mirrors the src into an inline\n --gp-shape:url(...) custom property whenever it sees this class --\n authors only ever type the class. threshold 0.2 ignores near-transparent\n anti-aliasing halos; shape-margin shares the float-gap vocabulary. */\nimg.gp-shape {\n shape-outside: var(--gp-shape);\n shape-image-threshold: 0.2;\n shape-margin: var(--gp-gap, 1em);\n}\n\n/* pin \u2014 within the nearest positioned ancestor (.page/.spread, rule above).\n inset:0 and the explicit centers are load-bearing; see doctrine comment. */\n.gp-pin {\n position: absolute;\n inset: 0;\n align-self: center;\n justify-self: center;\n margin: 0;\n max-width: 100%;\n}\n\n/* pin edge modifiers \u2014 AFTER .gp-pin to beat its center defaults;\n justify-self is inert on in-flow floats, so gp-left/gp-right safely do\n double duty as flow float + pin edge */\n.gp-top { align-self: start; }\n.gp-bottom { align-self: end; }\n.gp-left { justify-self: start; }\n.gp-right { justify-self: end; }\n\n/* wrapper-margin neutralization (same pattern and rationale as the\n .gp-bleed paragraph-margin note in the doctrine comment; for pin, the\n emptied paragraph would otherwise leave a phantom margin gap in flow) */\n:where(p:has(> img.gp-bleed:only-child)) { margin: 0; }\n:where(p:has(> img.gp-pin:only-child)) { margin: 0; }\n\n/* depth \u2014 a named ladder for z-index, so books stop hand-tuning bare\n integers. A real book measured 21 z-index declarations using only four\n distinct values (-1, 0, 1, 2), each written literally at its use site.\n The custom properties are the author-settable surface (a book needing a\n deeper stack raises them once); the classes are the shorthand.\n\n NOT named \"layer\": CSS Paged Media 3 \u00A73.1 already defines \"page layers\"\n (page background, canvas, borders, contents, margin boxes) and those are\n parts of the PAGE BOX, not a z-ladder for content. Reusing the word for a\n different concept would collide with the spec vocabulary this project\n tracks. The pin EDGE modifiers already own .gp-top/.gp-bottom, so the\n ladder avoids those words too.\n\n .gp-behind is the one that earns its place: it puts a pinned image UNDER\n the page's text, which is otherwise impossible to express without a bare\n negative z-index. \"Above\" needs no class \u2014 an out-of-flow pin already\n paints above in-flow content.\n\n Two things silently defeat .gp-behind, neither visible at the use site:\n - a stacking context on the .page/.spread ancestor (z-index, isolation,\n opacity, filter, transform on it traps the negative layer inside).\n Core keeps .page/.spread at 'position: relative; z-index: auto'\n precisely so they are not stacking contexts.\n - a clipping ancestor (overflow other than visible), the same mechanism
|
|
133
|
+
export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary. One vocabulary, gp-* only \u2014 the\n pre-vocabulary utility names (.center/.float-left/.float-right/\n .full-width/.full-bleed) were REMOVED when gp-* shipped; books rename\n the classes in their markdown (see the migration note). Source ORDER is\n the contract \u2014 see the doctrine comment above. */\n\n/* flow positions */\n.gp-left {\n float: left;\n margin: 0 var(--gp-gap, 1em) var(--gp-gap, 1em) 0;\n max-width: 50%;\n}\n.gp-right {\n float: right;\n margin: 0 0 var(--gp-gap, 1em) var(--gp-gap, 1em);\n max-width: 50%;\n}\n.gp-center {\n display: block;\n float: none;\n margin-left: auto;\n margin-right: auto;\n max-width: 100%;\n}\n.gp-full {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n@page gp-full-bleed { margin-left: 0; margin-right: 0; }\n.gp-bleed {\n display: block;\n float: none;\n break-before: page;\n page: gp-full-bleed;\n max-width: none;\n width: 100%;\n margin-left: 0;\n margin-right: 0;\n}\n\n/* sizes \u2014 AFTER the flow positions so max-width:100% lifts the floats' 50%\n cap at equal specificity */\n.gp-small { width: 25%; max-width: 100%; }\n.gp-medium { width: 50%; max-width: 100%; }\n.gp-large { width: 75%; max-width: 100%; }\n\n/* float clearance presets \u2014 consumed by var(--gp-gap) in the float rules\n above and by .gp-shape's shape-margin below; --gp-gap itself is\n author-settable CSS */\n.gp-tight { --gp-gap: 0.5em; }\n.gp-loose { --gp-gap: 2em; }\n\n/* column runs \u2014 plain CSS Multi-column, exposed as author vocabulary so\n \"put this in two columns\" does not require borrowing a styled container\n from the book's own component layer. That borrowing is what this exists\n to prevent: a book whose theme paints .section chrome by default gives\n every author who opens a section just to start a column run a panel they\n did not ask for, and the book then needs a reset rule to take it back.\n With a neutral primitive the author opts into columns and nothing else.\n\n Permanent vocabulary, not a shim: Chromium implements multicol natively\n and these rules are the standard properties verbatim, so there is no\n spec gap here to remove later. Deliberately minimal \u2014 column-fill is\n NOT set, because the correct value depends on whether the run fragments\n across pages (auto packs each page's columns; the CSS initial balance is\n right for a run that fits on one page) and only the author knows which.\n --gp-column-gap is author-settable. */\n.gp-columns-2 { columns: 2; column-gap: var(--gp-column-gap, 1.5em); }\n.gp-columns-3 { columns: 3; column-gap: var(--gp-column-gap, 1.5em); }\n\n/* grid runs \u2014 the SLOTTED counterpart to the column runs above. Grid places\n each child into the next cell, across then down (deterministic slots: card\n layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run\n down then across. Same neutral-primitive rationale as .gp-columns-*, and\n permanent vocabulary for the same reason: standard CSS Grid verbatim, no\n spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):\n grid rows fragment across sheets with EXACT print/viewer parity \u2014 2- and\n 3-col, unequal item heights, mid-row cuts, multi-sheet overflow,\n break-inside:avoid, gap geometry \u2014 so a grid taller than the page is safe,\n no fit-one-page constraint. Two things to know, not fix:\n - on a min-height page root (MARKER_CSS), default align-content\n stretches rows apart to fill the page \u2014 identically in both engines.\n Authors wanting packed rows set align-content: start.\n - a @page-break / @column-break marker DIRECTLY inside a grid container\n becomes a grid item and corrupts placement (the one measured parity\n break); markers.js diagnoses it (break_inside_grid).\n --gp-grid-gap is author-settable. */\n.gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n.gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n\n/* shape wrap \u2014 text follows the image's alpha silhouette instead of its\n rectangular box. shape-outside only applies to floats, so this is inert\n without .gp-left/.gp-right (and under .gp-pin, which un-floats). The\n shape URL cannot be written in CSS (url() contexts can't read attr()),\n so the image renderer rule (images.ts) mirrors the src into an inline\n --gp-shape:url(...) custom property whenever it sees this class --\n authors only ever type the class. threshold 0.2 ignores near-transparent\n anti-aliasing halos; shape-margin shares the float-gap vocabulary. */\nimg.gp-shape {\n shape-outside: var(--gp-shape);\n shape-image-threshold: 0.2;\n shape-margin: var(--gp-gap, 1em);\n}\n\n/* pin \u2014 within the nearest positioned ancestor (.page/.spread, rule above).\n inset:0 and the explicit centers are load-bearing; see doctrine comment. */\n.gp-pin {\n position: absolute;\n inset: 0;\n align-self: center;\n justify-self: center;\n margin: 0;\n max-width: 100%;\n}\n\n/* pin edge modifiers \u2014 AFTER .gp-pin to beat its center defaults;\n justify-self is inert on in-flow floats, so gp-left/gp-right safely do\n double duty as flow float + pin edge */\n.gp-top { align-self: start; }\n.gp-bottom { align-self: end; }\n.gp-left { justify-self: start; }\n.gp-right { justify-self: end; }\n\n/* wrapper-margin neutralization (same pattern and rationale as the\n .gp-bleed paragraph-margin note in the doctrine comment; for pin, the\n emptied paragraph would otherwise leave a phantom margin gap in flow) */\n:where(p:has(> img.gp-bleed:only-child)) { margin: 0; }\n:where(p:has(> img.gp-pin:only-child)) { margin: 0; }\n\n/* depth \u2014 a named ladder for z-index, so books stop hand-tuning bare\n integers. A real book measured 21 z-index declarations using only four\n distinct values (-1, 0, 1, 2), each written literally at its use site.\n The custom properties are the author-settable surface (a book needing a\n deeper stack raises them once); the classes are the shorthand.\n\n NOT named \"layer\": CSS Paged Media 3 \u00A73.1 already defines \"page layers\"\n (page background, canvas, borders, contents, margin boxes) and those are\n parts of the PAGE BOX, not a z-ladder for content. Reusing the word for a\n different concept would collide with the spec vocabulary this project\n tracks. The pin EDGE modifiers already own .gp-top/.gp-bottom, so the\n ladder avoids those words too.\n\n .gp-behind is the one that earns its place: it puts a pinned image UNDER\n the page's text, which is otherwise impossible to express without a bare\n negative z-index. \"Above\" needs no class \u2014 an out-of-flow pin already\n paints above in-flow content.\n\n Two things silently defeat .gp-behind, neither visible at the use site:\n - a stacking context on the .page/.spread ancestor (z-index, isolation,\n opacity, filter, transform on it traps the negative layer inside).\n Core keeps .page/.spread at 'position: relative; z-index: auto'\n precisely so they are not stacking contexts.\n - a clipping ancestor (overflow other than visible) \u2014 but only where\n the art actually overhangs that ancestor's clip box on a clipped\n axis: the overhang is cut off, the same mechanism that clips a\n .gp-bleed plate back to the wrapper's width. Clipping never reorders\n layers \u2014 within-bounds art under a clipping .page prints whole and\n still behind (measured; see the build audit's comment in\n engine/compiler/build.ts), and a static wrapper's overflow never\n binds an abspos .gp-pin at all.\n The build-time engine.layer.trapped audit reports both against the live\n ancestor chain. printsafe/page-containment is only an early source hint for\n declarations written directly on .page/.spread. */\n:root {\n --gp-z-behind: -1;\n --gp-z-base: 0;\n --gp-z-raised: 1;\n --gp-z-front: 2;\n}\n.gp-behind { z-index: var(--gp-z-behind); }\n.gp-base { z-index: var(--gp-z-base); }\n.gp-raised { z-index: var(--gp-z-raised); }\n.gp-front { z-index: var(--gp-z-front); }\n";
|