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
@@ -148,22 +148,22 @@ 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-gh0jjve9.json";
151
+ var manifest_schema_default = "./manifest.schema-5pzhscqh.json";
152
152
 
153
153
  // src/assets/preview/scripts/preview-interface.js
154
- var preview_interface_default = "./preview-interface-c44qk17f.js";
154
+ var preview_interface_default = "./preview-interface-2n1d9pbf.js";
155
155
 
156
156
  // src/assets/preview/scripts/preview-bridge.js
157
- var preview_bridge_default = "./preview-bridge-8jhd1wpb.js";
157
+ var preview_bridge_default = "./preview-bridge-evecsh4w.js";
158
158
 
159
159
  // src/assets/preview/scripts/preview-shell.js
160
- var preview_shell_default = "./preview-shell-xwn84kqr.js";
160
+ var preview_shell_default = "./preview-shell-hyp7g158.js";
161
161
 
162
162
  // profiles/CGATS21_CRPC1.icc
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-ez7k4ha7.js";
166
+ var gutterpress_viewer_default = "./gutterpress-viewer-k4mpqjh0.js";
167
167
 
168
168
  // src/assets/engine/gutterpress-agent.js
169
169
  var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
@@ -19,7 +19,7 @@ import {
19
19
  stringValueAt,
20
20
  toFolioPage,
21
21
  wantsRecto
22
- } from "./index-vmcfahe2.js";
22
+ } from "./index-jt552wtf.js";
23
23
  import {
24
24
  launchChromium
25
25
  } from "./index-rzm1phg0.js";
@@ -724,7 +724,8 @@ var BUILD_DIAGNOSTIC_CODES = [
724
724
  "engine.content.overheight",
725
725
  "engine.image.low-dpi",
726
726
  "engine.flush.margin-box",
727
- "engine.page-background.unreferenced"
727
+ "engine.page-background.unreferenced",
728
+ "engine.hyphenation.unsupported"
728
729
  ];
729
730
  async function build(opts) {
730
731
  const log = opts.onProgress ?? (() => {});
@@ -1333,6 +1334,54 @@ ${mapCss}`;
1333
1334
  if (multicol.length)
1334
1335
  log(`audit: ${multicol.length} fragmenting multicol warning(s)`);
1335
1336
  }
1337
+ {
1338
+ const missing = JSON.parse(await page.evaluate(`(() => {
1339
+ const words = new Map();
1340
+ const styled = new Map();
1341
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
1342
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
1343
+ const found = n.data.match(/\\p{L}{7,}/gu);
1344
+ const el = n.parentElement;
1345
+ if (!found || !el) continue;
1346
+ if (!styled.has(el)) styled.set(el, getComputedStyle(el).hyphens === "auto");
1347
+ if (!styled.get(el)) continue;
1348
+ const host = el.closest("[lang]");
1349
+ const lang = host ? host.getAttribute("lang") : null;
1350
+ if (!words.has(lang)) words.set(lang, new Set());
1351
+ const set = words.get(lang);
1352
+ for (const w of found) if (set.size < 200) set.add(w);
1353
+ }
1354
+ if (!words.size) return "[]";
1355
+ const box = document.createElement("div");
1356
+ box.style.cssText = "position:absolute;left:-10000px;top:0;visibility:hidden";
1357
+ const probes = [];
1358
+ for (const [lang, set] of words) {
1359
+ for (const w of [...set].sort((a, b) => b.length - a.length).slice(0, 8)) {
1360
+ const pair = ["auto", "manual"].map((h) => {
1361
+ const d = document.createElement("div");
1362
+ if (lang !== null) d.setAttribute("lang", lang);
1363
+ d.style.cssText = "width:1px;font:16px/16px serif;word-break:normal;overflow-wrap:normal;hyphens:" + h;
1364
+ d.textContent = w;
1365
+ box.appendChild(d);
1366
+ return d;
1367
+ });
1368
+ probes.push({ lang, pair });
1369
+ }
1370
+ }
1371
+ document.body.appendChild(box);
1372
+ const hyphenates = new Set();
1373
+ for (const { lang, pair } of probes)
1374
+ if (pair[0].offsetHeight > pair[1].offsetHeight) hyphenates.add(lang);
1375
+ box.remove();
1376
+ const ua = navigator.userAgent;
1377
+ const browser = (/Electron\\/[\\d.]+/.exec(ua) || /(?:HeadlessChrome|Chrome)\\/[\\d.]+/.exec(ua) || [ua])[0];
1378
+ return JSON.stringify([...words.keys()].filter((l) => !hyphenates.has(l)).map((lang) => ({ lang, browser })));
1379
+ })()`));
1380
+ for (const { lang, browser: browser2 } of missing)
1381
+ diagnose("engine.hyphenation.unsupported", `Your stylesheets set hyphens: auto, but the browser making this PDF (${browser2}) has no hyphenation dictionary for ${lang ? `"${lang}"` : "the document's language"}, so no word is hyphenated automatically and lines (and pages) break differently than in a browser that has one. Build in a browser that hyphenates, such as Chrome (which the gutterpress CLI uses), or add soft hyphens (&shy;) where a word may break.`);
1382
+ if (missing.length)
1383
+ log(`audit: hyphenation unavailable for ${missing.length} language(s)`);
1384
+ }
1336
1385
  if (bytes === undefined) {
1337
1386
  prints++;
1338
1387
  bytes = await printPdf(page);
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ import {
38
38
  autoSyncDelayMs,
39
39
  checkCss,
40
40
  checkExtensionUpdates,
41
+ checkRemoteUpdates,
41
42
  checkToolAvailability,
42
43
  cloneRepository,
43
44
  connectGenericHost,
@@ -153,14 +154,14 @@ import {
153
154
  testRemoteAccess,
154
155
  updateExtensions,
155
156
  validateProjectExtensions
156
- } from "./index-3k3fyrec.js";
157
+ } from "./index-cf1kanp0.js";
157
158
  import"./index-ht1bcqdd.js";
158
159
  import {
159
160
  BUILD_DIAGNOSTIC_CODES
160
- } from "./index-09h3n36d.js";
161
+ } from "./index-n8vs6pss.js";
161
162
  import {
162
163
  BuildError
163
- } from "./index-vmcfahe2.js";
164
+ } from "./index-jt552wtf.js";
164
165
  import {
165
166
  DEFAULT_PRINT_OPTS,
166
167
  assertMilestone,
@@ -318,6 +319,7 @@ export {
318
319
  connectGenericHost,
319
320
  cloneRepository,
320
321
  checkToolAvailability,
322
+ checkRemoteUpdates,
321
323
  checkExtensionUpdates,
322
324
  checkCss,
323
325
  capabilitiesFor,
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `data-name` — a stable hook for targeting ONE block from a stylesheet.
3
+ *
4
+ * A book's markdown holds content and semantic markers; its look lives in the
5
+ * plugin's and the book's CSS. What CSS needs, to restyle a single block
6
+ * without a markdown edit, is something on that block to select by. Class
7
+ * names are shared by every instance, an `id` is something the author must
8
+ * type, and position selectors break on any insertion. So every block this
9
+ * pipeline can name gets a `data-name`:
10
+ *
11
+ * [data-name="sporos"] { break-inside: avoid; }
12
+ * img[data-name="founder-house"] { float: right; width: 40%; }
13
+ *
14
+ * Plain HTML: a `data-*` attribute is standard, selected with the standard
15
+ * attribute selector, and nothing here is a behaviour a future browser
16
+ * feature would replace. The viewer never reads it; it exists for the
17
+ * author's CSS.
18
+ *
19
+ * WHAT GETS A NAME
20
+ *
21
+ * Marker-opened blocks (`.chapter`, `.spread`, `.page`, `.section`, every
22
+ * declared marker, `@columns` included, their variants too) take, in order:
23
+ * 1. the marker's explicit `name=` (core already emits it as `data-name`);
24
+ * 2. its `label=`, slugged;
25
+ * 3. the slug of the block's OWN first heading: the first heading whose
26
+ * innermost marker block is this one, so a `@page` that holds a
27
+ * `@section` does not borrow the section's title and the two never
28
+ * share a name;
29
+ * 4. nothing. A block with no explicit name, label or own heading stays
30
+ * unnamed rather than getting a position-based name that moves when
31
+ * a block is inserted.
32
+ * Headings take an explicit `{name=…}`, else the slug of their own text.
33
+ * Images take an explicit `{name=…}`, else the slug of the file's basename
34
+ * without its extension (see {@link nameFromSrc}, applied in images.ts).
35
+ *
36
+ * SLUGS: lower-case, accents folded to ASCII, apostrophes (' and ’) dropped,
37
+ * every run of other characters becomes one `-`, trimmed. `slug.ts`'s `slugify` plus a few letters NFKD
38
+ * does not decompose.
39
+ *
40
+ * COLLISIONS. Names are unique within one source file, because a file is
41
+ * rendered on its own (a book is the concatenation of its files):
42
+ * - blocks share ONE namespace, so `[data-name="x"]` is exactly one block
43
+ * in a file whatever its kind; headings have their own namespace, so a
44
+ * section and its title can both be `sporos`. Pair the attribute with an
45
+ * element (`h3[data-name="sporos"]`) or a class to say which;
46
+ * - a later duplicate gets `-2`, `-3`, … in document order. Explicit names
47
+ * are authored and never rewritten; they are reserved first, so a
48
+ * derived name steps around them;
49
+ * - images are NOT numbered: two images from the same file share a name,
50
+ * which is usually what a book wants.
51
+ * A book that needs a name unique across files scopes the selector by chapter
52
+ * (`#ch-06 [data-name="x"]`) or types `name=`.
53
+ *
54
+ * CONTINUATIONS. A `gp-continued` section (made by `@continue`) is the SAME
55
+ * block, so it keeps its original's `data-name` and carries the
56
+ * `gp-continued` class; `:not(.gp-continued)` picks the first piece. It does
57
+ * NOT repeat the `id` (an id names one element; see markers.js).
58
+ *
59
+ * NO `id`s ARE ADDED. Ids feed `target-counter()` and links; inventing them
60
+ * would change what a document links to.
61
+ *
62
+ * Runs last among the built-in core rules and before any loaded plugin's own
63
+ * rules, so it sees `markdown-it-attrs`' result and a plugin sees the names.
64
+ */
65
+ import type MarkdownIt from "markdown-it";
66
+ import type Token from "markdown-it/lib/token.mjs";
67
+ /** The `data-name` slug of `text`: `""` when nothing usable is left. */
68
+ export declare function nameSlug(text: string): string;
69
+ /**
70
+ * The image name for `src`: its file's basename without extension, slugged.
71
+ * `images/chapter-00/Founder%20House.png?v=2` is `founder-house`. A `data:`
72
+ * URI has no file name, so it has none.
73
+ */
74
+ export declare function nameFromSrc(src: string): string;
75
+ /**
76
+ * The author's explicit name on a token: `data-name`, or the `name` a
77
+ * `{name=…}` brace leaves behind (not a valid attribute on these elements, so
78
+ * it is moved to `data-name`). `""` when there is none.
79
+ */
80
+ export declare function takeExplicitName(token: Token): string;
81
+ /** Register the rule. Called by `createMarkdownRenderer` right after the marker plugin. */
82
+ export declare function gpBlockNames(md: MarkdownIt): void;
@@ -89,8 +89,9 @@ export interface GutterpressMarkerLabel {
89
89
  *
90
90
  * The close token has no meta. A `section: true` marker produces the core
91
91
  * `layout_section_open` / `layout_section_close` pair instead; its open token
92
- * carries `{ line, component, kind, variant, attrs }` (plus `continued: true`
93
- * on a `@continue` reopening) and a plain `@section`'s carries only `{ line }`.
92
+ * carries `{ line, component, kind, variant, attrs }` and a plain `@section`'s
93
+ * carries only `{ line }`; a `@continue` reopening of either adds
94
+ * `continued: true`.
94
95
  *
95
96
  * See `markers.js`'s header comment for a worked example and
96
97
  * `buildDeclaredMarkerRegistry` for the full validation/resolution contract
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Table column alignment as `data-align`, not an inline style.
3
+ *
4
+ * markdown-it writes `|:--|--:|` as `style="text-align:left"` on every cell.
5
+ * An inline style beats every stylesheet, so a book could never restyle a
6
+ * column without `!important`. The same fact as a plain attribute,
7
+ * `data-align="left|center|right"`, is applied by {@link TABLE_ALIGN_CSS} and
8
+ * loses to the book's own rules.
9
+ *
10
+ * Only markdown-it's own exact `text-align:` value is converted. An author's
11
+ * own `{style=…}` on a cell is left untouched.
12
+ */
13
+ import type MarkdownIt from "markdown-it";
14
+ export declare function gpTableAlign(md: MarkdownIt): void;
15
+ /**
16
+ * The rules that apply `data-align`. `assemble.ts` injects them UNLAYERED,
17
+ * just before the book's own stylesheets, and with the natural specificity of
18
+ * an element plus an attribute (0,1,1). That puts them where the inline style
19
+ * they replace sat against the two things that used to lose to it:
20
+ *
21
+ * - a plugin's default (`th { text-align: left }`, always in a cascade
22
+ * layer): unlayered beats layered, so a markdown `:-:` still wins;
23
+ * - a book's generic element rule (`th { text-align: left }`, specificity
24
+ * 0,0,1): the attribute selector outranks it, so the same book prints the
25
+ * same. Measured on the user guide and the design guide, whose `th` rule
26
+ * centred headers only until a layered version of these rules let it win.
27
+ *
28
+ * Unlike an inline style they can be overridden without `!important`: any
29
+ * book rule at least as specific that comes later wins, e.g.
30
+ * `td[data-align="right"] { text-align: center }` or `.stats td { … }`.
31
+ */
32
+ export declare const TABLE_ALIGN_CSS = "\nth[data-align=\"left\"], td[data-align=\"left\"] { text-align: left; }\nth[data-align=\"center\"], td[data-align=\"center\"] { text-align: center; }\nth[data-align=\"right\"], td[data-align=\"right\"] { text-align: right; }\n";
@@ -21,6 +21,17 @@ export interface VendorPreset extends Omit<ResolvedConfig, "title" | "authors" |
21
21
  /** The built-in preset ids, in the order pickers should offer them. */
22
22
  export declare const PRESET_IDS: readonly ["dtrpg", "book", "custom"];
23
23
  export type PresetId = (typeof PRESET_IDS)[number];
24
+ /**
25
+ * DriveThruRPG print-on-demand interior. Where each value comes from:
26
+ *
27
+ * SPEC = DriveThru Partners, "Quick Specifications for Print Books"
28
+ * (help.drivethrupartners.com/hc/en-us/articles/12780800178583)
29
+ * GUIDE = third-party DriveThruRPG layout guides (consistent with SPEC's
30
+ * bleed rule; not stated by DriveThru itself)
31
+ * CONVENTION = Gutterpress's own choice or a value DERIVED from a spec
32
+ * requirement (e.g. PDF/X-1a forbids transparency) — NOT a DriveThru
33
+ * requirement in its own right.
34
+ */
24
35
  export declare const DTRPG_PRESET: VendorPreset;
25
36
  /**
26
37
  * A neutral, vendor-agnostic default for authors who aren't targeting a
@@ -2,6 +2,7 @@ export declare const ruleRemoteUrls = "printsafe/no-remote-urls";
2
2
  export declare const ruleRiskyProps = "printsafe/no-risky-print-effects";
3
3
  export declare const ruleSyntax = "printsafe/syntax-error";
4
4
  export declare const rulePageContainment = "printsafe/page-containment";
5
+ export declare const rulePreviewCannotFollow = "printsafe/preview-cannot-follow";
5
6
  export interface PrintSafeWarning {
6
7
  rule: string;
7
8
  severity: "error" | "warning";
@@ -61,3 +61,24 @@ export declare function refreshRemoteCopies(options: {
61
61
  refreshed: boolean;
62
62
  reason?: "no-remote" | "auth" | "offline";
63
63
  }>;
64
+ /**
65
+ * Has the book's branch moved online since this copy last heard from it?
66
+ * Reads only the server's advertised refs (`listServerRefs`: no objects
67
+ * downloaded, no history walked, nothing written), so it is cheap enough to
68
+ * poll and safe on a dirty tree. The branch's online tip is compared with the
69
+ * remote-tracking ref (the last tip this copy fetched or pushed) and with the
70
+ * local tip: if it matches neither, someone else sent changes the author has
71
+ * not got. A local copy ahead of the remote is not "updates available".
72
+ *
73
+ * Best-effort like refreshRemoteCopies: it never throws; it reports why it
74
+ * could not tell.
75
+ */
76
+ export declare function checkRemoteUpdates(options: {
77
+ projectDir: string;
78
+ credential?: HostCredential;
79
+ tokenStore?: TokenStore;
80
+ httpClient?: typeof defaultGitHttp;
81
+ }): Promise<{
82
+ available: boolean;
83
+ reason?: "no-remote" | "auth" | "offline";
84
+ }>;
@@ -0,0 +1,12 @@
1
+ import type Token from "markdown-it/lib/token.mjs";
2
+ /** One class as the markdown uses it: where it first appears, and how often. */
3
+ export interface ClassUse {
4
+ line: number;
5
+ count: number;
6
+ }
7
+ /** The author-written classes the tokens carry (`gp-*` and pipeline-emitted ones left out), keyed by class. */
8
+ export declare function classesInTokens(tokens: readonly Token[]): Map<string, ClassUse>;
9
+ /** Add every class a selector in `css` names to `into`. Unparseable CSS contributes what a text scan finds. */
10
+ export declare function addCssClasses(css: string, into: Set<string>): void;
11
+ /** The known class `cls` is most likely a typo of, or null: one edit for short names, two for longer ones. */
12
+ export declare function nearestKnownClass(cls: string, known: ReadonlySet<string>): string | null;
@@ -200,6 +200,12 @@
200
200
  "description": "Allowed deviation, in points, between the expected and the actual page size.",
201
201
  "default": 0.5,
202
202
  "minimum": 0
203
+ },
204
+ "bleed": {
205
+ "type": "number",
206
+ "description": "A bleed, in points, that the page width x height ALREADY CONTAINS on the top, bottom and OUTER edges (none on the binding edge) — how DriveThruRPG's interior file is cut (8.625 x 11.25in for an 8.5 x 11in book). Preview overlay only: it tells the margin & bleed overlay where the trim is and changes nothing in the PDF. CSS `@page { bleed }` wins when the stylesheet declares one. Preset default: dtrpg 9 (0.125in); book and custom none.",
207
+ "minimum": 0,
208
+ "examples": [9]
203
209
  }
204
210
  }
205
211
  },
@@ -237,7 +243,7 @@
237
243
  },
238
244
  "rules": {
239
245
  "type": "object",
240
- "description": "Per-rule level overrides, keyed by rule id: a printsafe rule (printsafe/no-risky-print-effects, printsafe/page-containment, printsafe/no-remote-urls) or a check id such as source.accessibility.heading-order. \"off\" silences the rule; \"warn\" / \"error\" set its severity. An id that matches no rule is ignored. To silence one site instead, put a `gutterpress-disable-next-line <rule-id> -- reason` comment (CSS `/* */` or markdown `<!-- -->`) on the line above it.",
246
+ "description": "Per-rule level overrides, keyed by rule id: a printsafe rule (printsafe/no-risky-print-effects, printsafe/page-containment, printsafe/preview-cannot-follow, printsafe/no-remote-urls) or a check id such as source.accessibility.heading-order. \"off\" silences the rule; \"warn\" / \"error\" set its severity. An id that matches no rule is ignored. To silence one site instead, put a `gutterpress-disable-next-line <rule-id> -- reason` comment (CSS `/* */` or markdown `<!-- -->`) on the line above it.",
241
247
  "additionalProperties": { "enum": ["off", "warn", "error"] },
242
248
  "examples": [{ "printsafe/no-risky-print-effects": "off" }]
243
249
  }
@@ -7,17 +7,17 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-a4qtctrw.js";
10
+ } from "./cli-gf1ywzeg.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-zfgvssqz.js";
13
+ } from "./cli-d0cq87s5.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-8nw6d915.js";
18
+ } from "./cli-ytck518d.js";
19
19
  import"./cli-ht1bcqdd.js";
20
- import"./cli-ycbg7mxk.js";
20
+ import"./cli-ptez7zcc.js";
21
21
  import {
22
22
  EXIT_CODES
23
23
  } from "./cli-46ycxe6r.js";
@@ -2,17 +2,17 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-a4qtctrw.js";
5
+ } from "./cli-gf1ywzeg.js";
6
6
  import {
7
7
  log
8
- } from "./cli-zfgvssqz.js";
8
+ } from "./cli-d0cq87s5.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-8nw6d915.js";
13
+ } from "./cli-ytck518d.js";
14
14
  import"./cli-ht1bcqdd.js";
15
- import"./cli-ycbg7mxk.js";
15
+ import"./cli-ptez7zcc.js";
16
16
  import {
17
17
  BuildError,
18
18
  EXIT_CODES
@@ -22,7 +22,7 @@ import type { PrintPadding } from '../engine/shared/synthesis.ts';
22
22
  * no per-source wrappers or forced breaks are added, so preview pagination
23
23
  * never diverges from the PDF where a source file begins mid-page.
24
24
  */
25
- export declare function injectPreviewScripts(html: string): string;
25
+ export declare function injectPreviewScripts(html: string, insetBleedPt?: number): string;
26
26
  /**
27
27
  * Generate HTML from markdown and write book.html to the temp directory.
28
28
  * The desktop's iframe loads `book.html` via a relative URL — same name in
@@ -45,6 +45,9 @@ export declare function generateAndWriteHtml(inputPath: string, tempDir: string,
45
45
  };
46
46
  extensions?: ResolvedExtensionConfig[];
47
47
  print?: PrintPadding;
48
+ page?: {
49
+ bleed?: number;
50
+ };
48
51
  }, cssAssets: Map<string, string>, imageRefs?: Set<string>): Promise<void>;
49
52
  /**
50
53
  * Chokidar 5's `ignored` matcher (regex or function) is tested against the
@@ -14,7 +14,7 @@
14
14
  //
15
15
  // Commands map 1:1 to previewAPI methods: getTotalPages, getCurrentPage,
16
16
  // goToPage, firstPage, prevPage, nextPage, lastPage, setViewMode, setZoom,
17
- // toggleDebugMode. Plus a synthetic 'print' command that calls window.print().
17
+ // toggleDebugMode, setMarginOverlay(on, {color, strength}). Plus a synthetic 'print' command that calls window.print().
18
18
  //
19
19
  // Additional messages:
20
20
  // parent -> iframe: { type: 'gutterpress:inject-styles', id: <attr-name>, css: <string> }
@@ -82,6 +82,10 @@
82
82
  window.addEventListener('contextMenuRequested', function (e) {
83
83
  post({ type: 'gutterpress:event', name: 'contextMenuRequested', detail: e.detail });
84
84
  });
85
+ // Margin & bleed overlay result (protocol v9): {on, issues, intended, bleedPt}.
86
+ window.addEventListener('overlayChanged', function (e) {
87
+ post({ type: 'gutterpress:event', name: 'overlayChanged', detail: e.detail });
88
+ });
85
89
  // In-flow block editing (protocol v8). `Requested` is the double-click entry
86
90
  // point — the SPA answers it with a beginBlockEdit command. `Finished`
87
91
  // carries the edited text back for an end the AUTHOR initiated inside the
@@ -7,10 +7,10 @@ import {
7
7
  runBuild,
8
8
  splitOutPath,
9
9
  startPreviewServer
10
- } from "./cli-a4qtctrw.js";
10
+ } from "./cli-gf1ywzeg.js";
11
11
  import {
12
12
  log
13
- } from "./cli-zfgvssqz.js";
13
+ } from "./cli-d0cq87s5.js";
14
14
  import {
15
15
  UsageError,
16
16
  parseFormat,
@@ -18,9 +18,9 @@ import {
18
18
  rejectExtraPositionals,
19
19
  rejectUnknownFlags,
20
20
  resolvePort
21
- } from "./cli-8nw6d915.js";
21
+ } from "./cli-ytck518d.js";
22
22
  import"./cli-ht1bcqdd.js";
23
- import"./cli-ycbg7mxk.js";
23
+ import"./cli-ptez7zcc.js";
24
24
  import {
25
25
  BuildError
26
26
  } from "./cli-46ycxe6r.js";
@@ -1070,6 +1070,12 @@
1070
1070
  refreshPages();
1071
1071
  currentPage = detectVisiblePage();
1072
1072
  recordVisibleSource();
1073
+ // A hot-reload swap makes this frame the active one AFTER its overlay was
1074
+ // switched on (while hidden, where nothing is relayed): say so now.
1075
+ var overlay = api.getMarginOverlay();
1076
+ if (overlay && overlay.on) {
1077
+ window.dispatchEvent(new CustomEvent('overlayChanged', { detail: overlay }));
1078
+ }
1073
1079
  return api.notifyPageChange();
1074
1080
  },
1075
1081
 
@@ -1091,7 +1097,33 @@
1091
1097
  // floating edit panel this replaces. A v8 lib with a pre-v8 SPA loses the
1092
1098
  // "Edit this block" action and nothing else; a v8 SPA feature-detects on
1093
1099
  // the version before offering it.
1094
- getProtocolVersion: function () { return 8; },
1100
+ // v9: ADDED setMarginOverlay()/toggleMarginOverlay()/getMarginOverlay() and
1101
+ // the overlayChanged event (margin & bleed overlay, engine/viewer/
1102
+ // overlay.ts). A v9 lib with a pre-v9 SPA simply never turns it on; a v9
1103
+ // SPA feature-detects on the version before offering the toggle.
1104
+ // (v9 is unreleased as of this writing, so setMarginOverlay's optional
1105
+ // second argument — the look, below — is part of v9, not a bump.)
1106
+ getProtocolVersion: function () { return 9; },
1107
+
1108
+ // Margin & bleed overlay. Viewer chrome only — it adds no box to the book
1109
+ // and no attribute to its elements, so pagination is identical on or off.
1110
+ // Returns the summary {on, issues, intended, bleedPt}, or null when the
1111
+ // viewer predates it. `style` ({color: '#rrggbb', strength: 'subtle' |
1112
+ // 'normal' | 'strong'}) is optional: it only sets the viewer's own
1113
+ // `--gp-ov-*` tokens, and a call that carries it while the overlay is
1114
+ // already on restyles without repainting.
1115
+ setMarginOverlay: function (on, style) {
1116
+ var d = window.Gutterpress && window.Gutterpress.decoration;
1117
+ return d && typeof d.setOverlay === 'function' ? d.setOverlay(!!on, style) : null;
1118
+ },
1119
+ toggleMarginOverlay: function () {
1120
+ var d = window.Gutterpress && window.Gutterpress.decoration;
1121
+ return d && typeof d.setOverlay === 'function' ? d.setOverlay(document.body.dataset.overlay !== 'on') : null;
1122
+ },
1123
+ getMarginOverlay: function () {
1124
+ var d = window.Gutterpress && window.Gutterpress.decoration;
1125
+ return d && typeof d.overlay === 'function' ? d.overlay() : null;
1126
+ },
1095
1127
 
1096
1128
  // Resolve the annotated element/selection at a viewport point (protocol
1097
1129
  // v4). Pure read; see buildContextTarget() above for the full contract.
@@ -1596,4 +1628,48 @@
1596
1628
  // The Gutterpress engine viewer dispatches this when its pagination
1597
1629
  // completes (engine/viewer/index.ts's mount()).
1598
1630
  window.addEventListener('gp:layout', onRenderingComplete);
1631
+
1632
+ // Margin & bleed overlay: relay the viewer's result as 'overlayChanged' (the
1633
+ // bridge posts it to a host toolbar) and, where no host toolbar exists, give
1634
+ // the standalone CLI preview its own switch: Alt+M, plus a small status pill.
1635
+ // "Standalone" = the shell is top level (the CLI); under the desktop app the
1636
+ // shell's parent is the app, which owns the toggle and the count.
1637
+ var standalone = false;
1638
+ try { standalone = window.parent === window || window.parent.parent === window.parent; } catch (_e) {}
1639
+
1640
+ function updateOverlayBadge(summary) {
1641
+ var badge = document.getElementById('gp-ov-badge');
1642
+ if (!standalone || !summary || !summary.on) {
1643
+ if (badge) badge.remove();
1644
+ return;
1645
+ }
1646
+ if (!badge) {
1647
+ badge = document.createElement('div');
1648
+ badge.id = 'gp-ov-badge';
1649
+ badge.setAttribute('role', 'status');
1650
+ badge.style.cssText = 'position:fixed;top:8px;right:8px;z-index:2147483000;pointer-events:none;' +
1651
+ 'background:rgba(20,20,24,.88);color:#fff;font:600 11px/1.4 ui-sans-serif,system-ui,sans-serif;' +
1652
+ 'padding:4px 10px;border-radius:999px;box-shadow:0 1px 6px rgba(0,0,0,.4)';
1653
+ document.documentElement.appendChild(badge);
1654
+ }
1655
+ var parts = ['Margins & bleed'];
1656
+ parts.push(summary.issues ? summary.issues + ' overflowing' : 'nothing overflows');
1657
+ if (summary.intended) parts.push(summary.intended + ' placed on purpose');
1658
+ if (summary.bleedInSheet) parts.push('bleed inside the page (print preset)');
1659
+ if (!summary.bleedPt) parts.push('no bleed set \u2014 add @page { bleed: 0.125in } to see the bleed edge');
1660
+ badge.textContent = parts.join(' \u00b7 ') + ' (Alt+M hides)';
1661
+ }
1662
+
1663
+ window.addEventListener('gp:overlay', function (e) {
1664
+ updateOverlayBadge(e.detail);
1665
+ window.dispatchEvent(new CustomEvent('overlayChanged', { detail: e.detail }));
1666
+ });
1667
+
1668
+ if (standalone) {
1669
+ document.addEventListener('keydown', function (e) {
1670
+ if (e.code !== 'KeyM' || !e.altKey || e.ctrlKey || e.metaKey || e.defaultPrevented) return;
1671
+ e.preventDefault();
1672
+ api.toggleMarginOverlay();
1673
+ });
1674
+ }
1599
1675
  })();
@@ -106,6 +106,15 @@
106
106
  else if (sourceBody.classList.contains('view-spread')) api.setViewMode('spread', true);
107
107
  else if (sourceBody.classList.contains('view-two-column')) api.setViewMode('two-column', true);
108
108
  if (sourceBody.classList.contains('debug')) api.toggleDebugMode();
109
+ // The margin & bleed overlay is a review aid the author switched on: a
110
+ // hot reload must not drop it.
111
+ // Its look (colour, strength) travels with it.
112
+ if (sourceBody.dataset.overlay === 'on' && api.setMarginOverlay) {
113
+ api.setMarginOverlay(true, {
114
+ color: sourceBody.style.getPropertyValue('--gp-ov-color').trim(),
115
+ strength: sourceBody.dataset.overlayStrength
116
+ });
117
+ }
109
118
  }
110
119
  var zoom = source.documentElement.style.getPropertyValue('--gutterpress-zoom');
111
120
  // Carry the fit flag too (setZoom's third argument clips sideways overflow).
@@ -11,18 +11,18 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-a4qtctrw.js";
14
+ } from "./cli-gf1ywzeg.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-zfgvssqz.js";
18
+ } from "./cli-d0cq87s5.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-8nw6d915.js";
23
+ } from "./cli-ytck518d.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-ycbg7mxk.js";
25
+ import"./cli-ptez7zcc.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";