gutterpress 0.11.17 → 0.11.18-alpha.10

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 (38) hide show
  1. package/dist/api/index.d.ts +1 -1
  2. package/dist/api/index.js +4 -2
  3. package/dist/{build-gxy185pv.js → build-2tgs5j4d.js} +4 -4
  4. package/dist/{cli-zfgvssqz.js → cli-d0cq87s5.js} +1 -1
  5. package/dist/{cli-a4qtctrw.js → cli-gf1ywzeg.js} +411 -50
  6. package/dist/{cli-ycbg7mxk.js → cli-ptez7zcc.js} +53 -5
  7. package/dist/{cli-8nw6d915.js → cli-ytck518d.js} +1 -1
  8. package/dist/cli.js +12 -12
  9. package/dist/{doctor-fygmzevd.js → doctor-y4yg51zy.js} +2 -2
  10. package/dist/engine/compiler/build.d.ts +1 -1
  11. package/dist/{engine-yf2w9hj7.js → engine-wtnakejg.js} +1 -1
  12. package/dist/{engine-5d1rpxxq.js → engine-zy6fzs5a.js} +2 -2
  13. package/dist/{ext-c48s56jm.js → ext-t2sye4as.js} +4 -4
  14. package/dist/{gutterpress-viewer-ez7k4ha7.js → gutterpress-viewer-k4mpqjh0.js} +406 -14
  15. package/dist/{index-3k3fyrec.js → index-cf1kanp0.js} +454 -52
  16. package/dist/{index-vmcfahe2.js → index-jt552wtf.js} +5 -5
  17. package/dist/{index-09h3n36d.js → index-n8vs6pss.js} +51 -2
  18. package/dist/index.js +5 -3
  19. package/dist/lib/markdown/block-names.d.ts +82 -0
  20. package/dist/lib/markdown/renderer.d.ts +3 -2
  21. package/dist/lib/markdown/table-align.d.ts +32 -0
  22. package/dist/lib/presets.d.ts +11 -0
  23. package/dist/lib/printsafe.d.ts +1 -0
  24. package/dist/lib/remote-auth/sync.d.ts +21 -0
  25. package/dist/lib/unknown-classes.d.ts +12 -0
  26. package/dist/{manifest.schema-gh0jjve9.json → manifest.schema-5pzhscqh.json} +7 -1
  27. package/dist/{new-a9m2jwyy.js → new-pbd5zrds.js} +4 -4
  28. package/dist/{preflight-w3tnbtb8.js → preflight-vnkb8tj7.js} +4 -4
  29. package/dist/preview/file-watcher.d.ts +4 -1
  30. package/dist/{preview-bridge-8jhd1wpb.js → preview-bridge-evecsh4w.js} +5 -1
  31. package/dist/{preview-7z8tt0b4.js → preview-g1hp0jt8.js} +4 -4
  32. package/dist/{preview-interface-c44qk17f.js → preview-interface-2n1d9pbf.js} +77 -1
  33. package/dist/{preview-shell-xwn84kqr.js → preview-shell-hyp7g158.js} +9 -0
  34. package/dist/{publish-bbftdxh2.js → publish-vkbmf3tj.js} +4 -4
  35. package/dist/render.js +199 -28
  36. package/dist/schema/manifest.types.d.ts +11 -0
  37. package/dist/{validate-x1ha1gqj.js → validate-q1xj2qxn.js} +4 -4
  38. package/package.json +1 -1
@@ -19,6 +19,7 @@
19
19
  __export(exports_fragment, {
20
20
  wrapGeometry: () => wrapGeometry,
21
21
  waitForLayoutReady: () => waitForLayoutReady,
22
+ versoShiftPt: () => versoShiftPt,
22
23
  synthesizeColumnBreaks: () => synthesizeColumnBreaks,
23
24
  stripMetrics: () => stripMetrics,
24
25
  strideOf: () => strideOf,
@@ -34,7 +35,9 @@
34
35
  loadStyleSources: () => loadStyleSources,
35
36
  injectViewerCss: () => injectViewerCss,
36
37
  injectBreakMapping: () => injectBreakMapping,
38
+ indexInStrip: () => indexInStrip,
37
39
  fragmentDocument: () => fragmentDocument,
40
+ cssZoomOf: () => cssZoomOf,
38
41
  contentEdgeRect: () => contentEdgeRect,
39
42
  compensateRepeatedHeaders: () => compensateRepeatedHeaders,
40
43
  compensateRectoBreaks: () => compensateRectoBreaks,
@@ -106,13 +109,20 @@
106
109
  below is already sized to the run's full width and owns the actual
107
110
  clipping/visibility, so the strip does not need to. */
108
111
  .gp-strip {
112
+ /* Column pitch less the content width. \`--gp-verso-shift\` is set only on a
113
+ book with mirrored margins: decorate.ts slides each verso's sheet by it,
114
+ and growing the pitch by the same amount keeps the gap between a verso and
115
+ the recto it faces at the sheet gap (the gap across the page turn gets
116
+ twice the shift instead). Without it, it is 0px and nothing changes. */
117
+ --gp-col-gap: calc(
118
+ var(--gp-margin-right) + var(--gp-margin-left) + var(--gp-sheet-gap) +
119
+ var(--gp-verso-shift, 0px)
120
+ );
109
121
  position: relative;
110
122
  width: var(--gp-content-w);
111
123
  height: var(--gp-content-h);
112
124
  column-width: var(--gp-content-w);
113
- column-gap: calc(
114
- var(--gp-margin-right) + var(--gp-margin-left) + var(--gp-sheet-gap)
115
- );
125
+ column-gap: var(--gp-col-gap);
116
126
  column-fill: auto;
117
127
  overflow: visible;
118
128
  margin: 0;
@@ -174,9 +184,7 @@
174
184
  );
175
185
  width: calc(
176
186
  var(--gp-content-w) * var(--gp-wrap-cols, 2) +
177
- (
178
- var(--gp-margin-right) + var(--gp-margin-left) + var(--gp-sheet-gap)
179
- ) * (var(--gp-wrap-cols, 2) - 1)
187
+ var(--gp-col-gap) * (var(--gp-wrap-cols, 2) - 1)
180
188
  );
181
189
  }
182
190
 
@@ -267,6 +275,107 @@
267
275
  .gp-marginbox[data-align="center"] > .gp-marginbox-content { justify-content: center; }
268
276
  .gp-marginbox[data-align="end"] > .gp-marginbox-content { justify-content: flex-end; }
269
277
 
278
+ /* margin & bleed overlay (overlay.ts) -------------------------------- */
279
+ /* THE LOOK LIVES HERE, in these tokens, and nowhere else: colours, line
280
+ width, line style, tint strength. The desktop app's settings change the
281
+ colour (an inline \`--gp-ov-color\` on <body>) and pick a strength preset
282
+ (\`data-overlay-strength\`); nothing in a book's CSS is involved or needed. */
283
+ .gp-stage {
284
+ --gp-ov-color: #2f80ed; /* margin box line + the tint over the margins */
285
+ --gp-ov-width: 2px; /* line width as seen on screen, at any zoom */
286
+ --gp-ov-style: dashed; /* margin box line style */
287
+ --gp-ov-tint: 0.1; /* opacity of the tint over the margin area */
288
+ --gp-ov-bleed-color: #e5484d; /* bleed band + trim line */
289
+ --gp-ov-bleed-alpha: 0.45; /* opacity of the bleed band */
290
+ --gp-ov-trim-color: #8a0b18; /* the trim line: darker than the band so it reads as an edge */
291
+ --gp-ov-flag-width: 2px; /* overflow flag outline */
292
+ --gp-ov-warn: #f59e0b; /* reaches into the margin */
293
+ --gp-ov-error: #e5484d; /* crosses the trim / leaves the page */
294
+ --gp-ov-intent: #8b5cf6; /* placed on purpose */
295
+ }
296
+ .gp-stage[data-overlay-strength="subtle"] {
297
+ --gp-ov-width: 1px;
298
+ --gp-ov-tint: 0.05;
299
+ --gp-ov-bleed-alpha: 0.3;
300
+ }
301
+ .gp-stage[data-overlay-strength="strong"] {
302
+ --gp-ov-width: 3px;
303
+ --gp-ov-tint: 0.2;
304
+ --gp-ov-bleed-alpha: 0.65;
305
+ --gp-ov-flag-width: 3px;
306
+ }
307
+
308
+ /* One sibling layer per run, ABOVE the strip so the lines stay visible over
309
+ author backgrounds and art. Absolutely positioned and pointer-transparent:
310
+ it takes no flow space and no hits, so it cannot move a page break. Exists
311
+ only while the overlay is on. */
312
+ .gp-ov {
313
+ position: absolute;
314
+ inset: 0;
315
+ z-index: 5;
316
+ pointer-events: none;
317
+ /* The stage is a paint transform, so a 2px line would shrink with the zoom
318
+ and vanish at fit-to-width. Lines are sized in layout px divided by the
319
+ zoom, which makes them the same width on screen at every zoom. */
320
+ --gp-ov-w: calc(var(--gp-ov-width) / var(--gp-zoom, 1));
321
+ --gp-ov-flag-w: calc(var(--gp-ov-flag-width) / var(--gp-zoom, 1));
322
+ }
323
+
324
+ .gp-ov-page {
325
+ position: absolute;
326
+ }
327
+
328
+ /* the margins as a region: the trim minus the content box (clip-path, set in
329
+ overlay.ts), so a margin reads as an area, not a hairline */
330
+ .gp-ov-tint {
331
+ position: absolute;
332
+ inset: 0;
333
+ background: color-mix(in srgb, var(--gp-ov-color) calc(var(--gp-ov-tint) * 100%), transparent);
334
+ }
335
+
336
+ /* margin box: the content area's outline */
337
+ .gp-ov-margin {
338
+ position: absolute;
339
+ box-sizing: border-box;
340
+ border: var(--gp-ov-w) var(--gp-ov-style) var(--gp-ov-color);
341
+ }
342
+
343
+ /* bleed: a translucent band OUTSIDE the trim (its width is the border) */
344
+ .gp-ov-bleed {
345
+ position: absolute;
346
+ box-sizing: border-box;
347
+ border-style: solid;
348
+ border-color: color-mix(in srgb, var(--gp-ov-bleed-color) calc(var(--gp-ov-bleed-alpha) * 100%), transparent);
349
+ }
350
+
351
+ /* trim: where the sheet will be cut, drawn only when there is bleed — solid,
352
+ just inside the edge so it never paints over the band (overlay.ts sizes and
353
+ places it: the whole sheet, or the sheet less an in-sheet bleed) */
354
+ .gp-ov-trim {
355
+ position: absolute;
356
+ outline: var(--gp-ov-w) solid var(--gp-ov-trim-color);
357
+ outline-offset: calc(-1 * var(--gp-ov-w));
358
+ }
359
+
360
+ /* overflow flags — outline + faint tint, never a box change */
361
+ .gp-ov-flag {
362
+ position: absolute;
363
+ box-sizing: border-box;
364
+ }
365
+ .gp-ov-flag[data-kind="margin"] {
366
+ outline: var(--gp-ov-flag-w) solid var(--gp-ov-warn);
367
+ background: color-mix(in srgb, var(--gp-ov-warn) 16%, transparent);
368
+ }
369
+ .gp-ov-flag[data-kind="bleed"],
370
+ .gp-ov-flag[data-kind="off-page"] {
371
+ outline: var(--gp-ov-flag-w) solid var(--gp-ov-error);
372
+ background: color-mix(in srgb, var(--gp-ov-error) 18%, transparent);
373
+ }
374
+ /* placed on purpose (.gp-bleed, .gp-pin, …): present, not a problem */
375
+ .gp-ov-flag[data-kind="intended"] {
376
+ outline: var(--gp-ov-flag-w) dotted var(--gp-ov-intent);
377
+ }
378
+
270
379
  /* designer mode ------------------------------------------------------- */
271
380
  .gp-guide-trim,
272
381
  .gp-guide-safe,
@@ -310,6 +419,7 @@
310
419
  transform: none;
311
420
  }
312
421
  .gp-layer,
422
+ .gp-ov,
313
423
  .gp-warning,
314
424
  .gp-guide-trim,
315
425
  .gp-guide-safe,
@@ -1362,6 +1472,13 @@
1362
1472
  }
1363
1473
  }
1364
1474
  function runPageBox(model, name, warnings = []) {
1475
+ const { right, agree, r, l } = pageSides(model, name);
1476
+ if (agree)
1477
+ return right;
1478
+ warnings.push(`@page :left and @page :right give ${name ? `the "${name}" page` : "this book"} ` + `different content areas (${l.w.toFixed(1)}×${l.h.toFixed(1)}pt vs ` + `${r.w.toFixed(1)}×${r.h.toFixed(1)}pt). Print alternates between them; the preview can ` + `only show one, so page breaks here may not match the PDF. Mirror the margins instead: ` + `swap which edge is wide and keep the total the same.`);
1479
+ return resolvePage(model, { name }).geometry;
1480
+ }
1481
+ function pageSides(model, name) {
1365
1482
  const right = resolvePage(model, { name, pseudos: ["right"] }).geometry;
1366
1483
  const left = resolvePage(model, { name, pseudos: ["left"] }).geometry;
1367
1484
  const box = (g) => ({
@@ -1370,10 +1487,23 @@
1370
1487
  });
1371
1488
  const r = box(right);
1372
1489
  const l = box(left);
1373
- if (Math.abs(r.w - l.w) < 0.01 && Math.abs(r.h - l.h) < 0.01)
1374
- return right;
1375
- warnings.push(`@page :left and @page :right give ${name ? `the "${name}" page` : "this book"} ` + `different content areas (${l.w.toFixed(1)}×${l.h.toFixed(1)}pt vs ` + `${r.w.toFixed(1)}×${r.h.toFixed(1)}pt). Print alternates between them; the preview can ` + `only show one, so page breaks here may not match the PDF. Mirror the margins instead: ` + `swap which edge is wide and keep the total the same.`);
1376
- return resolvePage(model, { name }).geometry;
1490
+ return {
1491
+ right,
1492
+ left,
1493
+ r,
1494
+ l,
1495
+ agree: Math.abs(r.w - l.w) < 0.01 && Math.abs(r.h - l.h) < 0.01
1496
+ };
1497
+ }
1498
+ function versoShiftPt(model, name, flushEdges = []) {
1499
+ const { right, left, agree } = pageSides(model, name);
1500
+ if (!agree)
1501
+ return 0;
1502
+ const r = flushMargins(right.margin, flushEdges);
1503
+ const l = flushMargins(left.margin, flushEdges);
1504
+ if (Math.abs(r.left + r.right - (l.left + l.right)) >= 0.01)
1505
+ return 0;
1506
+ return r.left - l.left;
1377
1507
  }
1378
1508
  function buildStrips(model, opts = {}, warnings = []) {
1379
1509
  const doc = document;
@@ -1399,6 +1529,10 @@
1399
1529
  strip.style.setProperty("--gp-margin-right", `${pt(margin.right)}px`);
1400
1530
  strip.style.setProperty("--gp-margin-bottom", `${pt(margin.bottom)}px`);
1401
1531
  strip.style.setProperty("--gp-margin-left", `${pt(margin.left)}px`);
1532
+ const rawShift = versoShiftPt(model, run.page, run.flushEdges);
1533
+ const versoShift = Math.abs(rawShift) >= 0.01 ? rawShift : 0;
1534
+ if (versoShift)
1535
+ strip.style.setProperty("--gp-verso-shift", `${pt(versoShift)}px`);
1402
1536
  run.nodes[0].before(strip);
1403
1537
  for (const n of run.nodes)
1404
1538
  strip.appendChild(n);
@@ -1407,6 +1541,7 @@
1407
1541
  page: run.page,
1408
1542
  geometry,
1409
1543
  flushEdges: run.flushEdges,
1544
+ versoShift,
1410
1545
  pages: 0,
1411
1546
  offset: 0
1412
1547
  });
@@ -2067,6 +2202,216 @@
2067
2202
  return /right/.test(name) ? "end" : "start";
2068
2203
  }
2069
2204
 
2205
+ // src/engine/compiler/tier2.ts
2206
+ var DEFAULT_SLUG_PT = 18;
2207
+ function pageGeometry(model, opts) {
2208
+ const base = resolvePage(model);
2209
+ const trim = { width: base.geometry.width, height: base.geometry.height };
2210
+ const bleed = opts.bleedPt ?? base.geometry.bleed;
2211
+ const wantsMarks = base.geometry.marks.length > 0 || opts.marks === true;
2212
+ const slug = wantsMarks ? opts.slugPt ?? DEFAULT_SLUG_PT : 0;
2213
+ return {
2214
+ trim,
2215
+ bleed,
2216
+ slug,
2217
+ marks: base.geometry.marks.length ? base.geometry.marks : wantsMarks ? ["crop"] : [],
2218
+ media: {
2219
+ width: trim.width + 2 * (bleed + slug),
2220
+ height: trim.height + 2 * (bleed + slug)
2221
+ }
2222
+ };
2223
+ }
2224
+
2225
+ // src/engine/viewer/overlay.ts
2226
+ function documentBleedPt(model) {
2227
+ return pageGeometry(model, { model }).bleed;
2228
+ }
2229
+ function pageBoxes(g, margin, bleed, scale = 1) {
2230
+ const w = g.width * scale;
2231
+ const h = g.height * scale;
2232
+ const b = Math.max(0, bleed) * scale;
2233
+ return {
2234
+ host: { x: 0, y: 0, w, h },
2235
+ trim: { x: 0, y: 0, w, h },
2236
+ content: {
2237
+ x: margin.left * scale,
2238
+ y: margin.top * scale,
2239
+ w: w - (margin.left + margin.right) * scale,
2240
+ h: h - (margin.top + margin.bottom) * scale
2241
+ },
2242
+ bleedBox: { x: -b, y: -b, w: w + 2 * b, h: h + 2 * b },
2243
+ bleed: b
2244
+ };
2245
+ }
2246
+ function insetPageBoxes(g, margin, bleed, side, scale = 1) {
2247
+ const sheet = pageBoxes(g, margin, 0, scale);
2248
+ const b = Math.max(0, bleed) * scale;
2249
+ return {
2250
+ host: sheet.host,
2251
+ trim: { x: side === "verso" ? b : 0, y: b, w: sheet.host.w - b, h: sheet.host.h - 2 * b },
2252
+ content: sheet.content,
2253
+ bleedBox: sheet.host,
2254
+ bleed: b
2255
+ };
2256
+ }
2257
+ var SEVERITY = { inside: 0, margin: 1, bleed: 2, "off-page": 3 };
2258
+ var within = (r, outer, tol) => r.x >= outer.x - tol && r.y >= outer.y - tol && r.x + r.w <= outer.x + outer.w + tol && r.y + r.h <= outer.y + outer.h + tol;
2259
+ function classifyRect(r, boxes, tol = 0) {
2260
+ if (within(r, boxes.content, tol))
2261
+ return "inside";
2262
+ if (within(r, boxes.trim, tol))
2263
+ return "margin";
2264
+ if (boxes.bleed > 0 && within(r, boxes.bleedBox, tol))
2265
+ return "bleed";
2266
+ return "off-page";
2267
+ }
2268
+ function worstKind(kinds) {
2269
+ return kinds.reduce((worst, k) => SEVERITY[k] > SEVERITY[worst] ? k : worst, "inside");
2270
+ }
2271
+ var INTENT_SELECTOR = ".gp-bleed, .gp-pin, .gp-flush, .gp-behind, .gp-page-full-bleed";
2272
+ var OVERLAY_STRENGTHS = ["subtle", "normal", "strong"];
2273
+ var HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
2274
+ function sanitizeOverlayStyle(style) {
2275
+ const s = style && typeof style === "object" ? style : {};
2276
+ const out = {};
2277
+ if (typeof s.color === "string" && HEX_COLOR.test(s.color.trim()))
2278
+ out.color = s.color.trim();
2279
+ if (OVERLAY_STRENGTHS.includes(s.strength))
2280
+ out.strength = s.strength;
2281
+ return out;
2282
+ }
2283
+ var OVERLAY_OFF = { on: false, issues: 0, intended: 0, bleedPt: 0 };
2284
+ function pageFrameMargin(margin, versoShift, side) {
2285
+ if (side === "recto" || !versoShift)
2286
+ return margin;
2287
+ return { ...margin, left: margin.left - versoShift, right: margin.right + versoShift };
2288
+ }
2289
+ function configInsetBleedPt() {
2290
+ const v = parseFloat(document.querySelector('meta[name="gp-inset-bleed"]')?.getAttribute("content") ?? "");
2291
+ return Number.isFinite(v) && v > 0 ? v : 0;
2292
+ }
2293
+ var SKIP = new Set(["SCRIPT", "STYLE", "LINK", "TEMPLATE", "BR", "WBR", "SOURCE", "TRACK", "META"]);
2294
+ var TOL_PX = 1;
2295
+ function div(cls, box) {
2296
+ const el = document.createElement("div");
2297
+ el.className = cls;
2298
+ if (box)
2299
+ place(el, box);
2300
+ return el;
2301
+ }
2302
+ function place(el, r) {
2303
+ el.style.left = `${r.x}px`;
2304
+ el.style.top = `${r.y}px`;
2305
+ el.style.width = `${r.w}px`;
2306
+ el.style.height = `${r.h}px`;
2307
+ }
2308
+ function marginTint(b) {
2309
+ const el = div("gp-ov-tint");
2310
+ const t = b.trim;
2311
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
2312
+ const l = clamp(b.content.x, t.x, t.x + t.w);
2313
+ const r = clamp(b.content.x + b.content.w, t.x, t.x + t.w);
2314
+ const top = clamp(b.content.y, t.y, t.y + t.h);
2315
+ const bot = clamp(b.content.y + b.content.h, t.y, t.y + t.h);
2316
+ const x2 = t.x + t.w;
2317
+ const y2 = t.y + t.h;
2318
+ el.style.clipPath = `polygon(evenodd, ${t.x}px ${t.y}px, ${x2}px ${t.y}px, ${x2}px ${y2}px, ${t.x}px ${y2}px, ${t.x}px ${t.y}px, ` + `${l}px ${top}px, ${l}px ${bot}px, ${r}px ${bot}px, ${r}px ${top}px, ${l}px ${top}px)`;
2319
+ return el;
2320
+ }
2321
+ function clearOverlay() {
2322
+ for (const layer of Array.from(document.querySelectorAll(".gp-ov")))
2323
+ layer.remove();
2324
+ }
2325
+ function paintOverlay(pages, strips, bleedPt, insetBleedPt = 0) {
2326
+ const inSheet = bleedPt <= 0 && insetBleedPt > 0;
2327
+ clearOverlay();
2328
+ const layers = new Map;
2329
+ const painted = new Map;
2330
+ for (const p of pages) {
2331
+ const run = p.sheet.closest(".gp-run");
2332
+ if (!run)
2333
+ continue;
2334
+ let layer = layers.get(run);
2335
+ if (!layer) {
2336
+ layer = div("gp-ov");
2337
+ run.appendChild(layer);
2338
+ layers.set(run, layer);
2339
+ }
2340
+ const boxes = inSheet ? insetPageBoxes(p.geometry, p.margin, insetBleedPt, p.sheet.dataset.side === "verso" ? "verso" : "recto", PX_PER_PT) : pageBoxes(p.geometry, p.margin, bleedPt, PX_PER_PT);
2341
+ const host = div("gp-ov-page", boxes.host);
2342
+ host.style.left = p.sheet.style.left;
2343
+ host.style.top = p.sheet.style.top;
2344
+ host.dataset.page = String(p.index + 1);
2345
+ if (boxes.bleed > 0) {
2346
+ const bb = boxes.bleedBox;
2347
+ const t = boxes.trim;
2348
+ const band = div("gp-ov-bleed", bb);
2349
+ band.style.borderWidth = `${t.y - bb.y}px ${bb.x + bb.w - (t.x + t.w)}px ${bb.y + bb.h - (t.y + t.h)}px ${t.x - bb.x}px`;
2350
+ host.appendChild(band);
2351
+ host.appendChild(div("gp-ov-trim", t));
2352
+ }
2353
+ host.appendChild(marginTint(boxes));
2354
+ host.appendChild(div("gp-ov-margin", boxes.content));
2355
+ layer.appendChild(host);
2356
+ painted.set(p.index, { host, boxes, sheet: p.sheet });
2357
+ }
2358
+ const flags = [];
2359
+ let issues = 0;
2360
+ let intended = 0;
2361
+ for (const strip of strips) {
2362
+ const zoom = cssZoomOf(strip.el);
2363
+ const colW = (parseFloat(strip.el.style.getPropertyValue("--gp-content-w")) || 0) * zoom;
2364
+ const colH = (parseFloat(strip.el.style.getPropertyValue("--gp-content-h")) || 0) * zoom;
2365
+ const visit = (el) => {
2366
+ if (SKIP.has(el.tagName) || el.classList.contains("gp-wrap-spacer"))
2367
+ return;
2368
+ const kinds = [];
2369
+ const found = [];
2370
+ for (const r of Array.from(el.getClientRects())) {
2371
+ if (r.width <= 0 && r.height <= 0)
2372
+ continue;
2373
+ const ax = r.left + Math.min(r.width, colW || r.width) / 2;
2374
+ const ay = r.top + Math.min(r.height, colH || r.height) / 2;
2375
+ const target = painted.get(strip.offset + indexInStrip(ax, ay, strip));
2376
+ if (!target)
2377
+ continue;
2378
+ target.rect ??= target.sheet.getBoundingClientRect();
2379
+ const local = {
2380
+ x: (r.left - target.rect.left) / zoom,
2381
+ y: (r.top - target.rect.top) / zoom,
2382
+ w: r.width / zoom,
2383
+ h: r.height / zoom
2384
+ };
2385
+ const kind = classifyRect(local, target.boxes, TOL_PX);
2386
+ kinds.push(kind);
2387
+ if (kind !== "inside")
2388
+ found.push({ host: target.host, kind, box: local });
2389
+ }
2390
+ const worst = worstKind(kinds);
2391
+ if (worst !== "inside") {
2392
+ const isIntended = el.closest(INTENT_SELECTOR) !== null;
2393
+ if (isIntended)
2394
+ intended++;
2395
+ else
2396
+ issues++;
2397
+ for (const f of found)
2398
+ flags.push(isIntended ? { ...f, kind: "intended" } : f);
2399
+ return;
2400
+ }
2401
+ for (const child of Array.from(el.children))
2402
+ visit(child);
2403
+ };
2404
+ for (const child of Array.from(strip.el.children))
2405
+ visit(child);
2406
+ }
2407
+ for (const f of flags) {
2408
+ const el = div(`gp-ov-flag`, f.box);
2409
+ el.dataset.kind = f.kind;
2410
+ f.host.appendChild(el);
2411
+ }
2412
+ return inSheet ? { on: true, issues, intended, bleedPt: insetBleedPt, bleedInSheet: true } : { on: true, issues, intended, bleedPt };
2413
+ }
2414
+
2070
2415
  // src/engine/viewer/decorate.ts
2071
2416
  var px = (v) => `${v * PX_PER_PT}px`;
2072
2417
  function elementForHref(href) {
@@ -2095,6 +2440,13 @@
2095
2440
  let blankPages = new Set;
2096
2441
  let paddedPages = new Set;
2097
2442
  const warnings = [];
2443
+ let overlayOn = false;
2444
+ let overlayPages = [];
2445
+ let overlaySummary = OVERLAY_OFF;
2446
+ const paintOverlayNow = () => {
2447
+ overlaySummary = paintOverlay(overlayPages, layout.strips, documentBleedPt(model), configInsetBleedPt());
2448
+ window.dispatchEvent(new CustomEvent("gp:overlay", { detail: overlaySummary }));
2449
+ };
2098
2450
  const api = {
2099
2451
  redraw: () => draw(),
2100
2452
  sheetFor: (p) => sheets.get(p),
@@ -2104,7 +2456,29 @@
2104
2456
  warnings,
2105
2457
  setDesigner(on) {
2106
2458
  document.body.dataset.designer = on ? "on" : "off";
2107
- }
2459
+ },
2460
+ setOverlay(on, style) {
2461
+ if (style) {
2462
+ const { color, strength } = sanitizeOverlayStyle(style);
2463
+ if (strength)
2464
+ document.body.dataset.overlayStrength = strength;
2465
+ if (color)
2466
+ document.body.style.setProperty("--gp-ov-color", color);
2467
+ }
2468
+ const wasOn = overlayOn;
2469
+ overlayOn = on;
2470
+ document.body.dataset.overlay = on ? "on" : "off";
2471
+ if (on) {
2472
+ if (!(wasOn && style))
2473
+ paintOverlayNow();
2474
+ } else {
2475
+ clearOverlay();
2476
+ overlaySummary = OVERLAY_OFF;
2477
+ window.dispatchEvent(new CustomEvent("gp:overlay", { detail: overlaySummary }));
2478
+ }
2479
+ return overlaySummary;
2480
+ },
2481
+ overlay: () => overlaySummary
2108
2482
  };
2109
2483
  const canvasBg = captureCanvasBackground();
2110
2484
  document.body.classList.add("gp-stage");
@@ -2262,9 +2636,12 @@
2262
2636
  warnings.length = 0;
2263
2637
  blankPages = new Set(layout.blankPageIndices);
2264
2638
  paddedPages = new Set(layout.paddedPageIndices);
2639
+ overlayPages = [];
2265
2640
  buildMaps();
2266
2641
  fillXrefs();
2267
2642
  let prevPageH = 0;
2643
+ let prevVersoShift = 0;
2644
+ let prevRunX = 0;
2268
2645
  let first = true;
2269
2646
  for (const strip of layout.strips) {
2270
2647
  const run = ensureRun(strip);
@@ -2281,12 +2658,13 @@
2281
2658
  const slot = i + shift;
2282
2659
  const row = Math.floor(slot / perRow);
2283
2660
  const colVisual = slot % perRow;
2284
- const sheetLeft = colVisual * stride;
2661
+ const side = bookIndex % 2 === 0 ? "recto" : "verso";
2662
+ const sheetLeft = colVisual * stride + (side === "verso" ? strip.versoShift * PX_PER_PT : 0);
2285
2663
  const sheetTop = row * rowStride;
2286
2664
  const sheet = document.createElement("div");
2287
2665
  sheet.className = "gp-sheet";
2288
2666
  sheet.dataset.page = String(bookIndex + 1);
2289
- sheet.dataset.side = bookIndex % 2 === 0 ? "recto" : "verso";
2667
+ sheet.dataset.side = side;
2290
2668
  sheet.style.left = `${sheetLeft}px`;
2291
2669
  sheet.style.top = `${sheetTop}px`;
2292
2670
  sheet.style.setProperty("--gp-page-w", px(ctx.geometry.width));
@@ -2300,16 +2678,30 @@
2300
2678
  drawMarginBoxes(sheet, ctx, layout.totalPages - paddedPages.size);
2301
2679
  drawGuides(sheet, ctx);
2302
2680
  drawCropMarks(sheet, ctx);
2681
+ overlayPages.push({
2682
+ index: bookIndex,
2683
+ sheet,
2684
+ strip,
2685
+ geometry: ctx.geometry,
2686
+ margin: ctx.padded || blankPages.has(bookIndex) ? ctx.geometry.margin : pageFrameMargin(flushMargins(g.margin, strip.flushEdges ?? []), strip.versoShift, side)
2687
+ });
2303
2688
  }
2304
2689
  const rows = Math.max(1, Math.ceil((strip.pages + shift) / perRow));
2305
2690
  run.style.height = `${rowStride * (rows - 1) + PX_PER_PT * g.height}px`;
2306
2691
  run.style.width = `${stride * perRow}px`;
2307
- run.style.marginTop = strip.wrapCols && shift === 1 && !first ? `${-(prevPageH + sheetGap)}px` : "";
2692
+ const composed = !!strip.wrapCols && shift === 1 && !first;
2693
+ run.style.marginTop = composed ? `${-(prevPageH + sheetGap)}px` : "";
2694
+ const runX = composed ? prevRunX + (prevVersoShift - strip.versoShift) * PX_PER_PT : 0;
2695
+ run.style.marginLeft = runX ? `${runX}px` : "";
2308
2696
  prevPageH = PX_PER_PT * g.height;
2697
+ prevVersoShift = strip.versoShift;
2698
+ prevRunX = runX;
2309
2699
  first = false;
2310
2700
  if (opts.designer)
2311
2701
  checkOverflow(strip, warnings);
2312
2702
  }
2703
+ if (overlayOn)
2704
+ paintOverlayNow();
2313
2705
  }
2314
2706
  function drawMarginBoxes(sheet, ctx, totalPages) {
2315
2707
  const g = ctx.geometry;