gutterpress 0.11.20-alpha.2 → 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 (43) hide show
  1. package/dist/api/index.js +3 -2
  2. package/dist/{build-qmjg9r2d.js → build-epdcrm2c.js} +5 -4
  3. package/dist/{cli-zacevwcy.js → cli-0ey902sx.js} +267 -455
  4. package/dist/{cli-j802s31j.js → cli-61vd4ews.js} +83 -41
  5. package/dist/{cli-7z29z4wz.js → cli-fmksazej.js} +1 -1
  6. package/dist/{cli-gh0ym80r.js → cli-h4tfmpg3.js} +121 -20
  7. package/dist/cli-sth219v1.js +418 -0
  8. package/dist/cli.js +12 -12
  9. package/dist/{doctor-fe7tcs4v.js → doctor-78es5scm.js} +2 -2
  10. package/dist/engine/compiler/build.d.ts +1 -1
  11. package/dist/engine/shared/margin-box-support.d.ts +2 -2
  12. package/dist/engine/shared/pin-edges.d.ts +127 -0
  13. package/dist/engine/shared/print-area.d.ts +68 -0
  14. package/dist/{engine-9xb1ky8d.js → engine-67f68bxs.js} +1 -1
  15. package/dist/{engine-yax79k9m.js → engine-7ha27n2b.js} +2 -2
  16. package/dist/{ext-ahwxt6gv.js → ext-9mc7sr0d.js} +5 -4
  17. package/dist/{gutterpress-agent-cazqstr1.js → gutterpress-agent-mrdnwktc.js} +78 -13
  18. package/dist/{gutterpress-viewer-retj6ym2.js → gutterpress-viewer-jr30yw8t.js} +155 -56
  19. package/dist/{index-6xwtb7cw.js → index-ad95fws8.js} +82 -40
  20. package/dist/{index-3nr5rzzp.js → index-gm5xbx5w.js} +3 -3
  21. package/dist/index-sth219v1.js +418 -0
  22. package/dist/{index-razpty47.js → index-t13p8y08.js} +493 -582
  23. package/dist/index.js +4 -3
  24. package/dist/lib/build-fingerprint.d.ts +8 -0
  25. package/dist/lib/extension-manifest.d.ts +13 -0
  26. package/dist/lib/ghostscript.d.ts +70 -16
  27. package/dist/lib/markdown/assemble.d.ts +12 -2
  28. package/dist/lib/markdown/gutterpress-css.d.ts +46 -30
  29. package/dist/lib/markdown/index.d.ts +5 -0
  30. package/dist/lib/pdf-inspect.d.ts +45 -0
  31. package/dist/{manifest.schema-74s1ykyv.json → manifest.schema-13dfax80.json} +16 -4
  32. package/dist/{new-ftd4fec4.js → new-s4f0tnya.js} +5 -4
  33. package/dist/pdf-inspect-zdmy7qca.js +35 -0
  34. package/dist/{preflight-8sqarcsr.js → preflight-7ck4rbsq.js} +5 -4
  35. package/dist/preview/file-watcher.d.ts +4 -10
  36. package/dist/{preview-q76xn95h.js → preview-j91sxadd.js} +5 -4
  37. package/dist/{publish-7gdy6gbf.js → publish-f12xaenm.js} +5 -4
  38. package/dist/render.js +57 -6
  39. package/dist/schema/manifest.types.d.ts +28 -8
  40. package/dist/test-helpers/testkit.d.ts +2 -2
  41. package/dist/{validate-e3ryegce.js → validate-pb5gysvr.js} +5 -4
  42. package/package.json +1 -1
  43. package/dist/engine/shared/flush.d.ts +0 -59
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The project's print area: the manifest's `page` block, carried into every
3
+ * assembled book (preview, build, publish) as ONE
4
+ * `<meta name="gp-print-area">` by the markdown assembly
5
+ * (lib/markdown/assemble.ts) and read back here by everything that needs it:
6
+ * the preview's print overlays (viewer/overlay.ts) and both engines' pinned
7
+ * art (pin-edges.ts — a pin sits on the printer's art-safe line).
8
+ *
9
+ * Only the manifest feeds it — `page.width` x `page.height` (the sheet),
10
+ * `page.bleed` (a bleed the sheet already contains on the top, bottom and
11
+ * OUTER edge; the binding edge has none, as DriveThruRPG cuts it),
12
+ * `page.safe` (the TEXT rule: its inset from the trim on every edge) and
13
+ * `page.artSafe` (the ART rule: its inset on the top, bottom and outer edge,
14
+ * while the binding edge takes `safe`; absent = `safe`) — plus the preset's
15
+ * id and the printer spec it follows, which only word what the overlay finds.
16
+ * No book or plugin CSS feeds it: not the `@page` margins (they are where the
17
+ * author put the TEXT, which is not the printer's safe area), not
18
+ * `@page { bleed }`.
19
+ *
20
+ * The meta is metadata: it changes nothing a browser renders on its own.
21
+ * Node-safe: the writer is pure, and the reader takes the document it reads.
22
+ */
23
+ /** The project's page geometry, in pt — the manifest's `page` block. */
24
+ export interface PrintArea {
25
+ width: number;
26
+ height: number;
27
+ /** in-sheet bleed on the top, bottom and outer edge; 0 = none */
28
+ bleed: number;
29
+ /** the TEXT rule: inset from the trim on every edge; 0 = none */
30
+ safe: number;
31
+ /** the ART rule: inset on the top, bottom and outer edge (the binding edge takes `safe`) */
32
+ artSafe: number;
33
+ /** the manifest's preset id, and the printer spec it follows: they only word what is found */
34
+ preset?: string;
35
+ spec?: {
36
+ label: string;
37
+ url: string;
38
+ };
39
+ }
40
+ /** Which side of the spread a page is: page 1 is a recto, so every odd page is one. */
41
+ export type PageSide = "recto" | "verso";
42
+ /** What a host knows from its resolved config: the `page` block, the preset and its spec. */
43
+ export interface PrintAreaSource {
44
+ width: number;
45
+ height: number;
46
+ bleed?: number;
47
+ safe?: number;
48
+ artSafe?: number;
49
+ preset?: string;
50
+ spec?: {
51
+ label: string;
52
+ url: string;
53
+ };
54
+ }
55
+ /**
56
+ * The `<meta>` tag for one book. An unset bleed or text rule is written as 0,
57
+ * never left out; an unset art rule stays out (the reader takes it as `safe`).
58
+ * Anything else the config's `page` block holds (`tolerance`) is not part of it.
59
+ */
60
+ export declare function printAreaMeta(page: PrintAreaSource): string;
61
+ /** The one DOM call the reader makes: a `Document` satisfies it. */
62
+ export interface MetaDocument {
63
+ querySelector(selector: string): {
64
+ getAttribute(name: string): string | null;
65
+ } | null;
66
+ }
67
+ /** The book's print area from its `<meta name="gp-print-area">`; `null` without one. */
68
+ export declare function printAreaFromMeta(doc: MetaDocument): PrintArea | null;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  build
3
- } from "./cli-j802s31j.js";
3
+ } from "./cli-61vd4ews.js";
4
4
  import {
5
5
  BuildError
6
6
  } from "./cli-46ycxe6r.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  build
3
- } from "./index-6xwtb7cw.js";
3
+ } from "./index-ad95fws8.js";
4
4
  import {
5
5
  BuildError
6
- } from "./index-3nr5rzzp.js";
6
+ } from "./index-gm5xbx5w.js";
7
7
  import"./index-bt0qws5c.js";
8
8
  import"./index-37x76zdn.js";
9
9
 
@@ -12,20 +12,21 @@ import {
12
12
  searchNpmExtensions,
13
13
  setExtensionEnabled,
14
14
  updateExtensions
15
- } from "./cli-zacevwcy.js";
16
- import"./cli-gh0ym80r.js";
15
+ } from "./cli-0ey902sx.js";
16
+ import"./cli-h4tfmpg3.js";
17
17
  import {
18
18
  UsageError,
19
19
  exitForUsage,
20
20
  rejectExtraPositionals,
21
21
  rejectUnknownFlags,
22
22
  resolveProjectDir
23
- } from "./cli-7z29z4wz.js";
23
+ } from "./cli-fmksazej.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-j802s31j.js";
25
+ import"./cli-61vd4ews.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";
29
+ import"./cli-sth219v1.js";
29
30
  import"./cli-eyyggect.js";
30
31
  import"./cli-asap4t1z.js";
31
32
  import"./cli-5qr3z0qa.js";
@@ -43,11 +43,13 @@
43
43
  xrefSites: () => xrefSites,
44
44
  targetTexts: () => targetTexts,
45
45
  stringSources: () => stringSources,
46
+ setPinGeometry: () => setPinGeometry,
47
+ setPinFurniture: () => setPinFurniture,
46
48
  setGenerated: () => setGenerated,
47
- setFlushFurniture: () => setFlushFurniture,
49
+ printArea: () => printArea,
50
+ pinRoots: () => pinRoots,
48
51
  instrument: () => instrument,
49
52
  forcedBreakSites: () => forcedBreakSites,
50
- flushRoots: () => flushRoots,
51
53
  fillLeaders: () => fillLeaders,
52
54
  counterResetSites: () => counterResetSites,
53
55
  collectCss: () => collectCss,
@@ -302,6 +304,51 @@
302
304
  return evaluateContent(parseContent(value), ctx);
303
305
  }
304
306
 
307
+ // src/engine/shared/pin-edges.ts
308
+ var PIN_EDGES = ["top", "right", "bottom", "left"];
309
+ var EDGE_PIN_SELECTOR = PIN_EDGES.map((edge) => `.gp-pin.gp-${edge}`).join(", ");
310
+ function pinEdgesIn(root) {
311
+ const pins = Array.from(root.querySelectorAll(EDGE_PIN_SELECTOR)).filter((pin) => pin.offsetParent === root);
312
+ return PIN_EDGES.filter((edge) => pins.some((pin) => pin.classList.contains(`gp-${edge}`)));
313
+ }
314
+ function pinKey(edges) {
315
+ return PIN_EDGES.filter((e) => edges.includes(e)).map((e) => e[0]).join("");
316
+ }
317
+ function paddingOf(cs) {
318
+ return {
319
+ top: parseFloat(cs.paddingTop) || 0,
320
+ right: parseFloat(cs.paddingRight) || 0,
321
+ bottom: parseFloat(cs.paddingBottom) || 0,
322
+ left: parseFloat(cs.paddingLeft) || 0
323
+ };
324
+ }
325
+
326
+ // src/engine/shared/print-area.ts
327
+ function printAreaFromMeta(doc) {
328
+ const raw = doc.querySelector('meta[name="gp-print-area"]')?.getAttribute("content");
329
+ if (!raw)
330
+ return null;
331
+ try {
332
+ const v = JSON.parse(raw);
333
+ const n = (x) => typeof x === "number" && Number.isFinite(x) && x > 0 ? x : 0;
334
+ if (!n(v.width) || !n(v.height))
335
+ return null;
336
+ const safe = n(v.safe);
337
+ const spec = v.spec && typeof v.spec.label === "string" && typeof v.spec.url === "string" && /^https?:\/\//i.test(v.spec.url) ? v.spec : undefined;
338
+ return {
339
+ width: n(v.width),
340
+ height: n(v.height),
341
+ bleed: n(v.bleed),
342
+ safe,
343
+ artSafe: typeof v.artSafe === "number" ? n(v.artSafe) : safe,
344
+ preset: typeof v.preset === "string" ? v.preset : undefined,
345
+ spec
346
+ };
347
+ } catch {
348
+ return null;
349
+ }
350
+ }
351
+
305
352
  // src/engine/compiler/agent.ts
306
353
  var uid = 0;
307
354
  function ensureAnchor(el) {
@@ -559,29 +606,45 @@
559
606
  addCss("gp-generated-content", css);
560
607
  return entries.length;
561
608
  }
562
- function flushRoots() {
609
+ function pinRoots() {
563
610
  const out = [];
564
611
  for (const root of Array.from(document.querySelectorAll(".page, .spread"))) {
565
- const edges = ["top", "right", "bottom", "left"].filter((edge) => root.querySelector(`.gp-pin.gp-flush.gp-${edge}`));
612
+ const edges = pinEdgesIn(root);
566
613
  if (!edges.length)
567
614
  continue;
568
- const page = getComputedStyle(root).page;
569
- const key = `${page === "auto" ? "" : page}|${edges.map((e) => e[0]).join("")}`;
570
- root.dataset.gpFlush = key;
571
- out.push({ id: ensureAnchor(root), page, edges, key });
615
+ const cs = getComputedStyle(root);
616
+ const padding = paddingOf(cs);
617
+ const key = `${cs.page === "auto" ? "" : cs.page}|${pinKey(edges)}|${edges.map((e) => padding[e]).join(",")}`;
618
+ root.dataset.gpPin = key;
619
+ out.push({ id: ensureAnchor(root), page: cs.page, edges, padding, key });
572
620
  }
573
621
  return out;
574
622
  }
575
- function setFlushFurniture(items) {
623
+ function printArea() {
624
+ return printAreaFromMeta(document);
625
+ }
626
+ function setPinGeometry(items) {
627
+ let set = 0;
628
+ for (const item of items) {
629
+ const host = anchorHost(item.id);
630
+ if (!host)
631
+ continue;
632
+ for (const [prop, value] of Object.entries(item.style))
633
+ host.style.setProperty(prop, value);
634
+ set++;
635
+ }
636
+ return set;
637
+ }
638
+ function setPinFurniture(items) {
576
639
  let painted = 0;
577
640
  for (const item of items) {
578
641
  const host = anchorHost(item.id);
579
642
  if (!host)
580
643
  continue;
581
- let layer = host.querySelector(":scope > .gp-flush-furniture");
644
+ let layer = host.querySelector(":scope > .gp-pin-furniture");
582
645
  if (!layer) {
583
646
  layer = document.createElement("div");
584
- layer.className = "gp-flush-furniture";
647
+ layer.className = "gp-pin-furniture";
585
648
  layer.setAttribute("aria-hidden", "true");
586
649
  layer.setAttribute("style", "position:absolute;inset:0;pointer-events:none;z-index:10;");
587
650
  host.appendChild(layer);
@@ -616,8 +679,10 @@
616
679
  instrument,
617
680
  addCss,
618
681
  setGenerated,
619
- flushRoots,
620
- setFlushFurniture
682
+ pinRoots,
683
+ printArea,
684
+ setPinGeometry,
685
+ setPinFurniture
621
686
  };
622
687
  if (typeof window !== "undefined")
623
688
  window.__gp = api;
@@ -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
@@ -1455,12 +1567,20 @@
1455
1567
  return head;
1456
1568
  }
1457
1569
  var pushTargets = new WeakMap;
1570
+ function repeatingHead(table) {
1571
+ const head = table.tHead;
1572
+ return head && getComputedStyle(head).display === "table-header-group" ? head : null;
1573
+ }
1574
+ function repeatingFoot(table) {
1575
+ const foot = table.tFoot;
1576
+ return foot && getComputedStyle(foot).display === "table-footer-group" ? foot : null;
1577
+ }
1458
1578
  function compensateRepeatedHeaders(strips, maxPasses = 24) {
1459
1579
  const warnings = [];
1460
1580
  let passes = 0;
1461
1581
  let touched = 0;
1462
1582
  for (const strip of strips) {
1463
- const tables = Array.from(strip.el.querySelectorAll("table")).filter((t) => t.tHead || t.tFoot);
1583
+ const tables = Array.from(strip.el.querySelectorAll("table")).filter((t) => repeatingHead(t) || repeatingFoot(t));
1464
1584
  if (!tables.length)
1465
1585
  continue;
1466
1586
  for (const table of tables) {
@@ -1485,9 +1605,9 @@
1485
1605
  const colBottom = strip.el.clientHeight;
1486
1606
  const plans = [];
1487
1607
  for (const table of tables) {
1488
- const head = table.tHead;
1608
+ const head = repeatingHead(table);
1489
1609
  const headRect = head?.getClientRects()[0];
1490
- const footHeight = (table.tFoot?.getBoundingClientRect().height ?? 0) / zoom;
1610
+ const footHeight = (repeatingFoot(table)?.getBoundingClientRect().height ?? 0) / zoom;
1491
1611
  const rows = Array.from(table.querySelectorAll("tbody > tr")).filter((r) => !r.classList.contains("gp-thead-shim") && !r.classList.contains("gp-tfoot-shim"));
1492
1612
  if (!rows.length)
1493
1613
  continue;
@@ -1567,7 +1687,7 @@
1567
1687
  td.colSpan = cell.colSpan;
1568
1688
  td.innerHTML = cell.innerHTML;
1569
1689
  const cs = getComputedStyle(cell);
1570
- td.style.cssText = `font:${cs.font};text-align:${cs.textAlign};padding:${cs.padding};border:${cs.border};background:${cs.backgroundColor};`;
1690
+ td.style.cssText = `font:${cs.font};color:${cs.color};letter-spacing:${cs.letterSpacing};text-transform:${cs.textTransform};vertical-align:${cs.verticalAlign};text-align:${cs.textAlign};padding:${cs.padding};border:${cs.border};background:${cs.backgroundColor};`;
1571
1691
  shim.appendChild(td);
1572
1692
  }
1573
1693
  } else {
@@ -1797,6 +1917,7 @@
1797
1917
  const headers = opts.compensateHeaders === false ? { tables: 0, passes: 0, warnings: [] } : compensateRepeatedHeaders(strips);
1798
1918
  const contentPages = measure(strips).totalPages;
1799
1919
  const totalPages = padToSignature(strips, contentPages);
1920
+ fitVersoPinRoots(model, strips);
1800
1921
  const api = {
1801
1922
  model,
1802
1923
  strips,
@@ -1810,6 +1931,7 @@
1810
1931
  relayout: () => {
1811
1932
  restoreFullHeightPageRoots();
1812
1933
  restoreScrollContainers();
1934
+ restorePinRoots();
1813
1935
  unwrapStrips(strips);
1814
1936
  for (const spacer of Array.from(document.querySelectorAll(".gp-recto-spacer, .gp-pad-spacer")))
1815
1937
  spacer.remove();
@@ -1828,6 +1950,7 @@
1828
1950
  const content = measure(strips).totalPages;
1829
1951
  padToSignature(strips, content);
1830
1952
  const r = measure(strips);
1953
+ fitVersoPinRoots(model, strips);
1831
1954
  api.totalPages = r.totalPages;
1832
1955
  api.blankPageIndices = blankPageIndices(strips);
1833
1956
  api.paddedPageIndices = pagesAfter(content, r.totalPages);
@@ -2174,30 +2297,6 @@
2174
2297
  artSafePt: 0,
2175
2298
  counts: { text: 0, art: 0, bleed: 0, offPage: 0, intended: 0 }
2176
2299
  };
2177
- function printAreaFromMeta() {
2178
- const raw = document.querySelector('meta[name="gp-print-area"]')?.getAttribute("content");
2179
- if (!raw)
2180
- return null;
2181
- try {
2182
- const v = JSON.parse(raw);
2183
- const n = (x) => typeof x === "number" && Number.isFinite(x) && x > 0 ? x : 0;
2184
- if (!n(v.width) || !n(v.height))
2185
- return null;
2186
- const safe = n(v.safe);
2187
- const spec = v.spec && typeof v.spec.label === "string" && typeof v.spec.url === "string" && /^https?:\/\//i.test(v.spec.url) ? v.spec : undefined;
2188
- return {
2189
- width: n(v.width),
2190
- height: n(v.height),
2191
- bleed: n(v.bleed),
2192
- safe,
2193
- artSafe: typeof v.artSafe === "number" ? n(v.artSafe) : safe,
2194
- preset: typeof v.preset === "string" ? v.preset : undefined,
2195
- spec
2196
- };
2197
- } catch {
2198
- return null;
2199
- }
2200
- }
2201
2300
  function sheetArea(sheet) {
2202
2301
  const px = (name) => parseFloat(sheet.style.getPropertyValue(name)) || 0;
2203
2302
  return { width: px("--gp-page-w") / PX_PER_PT, height: px("--gp-page-h") / PX_PER_PT, bleed: 0, safe: 0, artSafe: 0 };
@@ -2681,7 +2780,7 @@
2681
2780
  let overlayPages = [];
2682
2781
  let overlaySummary = OVERLAY_OFF;
2683
2782
  const paintOverlayNow = () => {
2684
- overlaySummary = paintOverlay(overlayPages, layout.strips, printAreaFromMeta(), overlayLayers);
2783
+ overlaySummary = paintOverlay(overlayPages, layout.strips, printAreaFromMeta(document), overlayLayers);
2685
2784
  window.dispatchEvent(new CustomEvent("gp:overlay", { detail: overlaySummary }));
2686
2785
  };
2687
2786
  const api = {