gutterpress 0.11.20-alpha.3 → 0.11.20-alpha.4

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.
Files changed (36) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-a8ycsg80.js → build-epdcrm2c.js} +4 -4
  3. package/dist/{cli-9k2tb493.js → cli-0ey902sx.js} +90 -28
  4. package/dist/{cli-x4p99w8d.js → cli-61vd4ews.js} +83 -41
  5. package/dist/{cli-hn9pk0hp.js → cli-fmksazej.js} +1 -1
  6. package/dist/{cli-y9bcya7r.js → cli-h4tfmpg3.js} +2 -2
  7. package/dist/cli.js +12 -12
  8. package/dist/{doctor-wbjkrcpc.js → doctor-78es5scm.js} +2 -2
  9. package/dist/engine/compiler/build.d.ts +1 -1
  10. package/dist/engine/shared/margin-box-support.d.ts +2 -2
  11. package/dist/engine/shared/pin-edges.d.ts +127 -0
  12. package/dist/engine/shared/print-area.d.ts +68 -0
  13. package/dist/{engine-yxnnp3hx.js → engine-67f68bxs.js} +1 -1
  14. package/dist/{engine-fdtv01a2.js → engine-7ha27n2b.js} +2 -2
  15. package/dist/{ext-pesyrs68.js → ext-9mc7sr0d.js} +4 -4
  16. package/dist/{gutterpress-agent-cazqstr1.js → gutterpress-agent-mrdnwktc.js} +78 -13
  17. package/dist/{gutterpress-viewer-1yse8cx3.js → gutterpress-viewer-jr30yw8t.js} +143 -52
  18. package/dist/{index-r87y7wzw.js → index-ad95fws8.js} +82 -40
  19. package/dist/{index-zagagnwq.js → index-gm5xbx5w.js} +3 -3
  20. package/dist/{index-tjvrchxj.js → index-t13p8y08.js} +199 -137
  21. package/dist/index.js +3 -3
  22. package/dist/lib/extension-manifest.d.ts +13 -0
  23. package/dist/lib/markdown/assemble.d.ts +12 -2
  24. package/dist/lib/markdown/gutterpress-css.d.ts +46 -30
  25. package/dist/lib/markdown/index.d.ts +5 -0
  26. package/dist/{manifest.schema-c9x8912a.json → manifest.schema-13dfax80.json} +3 -3
  27. package/dist/{new-8yah8z91.js → new-s4f0tnya.js} +4 -4
  28. package/dist/{preflight-8ccx4kdc.js → preflight-7ck4rbsq.js} +4 -4
  29. package/dist/preview/file-watcher.d.ts +4 -10
  30. package/dist/{preview-w7v9w36q.js → preview-j91sxadd.js} +4 -4
  31. package/dist/{publish-g5nh5myh.js → publish-f12xaenm.js} +4 -4
  32. package/dist/render.js +57 -6
  33. package/dist/schema/manifest.types.d.ts +8 -6
  34. package/dist/{validate-1yh59x0x.js → validate-pb5gysvr.js} +4 -4
  35. package/package.json +1 -1
  36. package/dist/engine/shared/flush.d.ts +0 -59
@@ -1038,10 +1038,12 @@
1038
1038
  `);
1039
1039
  }
1040
1040
 
1041
- // src/engine/shared/flush.ts
1042
- var FLUSH_EDGES = ["top", "right", "bottom", "left"];
1043
- function flushEdgesIn(root) {
1044
- return FLUSH_EDGES.filter((edge) => root.querySelector(`.gp-pin.gp-flush.gp-${edge}`));
1041
+ // src/engine/shared/pin-edges.ts
1042
+ var PIN_EDGES = ["top", "right", "bottom", "left"];
1043
+ var EDGE_PIN_SELECTOR = PIN_EDGES.map((edge) => `.gp-pin.gp-${edge}`).join(", ");
1044
+ function pinEdgesIn(root) {
1045
+ const pins = Array.from(root.querySelectorAll(EDGE_PIN_SELECTOR)).filter((pin) => pin.offsetParent === root);
1046
+ return PIN_EDGES.filter((edge) => pins.some((pin) => pin.classList.contains(`gp-${edge}`)));
1045
1047
  }
1046
1048
  function flushMargins(margin, edges) {
1047
1049
  return {
@@ -1051,6 +1053,57 @@
1051
1053
  left: edges.includes("left") ? 0 : margin.left
1052
1054
  };
1053
1055
  }
1056
+ function pinInsets(area, side, edges) {
1057
+ const binding = side === "recto" ? "left" : "right";
1058
+ const inset = (edge) => !area || !edges.includes(edge) ? 0 : edge === binding ? area.safe : area.bleed + area.artSafe;
1059
+ return { top: inset("top"), right: inset("right"), bottom: inset("bottom"), left: inset("left") };
1060
+ }
1061
+ var NO_PADDING = { top: 0, right: 0, bottom: 0, left: 0 };
1062
+ function pinRootStyle(margin, area, side, edges, padding = NO_PADDING) {
1063
+ const px = (pt) => pt * 96 / 72;
1064
+ const inset = pinInsets(area, side, edges);
1065
+ const style = { "box-decoration-break": "clone" };
1066
+ for (const edge of PIN_EDGES)
1067
+ if (edges.includes(edge))
1068
+ style[`padding-${edge}`] = `${px(margin[edge]) + padding[edge]}px`;
1069
+ for (const edge of PIN_EDGES)
1070
+ style[`--gp-pin-${edge}`] = `${px(inset[edge])}px`;
1071
+ return style;
1072
+ }
1073
+ function paddingOf(cs) {
1074
+ return {
1075
+ top: parseFloat(cs.paddingTop) || 0,
1076
+ right: parseFloat(cs.paddingRight) || 0,
1077
+ bottom: parseFloat(cs.paddingBottom) || 0,
1078
+ left: parseFloat(cs.paddingLeft) || 0
1079
+ };
1080
+ }
1081
+
1082
+ // src/engine/shared/print-area.ts
1083
+ function printAreaFromMeta(doc) {
1084
+ const raw = doc.querySelector('meta[name="gp-print-area"]')?.getAttribute("content");
1085
+ if (!raw)
1086
+ return null;
1087
+ try {
1088
+ const v = JSON.parse(raw);
1089
+ const n = (x) => typeof x === "number" && Number.isFinite(x) && x > 0 ? x : 0;
1090
+ if (!n(v.width) || !n(v.height))
1091
+ return null;
1092
+ const safe = n(v.safe);
1093
+ const spec = v.spec && typeof v.spec.label === "string" && typeof v.spec.url === "string" && /^https?:\/\//i.test(v.spec.url) ? v.spec : undefined;
1094
+ return {
1095
+ width: n(v.width),
1096
+ height: n(v.height),
1097
+ bleed: n(v.bleed),
1098
+ safe,
1099
+ artSafe: typeof v.artSafe === "number" ? n(v.artSafe) : safe,
1100
+ preset: typeof v.preset === "string" ? v.preset : undefined,
1101
+ spec
1102
+ };
1103
+ } catch {
1104
+ return null;
1105
+ }
1106
+ }
1054
1107
 
1055
1108
  // src/engine/viewer/fragment.ts
1056
1109
  var PX_PER_PT = 96 / 72;
@@ -1210,13 +1263,29 @@
1210
1263
  }
1211
1264
  return false;
1212
1265
  }
1213
- function pushRun(runs, page, nodes, flushEdges) {
1214
- const key = (r) => `${r.page ?? ""}\x00${(r.flushEdges ?? []).join(",")}`;
1266
+ function pushRun(runs, page, nodes, pin) {
1267
+ const key = (r) => `${r.page ?? ""}\x00${(r.pin?.edges ?? []).join(",")}`;
1215
1268
  const last = runs[runs.length - 1];
1216
- if (last && key(last) === key({ page, flushEdges }))
1269
+ if (last && key(last) === key({ page, pin })) {
1217
1270
  last.nodes.push(...nodes);
1218
- else
1219
- runs.push({ page, nodes, flushEdges: flushEdges?.length ? flushEdges : undefined });
1271
+ if (pin)
1272
+ last.pin.roots.push(...pin.roots);
1273
+ } else
1274
+ runs.push({ page, nodes, pin: pin && { edges: pin.edges, roots: [...pin.roots] } });
1275
+ }
1276
+ var PAGE_ROOT = ".page, .spread";
1277
+ var ownPadding = new WeakMap;
1278
+ function pinRunOf(el) {
1279
+ if (!el.matches(PAGE_ROOT))
1280
+ return;
1281
+ const edges = pinEdgesIn(el);
1282
+ return edges.length ? { edges, roots: [el] } : undefined;
1283
+ }
1284
+ function holdsPinRoot(el) {
1285
+ return Array.from(el.querySelectorAll(EDGE_PIN_SELECTOR)).some((pin) => {
1286
+ const root = pin.offsetParent;
1287
+ return !!root && root !== el && el.contains(root) && root.matches(PAGE_ROOT);
1288
+ });
1220
1289
  }
1221
1290
  function explodeChildren(container, model) {
1222
1291
  const runs = [];
@@ -1228,7 +1297,7 @@
1228
1297
  pending = [];
1229
1298
  const last = runs[runs.length - 1];
1230
1299
  if (last) {
1231
- pushRun(runs, last.page, held, last.flushEdges);
1300
+ pushRun(runs, last.page, held, last.pin && { edges: last.pin.edges, roots: [] });
1232
1301
  return [];
1233
1302
  }
1234
1303
  if (held.some((n) => (n.textContent ?? "").trim() !== "")) {
@@ -1245,19 +1314,19 @@
1245
1314
  const kid = node;
1246
1315
  if (kid.classList.contains("gp-layer"))
1247
1316
  continue;
1248
- const flush = flushEdgesIn(kid);
1317
+ const pin = pinRunOf(kid);
1249
1318
  const own = directPageName(kid, model);
1250
1319
  if (own !== undefined) {
1251
- pushRun(runs, own, [...carry(), kid], flush);
1320
+ pushRun(runs, own, [...carry(), kid], pin);
1252
1321
  continue;
1253
1322
  }
1254
- if (!hasDescendantPageAssignment(kid, model)) {
1255
- pushRun(runs, undefined, [...carry(), kid], flush);
1323
+ if (!hasDescendantPageAssignment(kid, model) && !holdsPinRoot(kid)) {
1324
+ pushRun(runs, undefined, [...carry(), kid], pin);
1256
1325
  continue;
1257
1326
  }
1258
1327
  const inner = explodeChildren(kid, model);
1259
1328
  if (inner.length <= 1) {
1260
- pushRun(runs, inner[0]?.page, [...carry(), kid]);
1329
+ pushRun(runs, inner[0]?.page, [...carry(), kid], pin ?? inner[0]?.pin);
1261
1330
  continue;
1262
1331
  }
1263
1332
  let lead = carry();
@@ -1266,7 +1335,7 @@
1266
1335
  for (const n of r.nodes)
1267
1336
  shell.appendChild(n);
1268
1337
  kid.before(shell);
1269
- pushRun(runs, r.page, [...lead, shell], flushEdgesIn(shell));
1338
+ pushRun(runs, r.page, [...lead, shell], pin ? pinRunOf(shell) : r.pin);
1270
1339
  lead = [];
1271
1340
  }
1272
1341
  kid.remove();
@@ -1323,6 +1392,42 @@
1323
1392
  delete el.dataset.gpLeadingPageRootDisplayPriority;
1324
1393
  }
1325
1394
  }
1395
+ function fitVersoPinRoots(model, strips) {
1396
+ const area = printAreaFromMeta(document);
1397
+ const versos = [];
1398
+ for (const strip of strips) {
1399
+ if (!strip.pinEdges || !strip.pinRoots?.length)
1400
+ continue;
1401
+ const edges = strip.pinEdges;
1402
+ const { left, agree } = pageSides(model, strip.page);
1403
+ const verso = agree ? left.margin : strip.geometry.margin;
1404
+ const margin = { ...strip.geometry.margin, left: verso.left, right: verso.right };
1405
+ const shift = pt(strip.versoShift);
1406
+ for (const root of strip.pinRoots) {
1407
+ if (pageOf(root, strips) % 2 !== 1)
1408
+ continue;
1409
+ const style = pinRootStyle(margin, area, "verso", edges, ownPadding.get(root));
1410
+ if (shift && edges.includes("left"))
1411
+ style["margin-left"] = `${shift}px`;
1412
+ if (shift && edges.includes("right"))
1413
+ style["margin-right"] = `${-shift}px`;
1414
+ versos.push({ root, style });
1415
+ }
1416
+ }
1417
+ for (const { root, style } of versos)
1418
+ for (const [prop, value] of Object.entries(style))
1419
+ root.style.setProperty(prop, value);
1420
+ }
1421
+ function restorePinRoots(doc = document) {
1422
+ for (const el of Array.from(doc.querySelectorAll("[data-gp-pin-style]"))) {
1423
+ const style = el.dataset.gpPinStyle;
1424
+ if (style)
1425
+ el.setAttribute("style", style);
1426
+ else
1427
+ el.removeAttribute("style");
1428
+ delete el.dataset.gpPinStyle;
1429
+ }
1430
+ }
1326
1431
  var SCROLLABLE = /^(auto|scroll|hidden)$/;
1327
1432
  function makeOverflowFragmentable(strips) {
1328
1433
  const todo = [];
@@ -1388,21 +1493,16 @@
1388
1493
  agree: Math.abs(r.w - l.w) < 0.01 && Math.abs(r.h - l.h) < 0.01
1389
1494
  };
1390
1495
  }
1391
- function versoShiftPt(model, name, flushEdges = []) {
1496
+ function versoShiftPt(model, name) {
1392
1497
  const { right, left, agree } = pageSides(model, name);
1393
- if (!agree)
1394
- return 0;
1395
- const r = flushMargins(right.margin, flushEdges);
1396
- const l = flushMargins(left.margin, flushEdges);
1397
- if (Math.abs(r.left + r.right - (l.left + l.right)) >= 0.01)
1398
- return 0;
1399
- return r.left - l.left;
1498
+ return agree ? right.margin.left - left.margin.left : 0;
1400
1499
  }
1401
1500
  function buildStrips(model, opts = {}, warnings = []) {
1402
1501
  const doc = document;
1403
1502
  const root = opts.root ?? doc.querySelector("main") ?? doc.body;
1404
1503
  const gap = opts.sheetGap ?? 24;
1405
1504
  const runs = explodeChildren(root, model);
1505
+ const area = printAreaFromMeta(doc);
1406
1506
  const strips = [];
1407
1507
  for (const run of runs) {
1408
1508
  const geometry = runPageBox(model, run.page, warnings);
@@ -1410,7 +1510,18 @@
1410
1510
  strip.className = "gp-strip";
1411
1511
  if (run.page)
1412
1512
  strip.dataset.page = run.page;
1413
- const margin = run.flushEdges?.length ? flushMargins(geometry.margin, run.flushEdges) : geometry.margin;
1513
+ const margin = run.pin ? flushMargins(geometry.margin, run.pin.edges) : geometry.margin;
1514
+ if (run.pin) {
1515
+ const { edges, roots } = run.pin;
1516
+ for (const pinRoot of roots)
1517
+ ownPadding.set(pinRoot, paddingOf(getComputedStyle(pinRoot)));
1518
+ for (const pinRoot of roots) {
1519
+ pinRoot.dataset.gpPinStyle ??= pinRoot.getAttribute("style") ?? "";
1520
+ const recto = pinRootStyle(geometry.margin, area, "recto", edges, ownPadding.get(pinRoot));
1521
+ for (const [prop, value] of Object.entries(recto))
1522
+ pinRoot.style.setProperty(prop, value);
1523
+ }
1524
+ }
1414
1525
  const w = pt(geometry.width - margin.left - margin.right);
1415
1526
  const h = pt(geometry.height - margin.top - margin.bottom);
1416
1527
  strip.style.setProperty("--gp-content-w", `${w}px`);
@@ -1422,7 +1533,7 @@
1422
1533
  strip.style.setProperty("--gp-margin-right", `${pt(margin.right)}px`);
1423
1534
  strip.style.setProperty("--gp-margin-bottom", `${pt(margin.bottom)}px`);
1424
1535
  strip.style.setProperty("--gp-margin-left", `${pt(margin.left)}px`);
1425
- const rawShift = versoShiftPt(model, run.page, run.flushEdges);
1536
+ const rawShift = versoShiftPt(model, run.page);
1426
1537
  const versoShift = Math.abs(rawShift) >= 0.01 ? rawShift : 0;
1427
1538
  if (versoShift)
1428
1539
  strip.style.setProperty("--gp-verso-shift", `${pt(versoShift)}px`);
@@ -1433,7 +1544,8 @@
1433
1544
  el: strip,
1434
1545
  page: run.page,
1435
1546
  geometry,
1436
- flushEdges: run.flushEdges,
1547
+ pinEdges: run.pin?.edges,
1548
+ pinRoots: run.pin?.roots,
1437
1549
  versoShift,
1438
1550
  pages: 0,
1439
1551
  offset: 0
@@ -1805,6 +1917,7 @@
1805
1917
  const headers = opts.compensateHeaders === false ? { tables: 0, passes: 0, warnings: [] } : compensateRepeatedHeaders(strips);
1806
1918
  const contentPages = measure(strips).totalPages;
1807
1919
  const totalPages = padToSignature(strips, contentPages);
1920
+ fitVersoPinRoots(model, strips);
1808
1921
  const api = {
1809
1922
  model,
1810
1923
  strips,
@@ -1818,6 +1931,7 @@
1818
1931
  relayout: () => {
1819
1932
  restoreFullHeightPageRoots();
1820
1933
  restoreScrollContainers();
1934
+ restorePinRoots();
1821
1935
  unwrapStrips(strips);
1822
1936
  for (const spacer of Array.from(document.querySelectorAll(".gp-recto-spacer, .gp-pad-spacer")))
1823
1937
  spacer.remove();
@@ -1836,6 +1950,7 @@
1836
1950
  const content = measure(strips).totalPages;
1837
1951
  padToSignature(strips, content);
1838
1952
  const r = measure(strips);
1953
+ fitVersoPinRoots(model, strips);
1839
1954
  api.totalPages = r.totalPages;
1840
1955
  api.blankPageIndices = blankPageIndices(strips);
1841
1956
  api.paddedPageIndices = pagesAfter(content, r.totalPages);
@@ -2182,30 +2297,6 @@
2182
2297
  artSafePt: 0,
2183
2298
  counts: { text: 0, art: 0, bleed: 0, offPage: 0, intended: 0 }
2184
2299
  };
2185
- function printAreaFromMeta() {
2186
- const raw = document.querySelector('meta[name="gp-print-area"]')?.getAttribute("content");
2187
- if (!raw)
2188
- return null;
2189
- try {
2190
- const v = JSON.parse(raw);
2191
- const n = (x) => typeof x === "number" && Number.isFinite(x) && x > 0 ? x : 0;
2192
- if (!n(v.width) || !n(v.height))
2193
- return null;
2194
- const safe = n(v.safe);
2195
- const spec = v.spec && typeof v.spec.label === "string" && typeof v.spec.url === "string" && /^https?:\/\//i.test(v.spec.url) ? v.spec : undefined;
2196
- return {
2197
- width: n(v.width),
2198
- height: n(v.height),
2199
- bleed: n(v.bleed),
2200
- safe,
2201
- artSafe: typeof v.artSafe === "number" ? n(v.artSafe) : safe,
2202
- preset: typeof v.preset === "string" ? v.preset : undefined,
2203
- spec
2204
- };
2205
- } catch {
2206
- return null;
2207
- }
2208
- }
2209
2300
  function sheetArea(sheet) {
2210
2301
  const px = (name) => parseFloat(sheet.style.getPropertyValue(name)) || 0;
2211
2302
  return { width: px("--gp-page-w") / PX_PER_PT, height: px("--gp-page-h") / PX_PER_PT, bleed: 0, safe: 0, artSafe: 0 };
@@ -2689,7 +2780,7 @@
2689
2780
  let overlayPages = [];
2690
2781
  let overlaySummary = OVERLAY_OFF;
2691
2782
  const paintOverlayNow = () => {
2692
- overlaySummary = paintOverlay(overlayPages, layout.strips, printAreaFromMeta(), overlayLayers);
2783
+ overlaySummary = paintOverlay(overlayPages, layout.strips, printAreaFromMeta(document), overlayLayers);
2693
2784
  window.dispatchEvent(new CustomEvent("gp:overlay", { detail: overlaySummary }));
2694
2785
  };
2695
2786
  const api = {
@@ -19,7 +19,7 @@ import {
19
19
  stringValueAt,
20
20
  toFolioPage,
21
21
  wantsRecto
22
- } from "./index-zagagnwq.js";
22
+ } from "./index-gm5xbx5w.js";
23
23
  import {
24
24
  launchChromium
25
25
  } from "./index-bt0qws5c.js";
@@ -29,14 +29,15 @@ import { readFile } from "node:fs/promises";
29
29
  import { resolve } from "node:path";
30
30
  import { pathToFileURL } from "node:url";
31
31
 
32
- // src/engine/shared/flush.ts
33
- var FLUSH_EDGES = ["top", "right", "bottom", "left"];
34
- function flushKey(edges) {
35
- return FLUSH_EDGES.filter((e) => edges.includes(e)).map((e) => e[0]).join("");
32
+ // src/engine/shared/pin-edges.ts
33
+ var PIN_EDGES = ["top", "right", "bottom", "left"];
34
+ var EDGE_PIN_SELECTOR = PIN_EDGES.map((edge) => `.gp-pin.gp-${edge}`).join(", ");
35
+ function pinKey(edges) {
36
+ return PIN_EDGES.filter((e) => edges.includes(e)).map((e) => e[0]).join("");
36
37
  }
37
- function flushPageName(authorPage, edges) {
38
+ function pinPageName(authorPage, edges) {
38
39
  const safe = authorPage ? authorPage.replace(/[^A-Za-z0-9_-]/g, "_") : "";
39
- return `gp--flush${safe ? `-${safe}` : ""}-${flushKey(edges)}`;
40
+ return `gp--pin${safe ? `-${safe}` : ""}-${pinKey(edges)}`;
40
41
  }
41
42
  function flushMargins(margin, edges) {
42
43
  return {
@@ -46,6 +47,23 @@ function flushMargins(margin, edges) {
46
47
  left: edges.includes("left") ? 0 : margin.left
47
48
  };
48
49
  }
50
+ function pinInsets(area, side, edges) {
51
+ const binding = side === "recto" ? "left" : "right";
52
+ const inset = (edge) => !area || !edges.includes(edge) ? 0 : edge === binding ? area.safe : area.bleed + area.artSafe;
53
+ return { top: inset("top"), right: inset("right"), bottom: inset("bottom"), left: inset("left") };
54
+ }
55
+ var NO_PADDING = { top: 0, right: 0, bottom: 0, left: 0 };
56
+ function pinRootStyle(margin, area, side, edges, padding = NO_PADDING) {
57
+ const px = (pt) => pt * 96 / 72;
58
+ const inset = pinInsets(area, side, edges);
59
+ const style = { "box-decoration-break": "clone" };
60
+ for (const edge of PIN_EDGES)
61
+ if (edges.includes(edge))
62
+ style[`padding-${edge}`] = `${px(margin[edge]) + padding[edge]}px`;
63
+ for (const edge of PIN_EDGES)
64
+ style[`--gp-pin-${edge}`] = `${px(inset[edge])}px`;
65
+ return style;
66
+ }
49
67
  function marginBoxesOnEdges(edges) {
50
68
  const owners = {
51
69
  "top-left-corner": ["top", "left"],
@@ -723,7 +741,7 @@ var BUILD_DIAGNOSTIC_CODES = [
723
741
  "engine.multicol.dead-column",
724
742
  "engine.content.overheight",
725
743
  "engine.image.low-dpi",
726
- "engine.flush.margin-box",
744
+ "engine.pin.margin-box",
727
745
  "engine.page-background.unreferenced",
728
746
  "engine.hyphenation.unsupported"
729
747
  ];
@@ -756,25 +774,31 @@ async function build(opts) {
756
774
  await page.send("Emulation.setDeviceMetricsOverride", sheetViewport);
757
775
  await page.send("Emulation.setEmulatedMedia", { media: "print" });
758
776
  log(`print media emulated for audits and measurement`);
759
- const flushRoots = await page.evaluate(`window.__gp.flushRoots()`);
760
- const flushGroups = new Map;
761
- for (const root of flushRoots) {
762
- if (flushGroups.has(root.key))
777
+ const pinRoots = await page.evaluate(`window.__gp.pinRoots()`);
778
+ const pinGroups = new Map;
779
+ for (const root of pinRoots) {
780
+ if (pinGroups.has(root.key))
763
781
  continue;
764
782
  const authorPage = root.page === "auto" ? undefined : root.page;
765
- flushGroups.set(root.key, {
783
+ pinGroups.set(root.key, {
766
784
  authorPage,
767
785
  edges: root.edges,
768
- genName: flushPageName(authorPage, root.edges),
786
+ padding: root.padding,
787
+ genName: pinPageName(authorPage, root.edges),
769
788
  key: root.key
770
789
  });
771
790
  }
772
- if (flushGroups.size) {
791
+ const printArea = pinRoots.length ? await page.evaluate(`window.__gp.printArea()`) : null;
792
+ const pinStyle = (group, pseudos) => pinRootStyle(resolvePage(model, { name: group.authorPage, pseudos }).geometry.margin, printArea, pseudos.includes("left") ? "verso" : "recto", group.edges, group.padding);
793
+ if (pinGroups.size) {
773
794
  const lines = [];
774
- for (const group of flushGroups.values()) {
795
+ const aliased = new Set;
796
+ for (const group of pinGroups.values()) {
797
+ const alias = !aliased.has(group.genName);
798
+ aliased.add(group.genName);
775
799
  const relocated = new Set(marginBoxesOnEdges(group.edges));
776
800
  const pseudoSets = [[]];
777
- if (group.authorPage) {
801
+ if (alias && group.authorPage) {
778
802
  for (const rule of model.pageRules) {
779
803
  if (rule.name !== group.authorPage)
780
804
  continue;
@@ -798,7 +822,7 @@ ${body.join(`
798
822
  }`);
799
823
  }
800
824
  }
801
- for (const pseudos of pseudoSets) {
825
+ for (const pseudos of alias ? pseudoSets : []) {
802
826
  const pseudo = pseudos.length ? `:${pseudos.join(":")}` : "";
803
827
  const body = [];
804
828
  for (const edge of group.edges)
@@ -810,14 +834,15 @@ ${body.join(`
810
834
  `)}
811
835
  }`);
812
836
  }
813
- lines.push(`:where(.page, .spread)[data-gp-flush="${group.key}"][data-gp-flush][data-gp-flush][data-gp-flush] { page: ${group.genName}; }`);
837
+ const recto = Object.entries(pinStyle(group, ["right"])).map(([prop, value]) => ` ${prop}: ${value};`).join("");
838
+ lines.push(`:where(.page, .spread)[data-gp-pin="${group.key}"][data-gp-pin][data-gp-pin][data-gp-pin] { page: ${group.genName};${recto} }`);
814
839
  }
815
- const flushCss = lines.join(`
840
+ const pinCss = lines.join(`
816
841
  `);
817
- await page.evaluate(`window.__gp.addCss("gp-flush-css", ${JSON.stringify(flushCss)})`);
842
+ await page.evaluate(`window.__gp.addCss("gp-pin-css", ${JSON.stringify(pinCss)})`);
818
843
  model = extract(`${cssText}
819
- ${flushCss}`);
820
- log(`.gp-flush: ${flushRoots.length} pinned root(s) -> ${flushGroups.size} generated page context(s)`);
844
+ ${pinCss}`);
845
+ log(`pinned art: ${pinRoots.length} page root(s) -> ${pinGroups.size} generated page context(s)`);
821
846
  }
822
847
  const { tier3Reasons } = classify(model);
823
848
  const pseudoContexts = pagePseudoContexts(model);
@@ -842,11 +867,16 @@ ${flushCss}`);
842
867
  return !!c && c !== "none" && c !== "normal";
843
868
  });
844
869
  };
845
- const furnitureRoots = flushRoots.filter((root) => {
846
- const group = flushGroups.get(root.key);
870
+ const furnitureRoots = pinRoots.filter((root) => {
871
+ const group = pinGroups.get(root.key);
847
872
  return pseudoContexts.some((pseudos) => relocationBoxes(group, pseudos).length > 0);
848
873
  });
849
- const flushDiagnosed = new Set;
874
+ const relocationDiagnosed = new Set;
875
+ const sideRoots = pinRoots.filter((root) => {
876
+ const group = pinGroups.get(root.key);
877
+ const recto = JSON.stringify(pinStyle(group, ["right"]));
878
+ return [["left"], ["right", "first"]].some((pseudos) => JSON.stringify(pinStyle(group, pseudos)) !== recto);
879
+ });
850
880
  const tier2 = synthesize({
851
881
  model,
852
882
  marks: opts.marks,
@@ -890,7 +920,7 @@ ${describe(widthOffenders.intrinsics)}`;
890
920
  log(`WARNING: ${msg}`);
891
921
  }
892
922
  const rectoDecls = model.breaks.filter(isRectoVersoBreak);
893
- const needsMeasure = tier3Reasons.length > 0 || consumedStrings(model).size > 0 || rectoDecls.length > 0 || model.counterResets.length > 0 || furnitureRoots.length > 0;
923
+ const needsMeasure = tier3Reasons.length > 0 || consumedStrings(model).size > 0 || rectoDecls.length > 0 || model.counterResets.length > 0 || furnitureRoots.length > 0 || sideRoots.length > 0;
894
924
  let tier = tier2.geometry.bleed > 0 || tier2.geometry.slug > 0 ? 2 : 1;
895
925
  let passes = 1;
896
926
  let pageMap = {};
@@ -918,7 +948,7 @@ ${describe(widthOffenders.intrinsics)}`;
918
948
  targets.add(s.id);
919
949
  for (const s of resetSites)
920
950
  targets.add(s.id);
921
- for (const r of furnitureRoots)
951
+ for (const r of [...furnitureRoots, ...sideRoots])
922
952
  targets.add(r.id);
923
953
  await page.evaluate(`window.__gp.instrument(${JSON.stringify([...targets])})`);
924
954
  const targetText = await page.evaluate(`window.__gp.targetTexts(${JSON.stringify([...targets])})`);
@@ -1004,8 +1034,12 @@ ${describe(widthOffenders.intrinsics)}`;
1004
1034
  /* Tier 3 */
1005
1035
  ${mapCss}`;
1006
1036
  }
1037
+ const pageAt = (id) => map[id] ?? predictedForResult?.pageMap[id];
1038
+ const pseudosAt = (physical) => [
1039
+ physical % 2 === 1 ? "right" : "left",
1040
+ ...physical === 1 ? ["first"] : []
1041
+ ];
1007
1042
  if (furnitureRoots.length) {
1008
- const pageAt = (id) => map[id] ?? predictedForResult?.pageMap[id];
1009
1043
  const byName = new Map;
1010
1044
  for (const s of sources) {
1011
1045
  const p = pageAt(s.id);
@@ -1023,10 +1057,8 @@ ${mapCss}`;
1023
1057
  const physical = pageAt(root.id);
1024
1058
  if (!physical)
1025
1059
  continue;
1026
- const group = flushGroups.get(root.key);
1027
- const pseudos = [physical % 2 === 1 ? "right" : "left"];
1028
- if (physical === 1)
1029
- pseudos.push("first");
1060
+ const group = pinGroups.get(root.key);
1061
+ const pseudos = pseudosAt(physical);
1030
1062
  const ctx = resolvePage(model, { name: group.authorPage, pseudos });
1031
1063
  const g = ctx.geometry;
1032
1064
  const eff = flushMargins(g.margin, group.edges);
@@ -1042,9 +1074,9 @@ ${mapCss}`;
1042
1074
  }
1043
1075
  });
1044
1076
  if (!text) {
1045
- if (!flushDiagnosed.has(name)) {
1046
- flushDiagnosed.add(name);
1047
- 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.`);
1077
+ if (!relocationDiagnosed.has(name)) {
1078
+ relocationDiagnosed.add(name);
1079
+ diagnose("engine.pin.margin-box", `@${name} sits on an edge a pinned image 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 pin the image to another edge.`);
1048
1080
  }
1049
1081
  continue;
1050
1082
  }
@@ -1069,7 +1101,17 @@ ${mapCss}`;
1069
1101
  items.push({ id: root.id, boxes });
1070
1102
  }
1071
1103
  if (items.length)
1072
- await page.evaluate(`window.__gp.setFlushFurniture(${JSON.stringify(items)})`);
1104
+ await page.evaluate(`window.__gp.setPinFurniture(${JSON.stringify(items)})`);
1105
+ }
1106
+ if (sideRoots.length) {
1107
+ const items = sideRoots.flatMap((root) => {
1108
+ const physical = pageAt(root.id);
1109
+ if (!physical)
1110
+ return [];
1111
+ return [{ id: root.id, style: pinStyle(pinGroups.get(root.key), pseudosAt(physical)) }];
1112
+ });
1113
+ if (items.length)
1114
+ await page.evaluate(`window.__gp.setPinGeometry(${JSON.stringify(items)})`);
1073
1115
  }
1074
1116
  };
1075
1117
  const predicted = await predictPageMap(browser, url, AGENT, VIEWER, {
@@ -1432,12 +1474,12 @@ function shrinkScale(boxes, limitPx) {
1432
1474
  async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreViewport) {
1433
1475
  const contexts = [
1434
1476
  resolvePage(model),
1435
- ...model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--flush-")).map((n) => resolvePage(model, { name: n }))
1477
+ ...model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--pin-")).map((n) => resolvePage(model, { name: n }))
1436
1478
  ];
1437
1479
  const contentPxOf = (c) => (c.geometry.width - c.geometry.margin.left - c.geometry.margin.right + bleedSlugExtensionPt) * 96 / 72;
1438
1480
  const defaultLimitPx = contentPxOf(contexts[0]);
1439
1481
  const namedLimits = {};
1440
- model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--flush-")).forEach((n) => {
1482
+ model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--pin-")).forEach((n) => {
1441
1483
  namedLimits[n] = contentPxOf(resolvePage(model, { name: n }));
1442
1484
  });
1443
1485
  const widths = [...new Set([defaultLimitPx, ...Object.values(namedLimits)])].sort((a, b) => a - b);
@@ -1624,7 +1666,7 @@ async function predictPageMap(browser, url, agentScript, viewerScript, args, she
1624
1666
  await page.waitForReady();
1625
1667
  await page.evaluate(`window.__GP_MANUAL__ = true;`);
1626
1668
  await page.evaluate(viewerScript);
1627
- await page.evaluate(`window.__gp.flushRoots()`);
1669
+ await page.evaluate(`window.__gp.pinRoots()`);
1628
1670
  await page.evaluate(`window.__gp.stringSources(${JSON.stringify(args.stringSets)})`);
1629
1671
  if (args.rectoDecls.length)
1630
1672
  await page.evaluate(`window.__gp.forcedBreakSites(${JSON.stringify(args.rectoDecls)})`);
@@ -148,7 +148,7 @@ import { fileURLToPath } from "node:url";
148
148
  var favicon_default = "./favicon-272ga6y0.ico";
149
149
 
150
150
  // src/assets/manifest.schema.json
151
- var manifest_schema_default = "./manifest.schema-c9x8912a.json";
151
+ var manifest_schema_default = "./manifest.schema-13dfax80.json";
152
152
 
153
153
  // src/assets/preview/scripts/preview-interface.js
154
154
  var preview_interface_default = "./preview-interface-nnmrxyqm.js";
@@ -163,10 +163,10 @@ var preview_shell_default = "./preview-shell-wk9k3gcd.js";
163
163
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
164
164
 
165
165
  // src/assets/engine/gutterpress-viewer.js
166
- var gutterpress_viewer_default = "./gutterpress-viewer-1yse8cx3.js";
166
+ var gutterpress_viewer_default = "./gutterpress-viewer-jr30yw8t.js";
167
167
 
168
168
  // src/assets/engine/gutterpress-agent.js
169
- var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
169
+ var gutterpress_agent_default = "./gutterpress-agent-mrdnwktc.js";
170
170
 
171
171
  // src/assets/templates/book/manifest.yaml
172
172
  var manifest_default = "./manifest-n1gh3qw5.yaml";