gutterpress 0.11.20-alpha.4 → 0.11.20-beta.1

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 (32) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-epdcrm2c.js → build-3bv5hgez.js} +4 -4
  3. package/dist/{cli-0ey902sx.js → cli-4d25xvs2.js} +45 -32
  4. package/dist/{cli-61vd4ews.js → cli-cyej3p9v.js} +4 -4
  5. package/dist/{cli-fmksazej.js → cli-gxqxn3sf.js} +1 -1
  6. package/dist/{cli-h4tfmpg3.js → cli-tsdrvrff.js} +1 -1
  7. package/dist/cli.js +12 -12
  8. package/dist/{doctor-78es5scm.js → doctor-x9vjqhfk.js} +2 -2
  9. package/dist/engine/shared/pin-edges.d.ts +5 -5
  10. package/dist/engine/shared/print-area.d.ts +12 -7
  11. package/dist/{engine-67f68bxs.js → engine-4rkhv5ga.js} +1 -1
  12. package/dist/{engine-7ha27n2b.js → engine-5fw55dfb.js} +2 -2
  13. package/dist/{ext-9mc7sr0d.js → ext-9wafdrps.js} +4 -4
  14. package/dist/{gutterpress-agent-mrdnwktc.js → gutterpress-agent-yvd8w8my.js} +1 -0
  15. package/dist/{gutterpress-viewer-jr30yw8t.js → gutterpress-viewer-w7njnkj7.js} +115 -20
  16. package/dist/{index-gm5xbx5w.js → index-crys9951.js} +3 -3
  17. package/dist/{index-t13p8y08.js → index-pjw7trb8.js} +63 -39
  18. package/dist/{index-ad95fws8.js → index-yw16cfm8.js} +2 -2
  19. package/dist/index.js +3 -3
  20. package/dist/lib/markdown/assemble.d.ts +1 -1
  21. package/dist/lib/markdown/gutterpress-css.d.ts +8 -8
  22. package/dist/lib/page-size.d.ts +5 -3
  23. package/dist/{manifest.schema-13dfax80.json → manifest.schema-c4wfpg6g.json} +8 -2
  24. package/dist/{new-s4f0tnya.js → new-zwfmqf9t.js} +4 -4
  25. package/dist/{preflight-7ck4rbsq.js → preflight-pfhbrhdb.js} +4 -4
  26. package/dist/preview/file-watcher.d.ts +1 -0
  27. package/dist/{preview-j91sxadd.js → preview-6bc098rf.js} +4 -4
  28. package/dist/{publish-f12xaenm.js → publish-pev62kc6.js} +4 -4
  29. package/dist/render.js +22 -13
  30. package/dist/schema/manifest.types.d.ts +20 -6
  31. package/dist/{validate-pb5gysvr.js → validate-x00gjxgb.js} +4 -4
  32. package/package.json +1 -1
@@ -119,7 +119,38 @@
119
119
  );
120
120
  position: relative;
121
121
  width: var(--gp-content-w);
122
- height: var(--gp-content-h);
122
+ /* Exactly one column tall, but DEFINITE only where print's flow root is.
123
+ A page root's percentage height (\`.page { height: 100% }\`) resolves
124
+ against its containing block. In print that is the flow root (\`body\` or
125
+ \`main\`), whose height is auto in an ordinary book, so the percentage
126
+ computes to \`auto\` and MARKER_CSS's min-height cushion sizes the root.
127
+ Here the strip stands in as that containing block, and a plain
128
+ \`height: var(--gp-content-h)\` resolved the percentage to the full column:
129
+ the root ended exactly on the column's edge, and the same percentage
130
+ reached everything else the author's rule matched (\`[data-name="x"]\`
131
+ selects a block AND its heading, so the heading filled the root and the
132
+ paragraph after it spilled into the next column). Mid-run the next root's
133
+ forced break absorbed the spill; a run's LAST root left a blank column —
134
+ a preview page the PDF does not have. Measured on
135
+ examples/with-print-effects with \`height: 100%\` on its page blocks: print
136
+ 5pp, viewer 8pp (a pin root is a run of its own, so three runs ended on
137
+ such a root).
138
+
139
+ \`height: 100%\` asks the strip's own containing block the question the
140
+ root asks print's: is the flow root's height definite? Auto there (the
141
+ ordinary book) is auto here, and the root's percentage is auto exactly
142
+ as in print. A definite flow root — \`html, body { height: 100% }\`, where
143
+ print's percentage DOES resolve to the page area — is definite here too
144
+ (measured, the same 5-root book: 6pp in both renderers). Either way the
145
+ min/max pair pins the used height, so the column height, to the content
146
+ height: \`clientHeight\` is still one column.
147
+
148
+ \`decorate.ts\`'s \`.gp-run\` wrapper sizes itself with \`min-height\` for the
149
+ same reason — a definite wrapper would resolve the percentage after the
150
+ run was measured and re-fragment it under its sheets. */
151
+ height: 100%;
152
+ min-height: var(--gp-content-h);
153
+ max-height: var(--gp-content-h);
123
154
  column-width: var(--gp-content-w);
124
155
  column-gap: var(--gp-col-gap);
125
156
  column-fill: auto;
@@ -1055,7 +1086,7 @@
1055
1086
  }
1056
1087
  function pinInsets(area, side, edges) {
1057
1088
  const binding = side === "recto" ? "left" : "right";
1058
- const inset = (edge) => !area || !edges.includes(edge) ? 0 : edge === binding ? area.safe : area.bleed + area.artSafe;
1089
+ const inset = (edge) => !area || !edges.includes(edge) ? 0 : edge === binding ? area.gutter : area.bleed + area.artSafe;
1059
1090
  return { top: inset("top"), right: inset("right"), bottom: inset("bottom"), left: inset("left") };
1060
1091
  }
1061
1092
  var NO_PADDING = { top: 0, right: 0, bottom: 0, left: 0 };
@@ -1097,6 +1128,7 @@
1097
1128
  bleed: n(v.bleed),
1098
1129
  safe,
1099
1130
  artSafe: typeof v.artSafe === "number" ? n(v.artSafe) : safe,
1131
+ gutter: typeof v.gutter === "number" ? n(v.gutter) : safe,
1100
1132
  preset: typeof v.preset === "string" ? v.preset : undefined,
1101
1133
  spec
1102
1134
  };
@@ -1213,7 +1245,7 @@
1213
1245
  continue;
1214
1246
  const stripTop = strip.getBoundingClientRect().top;
1215
1247
  const edge = prop === "break-after" ? rect.bottom : rect.top;
1216
- if (columnReserve((edge - stripTop) / cssZoomOf(strip), strip.clientHeight) === null)
1248
+ if (columnReserve((edge - stripTop) / cssZoomOf(strip), stripMetrics(strip).height) === null)
1217
1249
  continue;
1218
1250
  const spacer = document.createElement("div");
1219
1251
  spacer.className = "gp-column-break-spacer";
@@ -1597,12 +1629,11 @@
1597
1629
  let previous = "";
1598
1630
  for (let pass = 0;pass < maxPasses; pass++) {
1599
1631
  passes = Math.max(passes, pass + 1);
1600
- const stride = strideOf(strip.el);
1632
+ const { stride, height: colBottom } = stripMetrics(strip.el);
1601
1633
  const stripLeft = strip.el.getBoundingClientRect().left - strip.el.scrollLeft;
1602
1634
  const zoom = cssZoomOf(strip.el);
1603
1635
  const colOf = (r) => Math.floor(((r.left - stripLeft) / zoom + 1) / stride);
1604
1636
  const stripTop = strip.el.getBoundingClientRect().top;
1605
- const colBottom = strip.el.clientHeight;
1606
1637
  const plans = [];
1607
1638
  for (const table of tables) {
1608
1639
  const head = repeatingHead(table);
@@ -1773,7 +1804,7 @@
1773
1804
  const colGap = parseFloat(cs.columnGap) || 0;
1774
1805
  const h = parseFloat(cs.getPropertyValue("--gp-content-h"));
1775
1806
  const rowGap = parseFloat(cs.rowGap) || 0;
1776
- return { stride: w + colGap, rowStride: h + rowGap };
1807
+ return { stride: w + colGap, rowStride: h + rowGap, height: h };
1777
1808
  }
1778
1809
  function rowStrideOf(strip) {
1779
1810
  return stripMetrics(strip).rowStride;
@@ -2225,16 +2256,23 @@
2225
2256
  const b = Math.max(0, area.bleed) * scale;
2226
2257
  const s = Math.max(0, area.safe) * scale;
2227
2258
  const a = Math.max(0, area.artSafe) * scale;
2228
- const [left, right] = side === "verso" ? [a, s] : [s, a];
2259
+ const g = Math.max(0, area.gutter) * scale;
2260
+ const [textLeft, textRight] = side === "verso" ? [s, g] : [g, s];
2261
+ const [artLeft, artRight] = side === "verso" ? [a, g] : [g, a];
2229
2262
  const trim = { x: side === "verso" ? b : 0, y: b, w: Math.max(0, w - b), h: Math.max(0, h - 2 * b) };
2230
2263
  return {
2231
2264
  sheet: { x: 0, y: 0, w, h },
2232
2265
  trim,
2233
- safe: { x: trim.x + s, y: trim.y + s, w: Math.max(0, trim.w - 2 * s), h: Math.max(0, trim.h - 2 * s) },
2266
+ safe: {
2267
+ x: trim.x + textLeft,
2268
+ y: trim.y + s,
2269
+ w: Math.max(0, trim.w - textLeft - textRight),
2270
+ h: Math.max(0, trim.h - 2 * s)
2271
+ },
2234
2272
  safeArt: {
2235
- x: trim.x + left,
2273
+ x: trim.x + artLeft,
2236
2274
  y: trim.y + a,
2237
- w: Math.max(0, trim.w - left - right),
2275
+ w: Math.max(0, trim.w - artLeft - artRight),
2238
2276
  h: Math.max(0, trim.h - 2 * a)
2239
2277
  },
2240
2278
  bleed: b
@@ -2299,7 +2337,7 @@
2299
2337
  };
2300
2338
  function sheetArea(sheet) {
2301
2339
  const px = (name) => parseFloat(sheet.style.getPropertyValue(name)) || 0;
2302
- return { width: px("--gp-page-w") / PX_PER_PT, height: px("--gp-page-h") / PX_PER_PT, bleed: 0, safe: 0, artSafe: 0 };
2340
+ return { width: px("--gp-page-w") / PX_PER_PT, height: px("--gp-page-h") / PX_PER_PT, bleed: 0, safe: 0, artSafe: 0, gutter: 0 };
2303
2341
  }
2304
2342
  var OVERLAY_CSS = `
2305
2343
  .root {
@@ -2311,12 +2349,12 @@
2311
2349
  --gp-ov-bleed-color: #e5484d; /* bleed layer: the band, and (darker) the trim line */
2312
2350
  --gp-ov-bleed-alpha: 0.45; /* opacity of the bleed band */
2313
2351
  --gp-ov-trim-color: color-mix(in srgb, var(--gp-ov-bleed-color) 60%, #000); /* darker than the band so it reads as an edge */
2314
- --gp-ov-art-color: #00b4d8; /* art layer: the art line (dotted, a second cue) */
2352
+ --gp-ov-art-color: #a855f7; /* art layer: the art line (dotted, a second cue) */
2315
2353
  --gp-ov-margins-color: #1a9c6b; /* margins layer: the page's own content box */
2316
2354
  --gp-ov-flag-width: 2px; /* overflow flag outline */
2317
2355
  --gp-ov-warn: #f59e0b; /* leaves the safe area */
2318
2356
  --gp-ov-error: #e5484d; /* crosses the trim / leaves the sheet */
2319
- --gp-ov-intent: #8b5cf6; /* placed on purpose */
2357
+ --gp-ov-intent: #ec4899; /* placed on purpose (pink: apart from the purple art line) */
2320
2358
  /* The overlay is scaled with the stage, so a 2px line would shrink with the
2321
2359
  zoom; dividing by it keeps lines the same width on screen at every zoom. */
2322
2360
  --gp-ov-w: calc(var(--gp-ov-width) / var(--gp-ov-z, 1));
@@ -2386,7 +2424,7 @@
2386
2424
  .gp-ov-flag[data-kind="intended"] {
2387
2425
  outline: var(--gp-ov-flag-w) dotted var(--gp-ov-intent);
2388
2426
  }
2389
- /* the legend: small, fixed in a corner of the viewport, and the one part that takes the pointer (its link) */
2427
+ /* the legend: small, fixed in a corner of the viewport, and the one part that takes the pointer (its link and its fold button) */
2390
2428
  .gp-ov-legend {
2391
2429
  position: fixed;
2392
2430
  right: 12px;
@@ -2401,8 +2439,33 @@
2401
2439
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.4);
2402
2440
  pointer-events: auto;
2403
2441
  }
2404
- .gp-ov-legend > div + div { margin-top: 3px; }
2442
+ .gp-ov-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
2443
+ .gp-ov-body { margin-top: 6px; }
2444
+ .gp-ov-body > div + div { margin-top: 3px; }
2405
2445
  .gp-ov-legend a { color: #9cc2ff; }
2446
+ /* the fold button: a chevron made of two borders, down while the legend is open, up once it is folded to its header */
2447
+ .gp-ov-toggle {
2448
+ flex: none;
2449
+ display: grid;
2450
+ place-items: center;
2451
+ width: 18px;
2452
+ height: 18px;
2453
+ padding: 0;
2454
+ border: 0;
2455
+ background: none;
2456
+ color: inherit;
2457
+ cursor: pointer;
2458
+ }
2459
+ .gp-ov-toggle:focus-visible { outline: 2px solid #9cc2ff; }
2460
+ .gp-ov-toggle::before {
2461
+ content: "";
2462
+ width: 6px;
2463
+ height: 6px;
2464
+ border: solid currentColor;
2465
+ border-width: 0 2px 2px 0;
2466
+ transform: translateY(-2px) rotate(45deg);
2467
+ }
2468
+ .gp-ov-toggle[aria-expanded="false"]::before { transform: translateY(2px) rotate(-135deg); }
2406
2469
  .gp-ov-row { display: flex; align-items: flex-start; gap: 6px; }
2407
2470
  .gp-ov-swatch {
2408
2471
  flex: none;
@@ -2550,12 +2613,19 @@
2550
2613
  var ART_TAGS = new Set(["img", "svg", "video", "canvas"]);
2551
2614
  var inches = (px) => +(Math.abs(px) / 96).toFixed(2);
2552
2615
  var ptIn = (pt2) => `${+(pt2 / 72).toFixed(3)}in`;
2616
+ var LEGEND_FOLD_KEY = "gutterpress.overlay-legend-collapsed";
2617
+ var legendCollapsed = false;
2618
+ try {
2619
+ legendCollapsed = localStorage.getItem(LEGEND_FOLD_KEY) === "true";
2620
+ } catch {}
2553
2621
  function legend(layers, area, summary) {
2554
2622
  const el = div("gp-ov-legend");
2623
+ const body = div("gp-ov-body");
2624
+ body.id = "gp-ov-body";
2555
2625
  const add = (cls, ...content) => {
2556
2626
  const line = div(cls);
2557
2627
  line.append(...content);
2558
- el.appendChild(line);
2628
+ body.appendChild(line);
2559
2629
  };
2560
2630
  const row = (layer, name, text) => {
2561
2631
  const swatch = div("gp-ov-swatch");
@@ -2567,14 +2637,15 @@
2567
2637
  add("gp-ov-row", swatch, label);
2568
2638
  };
2569
2639
  const { bleedPt, safePt, artSafePt, counts } = summary;
2640
+ const gutterPt = area?.gutter ?? safePt;
2570
2641
  if (layers.bleed) {
2571
2642
  row("bleed", "Bleed", bleedPt > 0 ? ` ${ptIn(bleedPt)} — top, bottom, outside; none at the binding` : " — none set (page.bleed in manifest.yaml)");
2572
2643
  }
2573
2644
  if (layers.safe) {
2574
- row("safe", "Safe area: text", safePt > 0 ? ` — ${ptIn(safePt)}, every edge` : " — none set (page.safe in manifest.yaml)");
2645
+ row("safe", "Safe area: text", safePt <= 0 ? " — none set (page.safe in manifest.yaml)" : gutterPt === safePt ? ` — ${ptIn(safePt)}, every edge` : ` — ${ptIn(safePt)}; ${ptIn(gutterPt)} at the binding`);
2575
2646
  }
2576
2647
  if (layers.art) {
2577
- row("art", "Safe area: art", safePt <= 0 && artSafePt <= 0 ? " — none set (page.artSafe in manifest.yaml)" : artSafePt === safePt ? ` — ${ptIn(artSafePt)}, every edge` : ` — ${ptIn(artSafePt)} top, bottom, outside; ${ptIn(safePt)} at the binding`);
2648
+ row("art", "Safe area: art", safePt <= 0 && artSafePt <= 0 ? " — none set (page.artSafe in manifest.yaml)" : artSafePt === gutterPt ? ` — ${ptIn(artSafePt)}, every edge` : ` — ${ptIn(artSafePt)} top, bottom, outside; ${ptIn(gutterPt)} at the binding`);
2578
2649
  }
2579
2650
  if (layers.margins)
2580
2651
  row("margins", "Page margins", " — your stylesheet's @page margins");
@@ -2610,6 +2681,30 @@
2610
2681
  const key = document.documentElement.dataset.overlayKey;
2611
2682
  if (key)
2612
2683
  add("gp-ov-note", `${key} hides`);
2684
+ const title = document.createElement("b");
2685
+ title.id = "gp-ov-title";
2686
+ title.textContent = "Print overlays";
2687
+ const toggle = document.createElement("button");
2688
+ toggle.type = "button";
2689
+ toggle.className = "gp-ov-toggle";
2690
+ toggle.setAttribute("aria-labelledby", title.id);
2691
+ toggle.setAttribute("aria-controls", body.id);
2692
+ const fold = () => {
2693
+ body.hidden = legendCollapsed;
2694
+ toggle.setAttribute("aria-expanded", String(!legendCollapsed));
2695
+ toggle.title = legendCollapsed ? "Show the legend" : "Hide the legend";
2696
+ };
2697
+ toggle.addEventListener("click", () => {
2698
+ legendCollapsed = !legendCollapsed;
2699
+ try {
2700
+ localStorage.setItem(LEGEND_FOLD_KEY, String(legendCollapsed));
2701
+ } catch {}
2702
+ fold();
2703
+ });
2704
+ fold();
2705
+ const head = div("gp-ov-head");
2706
+ head.append(title, toggle);
2707
+ el.append(head, body);
2613
2708
  return el;
2614
2709
  }
2615
2710
  function paintOverlay(pages, strips, area, layers) {
@@ -3029,7 +3124,7 @@
3029
3124
  overlayPages.push({ index: bookIndex, sheet, strip, padded: ctx.padded });
3030
3125
  }
3031
3126
  const rows = Math.max(1, Math.ceil((strip.pages + shift) / perRow));
3032
- run.style.height = `${rowStride * (rows - 1) + PX_PER_PT * g.height}px`;
3127
+ run.style.minHeight = `${rowStride * (rows - 1) + PX_PER_PT * g.height}px`;
3033
3128
  run.style.width = `${stride * perRow}px`;
3034
3129
  const composed = !!strip.wrapCols && shift === 1 && !first;
3035
3130
  run.style.marginTop = composed ? `${-(prevPageH + sheetGap)}px` : "";
@@ -3117,7 +3212,7 @@
3117
3212
  }
3118
3213
  }
3119
3214
  function checkOverflow(strip, out) {
3120
- const h = strip.el.clientHeight;
3215
+ const { height: h } = stripMetrics(strip.el);
3121
3216
  for (const el of Array.from(strip.el.querySelectorAll("figure,table,img,pre"))) {
3122
3217
  const r = el.getBoundingClientRect();
3123
3218
  if (r.height > h + 1) {
@@ -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-13dfax80.json";
151
+ var manifest_schema_default = "./manifest.schema-c4wfpg6g.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-jr30yw8t.js";
166
+ var gutterpress_viewer_default = "./gutterpress-viewer-w7njnkj7.js";
167
167
 
168
168
  // src/assets/engine/gutterpress-agent.js
169
- var gutterpress_agent_default = "./gutterpress-agent-mrdnwktc.js";
169
+ var gutterpress_agent_default = "./gutterpress-agent-yvd8w8my.js";
170
170
 
171
171
  // src/assets/templates/book/manifest.yaml
172
172
  var manifest_default = "./manifest-n1gh3qw5.yaml";
@@ -9,7 +9,7 @@ import {
9
9
  getAssetsDir,
10
10
  lastPageMode,
11
11
  parseSize
12
- } from "./index-gm5xbx5w.js";
12
+ } from "./index-crys9951.js";
13
13
  import {
14
14
  countLinkAnnotations,
15
15
  getImageResolutions,
@@ -180,7 +180,8 @@ var DTRPG_PRESET = {
180
180
  tolerance: 0.5,
181
181
  bleed: 9,
182
182
  safe: 36,
183
- artSafe: 18
183
+ artSafe: 18,
184
+ gutter: undefined
184
185
  },
185
186
  ink: {
186
187
  maxTac: 240,
@@ -267,7 +268,8 @@ var BOOK_PRESET = {
267
268
  tolerance: 0.5,
268
269
  bleed: undefined,
269
270
  safe: 18,
270
- artSafe: undefined
271
+ artSafe: undefined,
272
+ gutter: undefined
271
273
  },
272
274
  ink: {
273
275
  maxTac: 400,
@@ -631,7 +633,8 @@ function resolveCustomPage(c, m) {
631
633
  tolerance: c.page?.tolerance ?? m.page?.tolerance ?? 0.5,
632
634
  bleed: c.page?.bleed ?? m.page?.bleed,
633
635
  safe: c.page?.safe ?? m.page?.safe,
634
- artSafe: c.page?.artSafe ?? m.page?.artSafe
636
+ artSafe: c.page?.artSafe ?? m.page?.artSafe,
637
+ gutter: c.page?.gutter ?? m.page?.gutter
635
638
  };
636
639
  }
637
640
  function resolveWithPreset(cliOverrides, manifest, preset) {
@@ -1987,11 +1990,15 @@ var GUTTERPRESS_CSS = `
1987
1990
 
1988
1991
  /* sizes — AFTER the flow positions so max-width:100% lifts the floats' 50%
1989
1992
  cap at equal specificity */
1990
- .gp-small { width: 25%; max-width: 100%; }
1991
- .gp-medium { width: 50%; max-width: 100%; }
1992
- .gp-large { width: 60%; max-width: 100%; }
1993
- .gp-xl { width: 80%; max-width: 100%; }
1994
- .gp-xxl { width: 90%; max-width: 100%; }
1993
+ .gp-size-10 { width: 10%; max-width: 100%; }
1994
+ .gp-size-20 { width: 20%; max-width: 100%; }
1995
+ .gp-size-30 { width: 30%; max-width: 100%; }
1996
+ .gp-size-40 { width: 40%; max-width: 100%; }
1997
+ .gp-size-50 { width: 50%; max-width: 100%; }
1998
+ .gp-size-60 { width: 60%; max-width: 100%; }
1999
+ .gp-size-70 { width: 70%; max-width: 100%; }
2000
+ .gp-size-80 { width: 80%; max-width: 100%; }
2001
+ .gp-size-90 { width: 90%; max-width: 100%; }
1995
2002
 
1996
2003
  /* float clearance presets — consumed by var(--gp-gap) in the float rules
1997
2004
  above and by .gp-shape's shape-margin below; --gp-gap itself is
@@ -2189,11 +2196,15 @@ var GP_CLASSES = new Set([
2189
2196
  "gp-center",
2190
2197
  "gp-full",
2191
2198
  "gp-bleed",
2192
- "gp-small",
2193
- "gp-medium",
2194
- "gp-large",
2195
- "gp-xl",
2196
- "gp-xxl",
2199
+ "gp-size-10",
2200
+ "gp-size-20",
2201
+ "gp-size-30",
2202
+ "gp-size-40",
2203
+ "gp-size-50",
2204
+ "gp-size-60",
2205
+ "gp-size-70",
2206
+ "gp-size-80",
2207
+ "gp-size-90",
2197
2208
  "gp-tight",
2198
2209
  "gp-loose",
2199
2210
  "gp-columns",
@@ -2318,10 +2329,10 @@ import markdownItAttrs from "markdown-it-attrs";
2318
2329
  import markdownItFootnote from "markdown-it-footnote";
2319
2330
 
2320
2331
  // src/lib/markdown/gp-pin-scope.js
2321
- function warn3(env, line, type, message, marker) {
2332
+ function warn3(env, line, type, message, severity) {
2322
2333
  if (!env.layoutWarnings)
2323
2334
  env.layoutWarnings = [];
2324
- env.layoutWarnings.push({ line, type, message, marker });
2335
+ env.layoutWarnings.push(severity ? { line, type, message, severity } : { line, type, message });
2325
2336
  }
2326
2337
  function editDistance2(a, b) {
2327
2338
  let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
@@ -2364,7 +2375,7 @@ function checkFlushArt(token, env, line) {
2364
2375
  return;
2365
2376
  if (!classes.some((c) => PIN_EDGES.includes(c)))
2366
2377
  return;
2367
- warn3(env, line, "flush_art", FLUSH_ART_MSG);
2378
+ warn3(env, line, "flush_art", FLUSH_ART_MSG, "info");
2368
2379
  }
2369
2380
  function elementNameFor(token) {
2370
2381
  switch (token.type) {
@@ -3185,6 +3196,7 @@ function printAreaMeta(page) {
3185
3196
  bleed: page.bleed ?? 0,
3186
3197
  safe: page.safe ?? 0,
3187
3198
  artSafe: page.artSafe,
3199
+ gutter: page.gutter,
3188
3200
  preset: page.preset,
3189
3201
  spec: page.spec
3190
3202
  });
@@ -3813,7 +3825,7 @@ import { satisfies, validRange } from "semver";
3813
3825
  // package.json
3814
3826
  var package_default = {
3815
3827
  name: "gutterpress",
3816
- version: "0.11.20-alpha.4",
3828
+ version: "0.11.20-beta.1",
3817
3829
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
3818
3830
  author: "itlackey",
3819
3831
  license: "MPL-2.0",
@@ -6875,11 +6887,11 @@ var inches = (pt) => `${+(pt / 72).toFixed(2)}in`;
6875
6887
  var check4 = {
6876
6888
  id: "pdf.print.safe-margins",
6877
6889
  name: "Safe margins",
6878
- description: "Fails when text sits closer to the trim than `page.safe` (DriveThruRPG: 0.5in on every edge). Text only: art is judged in the preview's safe-area overlay, not here. Skipped when `page.safe` is not set",
6890
+ description: "Fails when text sits closer to the trim than `page.safe` (DriveThruRPG: 0.5in on every edge), or than `page.gutter` at the binding edge where one is set. Text only: art is judged in the preview's safe-area overlay, not here. Skipped when `page.safe` is not set",
6879
6891
  category: "pdf",
6880
6892
  phase: "post-build",
6881
6893
  async run(ctx) {
6882
- const { width, height, bleed = 0, safe = 0 } = ctx.config.page;
6894
+ const { width, height, bleed = 0, safe = 0, gutter = safe } = ctx.config.page;
6883
6895
  if (!ctx.pdfPath || !safe)
6884
6896
  return [];
6885
6897
  const doc = await loadPdf(ctx.pdfPath);
@@ -6917,8 +6929,7 @@ var check4 = {
6917
6929
  ["bottom", Math.min(...ys) - trim.bottom],
6918
6930
  ["top", trim.top - Math.max(...ys)]
6919
6931
  ];
6920
- const [edge, gap] = gaps.reduce((worst, g) => g[1] < worst[1] ? g : worst);
6921
- const shortfall = safe - gap;
6932
+ const [edge, shortfall] = gaps.map(([name, gap]) => [name, (name === "binding" ? gutter : safe) - gap]).reduce((worst, g) => g[1] > worst[1] ? g : worst);
6922
6933
  if (shortfall <= TOLERANCE_PT)
6923
6934
  continue;
6924
6935
  const text = item.str.trim();
@@ -6926,7 +6937,7 @@ var check4 = {
6926
6937
  severity: "error",
6927
6938
  message: `Page ${pageNum}: "${text.slice(0, 40)}${text.length > 40 ? "…" : ""}" is ${inches(shortfall)} too close to the ${edge} edge.`,
6928
6939
  file: ctx.pdfPath,
6929
- detail: `${rule}: text must stay at least ${inches(safe)} inside the trim on every edge.`,
6940
+ detail: gutter === safe ? `${rule}: text must stay at least ${inches(safe)} inside the trim on every edge.` : `${rule}: text must stay at least ${inches(safe)} inside the trim, ${inches(gutter)} at the binding.`,
6930
6941
  code: "text-outside-safe-margin",
6931
6942
  data: { page: pageNum, edge, shortfallPt: +shortfall.toFixed(2) }
6932
6943
  }));
@@ -10867,7 +10878,7 @@ class PdfOutput {
10867
10878
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10868
10879
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10869
10880
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10870
- const { buildNativePdf } = await import("./engine-7ha27n2b.js");
10881
+ const { buildNativePdf } = await import("./engine-5fw55dfb.js");
10871
10882
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10872
10883
  title: config.title,
10873
10884
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -12412,16 +12423,29 @@ import { existsSync as existsSync13 } from "node:fs";
12412
12423
  import { mkdir as mkdir8, readFile as readFile29, writeFile as writeFile8 } from "node:fs/promises";
12413
12424
  import path19 from "node:path";
12414
12425
  import postcss5 from "postcss";
12415
- import { Scalar as Scalar2 } from "yaml";
12426
+ import { isMap as isMap2, Scalar as Scalar2 } from "yaml";
12416
12427
  function isPositivePoints(value) {
12417
12428
  return typeof value === "number" && Number.isFinite(value) && value > 0;
12418
12429
  }
12419
12430
  function writePageBounds(doc, page) {
12420
- const out = { width: page.width, height: page.height };
12421
12431
  const tolerance = page.tolerance ?? numberAt(doc, ["page", "tolerance"]);
12432
+ if (!isMap2(doc.get("page", true)))
12433
+ doc.set("page", doc.createNode({}));
12434
+ doc.setIn(["page", "width"], page.width);
12435
+ doc.setIn(["page", "height"], page.height);
12422
12436
  if (tolerance !== undefined)
12423
- out.tolerance = tolerance;
12424
- doc.set("page", doc.createNode(out));
12437
+ doc.setIn(["page", "tolerance"], tolerance);
12438
+ }
12439
+ function deletePageBounds(doc) {
12440
+ const block = doc.get("page", true);
12441
+ if (!isMap2(block)) {
12442
+ doc.delete("page");
12443
+ return;
12444
+ }
12445
+ for (const key of ["width", "height", "tolerance"])
12446
+ block.delete(key);
12447
+ if (block.items.length === 0)
12448
+ doc.delete("page");
12425
12449
  }
12426
12450
  function numberAt(doc, keyPath) {
12427
12451
  const v = doc.getIn(keyPath);
@@ -12553,7 +12577,7 @@ async function setPageSetup(projectDir, choice) {
12553
12577
  if (choice.preset === "custom")
12554
12578
  writePageBounds(doc, size);
12555
12579
  else
12556
- doc.delete("page");
12580
+ deletePageBounds(doc);
12557
12581
  if (target === newSheet && !sheets.includes(newSheet)) {
12558
12582
  ensureSeq(doc, "styles").add(new Scalar2("styles/book.css"));
12559
12583
  }
@@ -12997,7 +13021,7 @@ import { cp as cp3, mkdir as mkdir11, readFile as readFile32, readdir as readdir
12997
13021
  import { constants as FS3, existsSync as existsSync15 } from "node:fs";
12998
13022
  import { access as access3 } from "node:fs/promises";
12999
13023
  import path22 from "node:path";
13000
- import { isMap as isMap2, isScalar as isScalar2, isSeq as isSeq3, parseDocument as parseDocument2 } from "yaml";
13024
+ import { isMap as isMap3, isScalar as isScalar2, isSeq as isSeq3, parseDocument as parseDocument2 } from "yaml";
13001
13025
  var BUILT_IN_TEMPLATE_IDS = [
13002
13026
  "book",
13003
13027
  "zine",
@@ -13063,7 +13087,7 @@ function collectEscaping(nodes, getValue, escapes) {
13063
13087
  return out;
13064
13088
  }
13065
13089
  function extensionEntryPath(node) {
13066
- const use = isMap2(node) ? scalarString(node.get("use", true)) : scalarString(node);
13090
+ const use = isMap3(node) ? scalarString(node.get("use", true)) : scalarString(node);
13067
13091
  return use !== null && isPathSpecifier(use) ? use : null;
13068
13092
  }
13069
13093
  function dropEscaping(doc, seq, key, escaping) {
@@ -13075,7 +13099,7 @@ function dropEscaping(doc, seq, key, escaping) {
13075
13099
  doc.delete(key);
13076
13100
  }
13077
13101
  function rewriteEntry(node, newPath) {
13078
- if (isMap2(node))
13102
+ if (isMap3(node))
13079
13103
  node.set("use", newPath);
13080
13104
  else if (isScalar2(node))
13081
13105
  node.value = newPath;
@@ -13871,7 +13895,7 @@ async function searchNpmExtensions(query, options = {}) {
13871
13895
  return { matches, total: pages.reduce((sum, page) => sum + page.total, 0) };
13872
13896
  }
13873
13897
  // src/lib/manifest-config.ts
13874
- import { isSeq as isSeq4, isMap as isMap3, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
13898
+ import { isSeq as isSeq4, isMap as isMap4, YAMLSeq as YAMLSeq3, Scalar as Scalar3 } from "yaml";
13875
13899
  function hasKey(obj, key) {
13876
13900
  return Object.prototype.hasOwnProperty.call(obj, key);
13877
13901
  }
@@ -13909,7 +13933,7 @@ async function setManifestFields(projectDir, updates) {
13909
13933
  if (f === null || Array.isArray(f) && f.length === 0) {
13910
13934
  doc.deleteIn(["source", "files"]);
13911
13935
  const source = doc.get("source", true);
13912
- if (isMap3(source) && source.items.length === 0) {
13936
+ if (isMap4(source) && source.items.length === 0) {
13913
13937
  doc.delete("source");
13914
13938
  }
13915
13939
  } else {
@@ -13939,7 +13963,7 @@ async function readManifestFields(projectDir) {
13939
13963
  out.sourceFiles = filesNode.items.map((i) => scalarString(i) ?? "");
13940
13964
  } else {
13941
13965
  const sourceNode = doc.get("source", true);
13942
- if (isMap3(sourceNode) && sourceNode.has("files"))
13966
+ if (isMap4(sourceNode) && sourceNode.has("files"))
13943
13967
  out.sourceFiles = null;
13944
13968
  }
13945
13969
  return out;
@@ -17636,10 +17660,10 @@ function normalizeProjectKey(projectDir) {
17636
17660
  return projectDir.replace(/[\\/]+$/, "");
17637
17661
  }
17638
17662
  // src/lib/publish/manifest-publish.ts
17639
- import { isMap as isMap4 } from "yaml";
17663
+ import { isMap as isMap5 } from "yaml";
17640
17664
  function settingsFromDoc(doc) {
17641
17665
  const node = doc.get("publish", true);
17642
- if (!isMap4(node))
17666
+ if (!isMap5(node))
17643
17667
  return {};
17644
17668
  const json = node.toJSON();
17645
17669
  const out = {};
@@ -17664,11 +17688,11 @@ async function setPublishProviderConfig(projectDir, providerId, values) {
17664
17688
  }
17665
17689
  }
17666
17690
  const section = doc.getIn(["publish", providerId], true);
17667
- if (isMap4(section) && section.items.length === 0) {
17691
+ if (isMap5(section) && section.items.length === 0) {
17668
17692
  doc.deleteIn(["publish", providerId]);
17669
17693
  }
17670
17694
  const publish = doc.get("publish", true);
17671
- if (isMap4(publish) && publish.items.length === 0) {
17695
+ if (isMap5(publish) && publish.items.length === 0) {
17672
17696
  doc.delete("publish");
17673
17697
  }
17674
17698
  await writeManifestDoc(file, doc);
@@ -19,7 +19,7 @@ import {
19
19
  stringValueAt,
20
20
  toFolioPage,
21
21
  wantsRecto
22
- } from "./index-gm5xbx5w.js";
22
+ } from "./index-crys9951.js";
23
23
  import {
24
24
  launchChromium
25
25
  } from "./index-bt0qws5c.js";
@@ -49,7 +49,7 @@ function flushMargins(margin, edges) {
49
49
  }
50
50
  function pinInsets(area, side, edges) {
51
51
  const binding = side === "recto" ? "left" : "right";
52
- const inset = (edge) => !area || !edges.includes(edge) ? 0 : edge === binding ? area.safe : area.bleed + area.artSafe;
52
+ const inset = (edge) => !area || !edges.includes(edge) ? 0 : edge === binding ? area.gutter : area.bleed + area.artSafe;
53
53
  return { top: inset("top"), right: inset("right"), bottom: inset("bottom"), left: inset("left") };
54
54
  }
55
55
  var NO_PADDING = { top: 0, right: 0, bottom: 0, left: 0 };
package/dist/index.js CHANGED
@@ -154,14 +154,14 @@ import {
154
154
  testRemoteAccess,
155
155
  updateExtensions,
156
156
  validateProjectExtensions
157
- } from "./index-t13p8y08.js";
157
+ } from "./index-pjw7trb8.js";
158
158
  import"./index-ht1bcqdd.js";
159
159
  import {
160
160
  BUILD_DIAGNOSTIC_CODES
161
- } from "./index-ad95fws8.js";
161
+ } from "./index-yw16cfm8.js";
162
162
  import {
163
163
  BuildError
164
- } from "./index-gm5xbx5w.js";
164
+ } from "./index-crys9951.js";
165
165
  import"./index-sth219v1.js";
166
166
  import {
167
167
  DEFAULT_PRINT_OPTS,
@@ -44,7 +44,7 @@ export interface LayoutWarning {
44
44
  type: string;
45
45
  message: string;
46
46
  marker?: unknown;
47
- /** Set only by a component's `validate`; absent means `"warning"`. */
47
+ /** Set by a component's `validate` and by `flush_art`; absent means `"warning"`. */
48
48
  severity?: "error" | "warning" | "info";
49
49
  }
50
50
  /**