gutterpress 0.11.18-alpha.2 → 0.11.18-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 (29) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-se062swz.js → build-63nwjhzf.js} +4 -4
  3. package/dist/{cli-qp6dtsh3.js → cli-36v78t6w.js} +1 -1
  4. package/dist/{cli-161z4n0s.js → cli-5x7s8bap.js} +2 -2
  5. package/dist/{cli-yqpnksaa.js → cli-88hddvbz.js} +1 -1
  6. package/dist/{cli-0w3961p5.js → cli-dmcy8kay.js} +400 -44
  7. package/dist/cli.js +12 -12
  8. package/dist/{doctor-tkbdx8c1.js → doctor-n1cwrwx2.js} +2 -2
  9. package/dist/{engine-jkf4pxz9.js → engine-6zfj0jte.js} +1 -1
  10. package/dist/{engine-7gfr16t8.js → engine-zw8626be.js} +2 -2
  11. package/dist/{ext-kg8m8vda.js → ext-ddfjfcg4.js} +4 -4
  12. package/dist/{gutterpress-viewer-spq1ge52.js → gutterpress-viewer-k4mpqjh0.js} +57 -29
  13. package/dist/{index-bb2d1ev8.js → index-1ergtg60.js} +401 -45
  14. package/dist/{index-t3zxy69d.js → index-jt552wtf.js} +2 -2
  15. package/dist/{index-3x23by5j.js → index-qbd874dh.js} +1 -1
  16. package/dist/index.js +3 -3
  17. package/dist/lib/markdown/block-names.d.ts +82 -0
  18. package/dist/lib/markdown/renderer.d.ts +3 -2
  19. package/dist/lib/markdown/table-align.d.ts +32 -0
  20. package/dist/lib/printsafe.d.ts +1 -0
  21. package/dist/lib/unknown-classes.d.ts +12 -0
  22. package/dist/{manifest.schema-8m82f37q.json → manifest.schema-5pzhscqh.json} +1 -1
  23. package/dist/{new-w7q0n21v.js → new-ede0f1nm.js} +4 -4
  24. package/dist/{preflight-m54qsjs7.js → preflight-b6c3k3qt.js} +4 -4
  25. package/dist/{preview-bg3wxqxc.js → preview-y2dxxbrk.js} +4 -4
  26. package/dist/{publish-waps63wf.js → publish-n60n0hm1.js} +4 -4
  27. package/dist/render.js +199 -28
  28. package/dist/{validate-c9d8d656.js → validate-2c7sfyxn.js} +4 -4
  29. package/package.json +1 -1
package/dist/api/index.js CHANGED
@@ -136,11 +136,11 @@ import {
136
136
  testRemoteAccess,
137
137
  updateExtensions,
138
138
  validateProjectExtensions
139
- } from "../index-bb2d1ev8.js";
139
+ } from "../index-1ergtg60.js";
140
140
  import"../index-ht1bcqdd.js";
141
141
  import {
142
142
  BuildError
143
- } from "../index-t3zxy69d.js";
143
+ } from "../index-jt552wtf.js";
144
144
  import {
145
145
  DEFAULT_PRINT_OPTS,
146
146
  assertMilestone,
@@ -1,19 +1,19 @@
1
1
  import {
2
2
  runBuild,
3
3
  splitOutPath
4
- } from "./cli-0w3961p5.js";
4
+ } from "./cli-dmcy8kay.js";
5
5
  import {
6
6
  log
7
- } from "./cli-qp6dtsh3.js";
7
+ } from "./cli-36v78t6w.js";
8
8
  import {
9
9
  UsageError,
10
10
  parseFormat,
11
11
  parsePdfxFlavor,
12
12
  rejectExtraPositionals,
13
13
  rejectUnknownFlags
14
- } from "./cli-yqpnksaa.js";
14
+ } from "./cli-88hddvbz.js";
15
15
  import"./cli-ht1bcqdd.js";
16
- import"./cli-161z4n0s.js";
16
+ import"./cli-5x7s8bap.js";
17
17
  import {
18
18
  BuildError
19
19
  } from "./cli-46ycxe6r.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "./cli-yqpnksaa.js";
3
+ } from "./cli-88hddvbz.js";
4
4
  import {
5
5
  execCapture,
6
6
  findTool,
@@ -1116,7 +1116,7 @@ import { fileURLToPath } from "node:url";
1116
1116
  var favicon_default = "./favicon-272ga6y0.ico";
1117
1117
 
1118
1118
  // src/assets/manifest.schema.json
1119
- var manifest_schema_default = "./manifest.schema-8m82f37q.json";
1119
+ var manifest_schema_default = "./manifest.schema-5pzhscqh.json";
1120
1120
 
1121
1121
  // src/assets/preview/scripts/preview-interface.js
1122
1122
  var preview_interface_default = "./preview-interface-2n1d9pbf.js";
@@ -1131,7 +1131,7 @@ var preview_shell_default = "./preview-shell-hyp7g158.js";
1131
1131
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
1132
1132
 
1133
1133
  // src/assets/engine/gutterpress-viewer.js
1134
- var gutterpress_viewer_default = "./gutterpress-viewer-spq1ge52.js";
1134
+ var gutterpress_viewer_default = "./gutterpress-viewer-k4mpqjh0.js";
1135
1135
 
1136
1136
  // src/assets/engine/gutterpress-agent.js
1137
1137
  var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
@@ -4,7 +4,7 @@ import {
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "gutterpress",
7
- version: "0.11.18-alpha.2",
7
+ version: "0.11.18-alpha.4",
8
8
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
9
9
  author: "itlackey",
10
10
  license: "MPL-2.0",
@@ -13,12 +13,12 @@ import {
13
13
  stampCreator,
14
14
  stripAnnotations,
15
15
  warn
16
- } from "./cli-qp6dtsh3.js";
16
+ } from "./cli-36v78t6w.js";
17
17
  import {
18
18
  DEBOUNCE,
19
19
  UsageError,
20
20
  resolvePort
21
- } from "./cli-yqpnksaa.js";
21
+ } from "./cli-88hddvbz.js";
22
22
  import {
23
23
  blackTextToGray
24
24
  } from "./cli-ht1bcqdd.js";
@@ -27,7 +27,7 @@ import {
27
27
  getAssetPath,
28
28
  getAssetsDir,
29
29
  lastPageMode
30
- } from "./cli-161z4n0s.js";
30
+ } from "./cli-5x7s8bap.js";
31
31
  import {
32
32
  BuildError,
33
33
  EXIT_CODES
@@ -826,10 +826,11 @@ function parseMarkerLine(line, declaredWords) {
826
826
  const kind = head.slice(1);
827
827
  if (!KNOWN_KINDS.includes(kind) && !(declaredWords && declaredWords.has(kind)))
828
828
  return null;
829
- if (kind === "page-break" || kind === "column-break" || kind === "continue" || kind.startsWith("end-")) {
829
+ if (kind === "continue" || kind.startsWith("end-")) {
830
830
  return { kind, name: null, attrs: {} };
831
831
  }
832
- const body = tokens.slice(1);
832
+ const isBreak = kind === "page-break" || kind === "column-break";
833
+ const body = isBreak ? tokens.slice(1).filter((token) => !isBareToken(token)) : tokens.slice(1);
833
834
  const hasExplicitAttrsOrShorthand = body.some((token) => token.includes("=") || token.startsWith(".") || token.startsWith("#"));
834
835
  const bareTokens = body.filter(isBareToken);
835
836
  let name = null;
@@ -897,6 +898,8 @@ function parseMarkerLine(line, declaredWords) {
897
898
  }
898
899
  if (classes.length)
899
900
  attrs.class = classes.join(" ");
901
+ if (isBreak)
902
+ return { kind, name: null, attrs };
900
903
  const marker = { kind, name, attrs };
901
904
  if (hasAmbiguousBareToken)
902
905
  marker.__ambiguousBareToken = true;
@@ -1079,6 +1082,11 @@ function attachDataAttrs(token, kind, name, attrs) {
1079
1082
  token.attrSet(k.startsWith("data-") ? k : `data-${k}`, v);
1080
1083
  }
1081
1084
  }
1085
+ function attachBreak(token, baseClass, attrs = {}) {
1086
+ addClasses(token, baseClass, attrs.class);
1087
+ token.attrSet("aria-hidden", "true");
1088
+ attachDataAttrs(token, "break", null, attrs);
1089
+ }
1082
1090
  var DECLARED_NAME_RE = /^[a-z][a-z0-9-]*$/;
1083
1091
  function validateDeclaredMarkerName(name, pluginName) {
1084
1092
  if (!DECLARED_NAME_RE.test(name)) {
@@ -1635,7 +1643,11 @@ function plugin(md, pluginOptions = {}) {
1635
1643
  attachDataAttrs(t, component.kind, component.variant, meta.attrs || {});
1636
1644
  else
1637
1645
  attachDataAttrs(t, "section", meta.name, meta.attrs || {});
1638
- t.meta = component ? { line: meta.__line, ...component, ...meta.__continued ? { continued: true } : {} } : { line: meta.__line };
1646
+ t.meta = {
1647
+ line: meta.__line,
1648
+ ...component || {},
1649
+ ...meta.__continued ? { continued: true } : {}
1650
+ };
1639
1651
  stack.open({
1640
1652
  kind: "section",
1641
1653
  classes: meta.attrs && meta.attrs.class || "",
@@ -1733,12 +1745,12 @@ function plugin(md, pluginOptions = {}) {
1733
1745
  drainDeclaredFrames(keep);
1734
1746
  const contMeta = {
1735
1747
  name: section.meta.name,
1736
- attrs: { ...section.meta.attrs || {} }
1748
+ attrs: { ...section.meta.attrs || {} },
1749
+ __continued: true
1737
1750
  };
1738
- if (section.meta.component) {
1751
+ delete contMeta.attrs.id;
1752
+ if (section.meta.component)
1739
1753
  contMeta.__component = section.meta.component;
1740
- contMeta.__continued = true;
1741
- }
1742
1754
  contMeta.__line = line;
1743
1755
  const cls = (contMeta.attrs.class || "").split(/\s+/).filter(Boolean);
1744
1756
  if (!cls.includes("gp-continued"))
@@ -1751,16 +1763,14 @@ function plugin(md, pluginOptions = {}) {
1751
1763
  if (kind === "page-break") {
1752
1764
  const t = new state.Token("layout_page_break", "div", 0);
1753
1765
  t.meta = { line };
1754
- t.attrSet("class", "gp-page-break");
1755
- t.attrSet("aria-hidden", "true");
1766
+ attachBreak(t, "gp-page-break", meta.attrs);
1756
1767
  out.push(t);
1757
1768
  continue;
1758
1769
  }
1759
1770
  if (kind === "column-break") {
1760
1771
  const t = new state.Token("layout_column_break", "div", 0);
1761
1772
  t.meta = { line };
1762
- t.attrSet("class", "gp-column-break");
1763
- t.attrSet("aria-hidden", "true");
1773
+ attachBreak(t, "gp-column-break", meta.attrs);
1764
1774
  out.push(t);
1765
1775
  continue;
1766
1776
  }
@@ -1824,24 +1834,10 @@ function plugin(md, pluginOptions = {}) {
1824
1834
  }
1825
1835
  });
1826
1836
  }
1827
- md.renderer.rules.layout_page_break = (tokens, idx) => {
1828
- const cls = tokens[idx].attrGet("class") || "gp-page-break";
1829
- const rangeAttr = tokens[idx].attrGet("data-source-range");
1830
- const rangeHtml = rangeAttr ? ` data-source-range="${escapeAttr(rangeAttr)}"` : "";
1831
- const chapterAttr = tokens[idx].attrGet("data-chapter-src");
1832
- const chapterHtml = chapterAttr ? ` data-chapter-src="${escapeAttr(chapterAttr)}"` : "";
1833
- return `<div class="${escapeAttr(cls)}" aria-hidden="true"${rangeHtml}${chapterHtml}></div>
1837
+ const renderBreak = (tokens, idx, options2, env, self) => `<div${self.renderAttrs(tokens[idx])}></div>
1834
1838
  `;
1835
- };
1836
- md.renderer.rules.layout_column_break = (tokens, idx) => {
1837
- const cls = tokens[idx].attrGet("class") || "gp-column-break";
1838
- const rangeAttr = tokens[idx].attrGet("data-source-range");
1839
- const rangeHtml = rangeAttr ? ` data-source-range="${escapeAttr(rangeAttr)}"` : "";
1840
- const chapterAttr = tokens[idx].attrGet("data-chapter-src");
1841
- const chapterHtml = chapterAttr ? ` data-chapter-src="${escapeAttr(chapterAttr)}"` : "";
1842
- return `<div class="${escapeAttr(cls)}" aria-hidden="true"${rangeHtml}${chapterHtml}></div>
1843
- `;
1844
- };
1839
+ md.renderer.rules.layout_page_break = renderBreak;
1840
+ md.renderer.rules.layout_column_break = renderBreak;
1845
1841
  md.renderer.rules.layout_marker = () => "";
1846
1842
  }
1847
1843
  var NO_MARGIN_BOXES = [
@@ -2292,6 +2288,177 @@ function gpPinScope(md) {
2292
2288
  });
2293
2289
  }
2294
2290
 
2291
+ // src/lib/slug.ts
2292
+ function slugify(name, fallback = "") {
2293
+ return name.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || fallback;
2294
+ }
2295
+ function prettify(slug) {
2296
+ const spaced = slug.replace(/[-_]+/g, " ").trim();
2297
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
2298
+ }
2299
+
2300
+ // src/lib/markdown/block-names.ts
2301
+ var BLOCK_OPEN = /^layout_(chapter|spread|page|section|component)_open$/;
2302
+ var BLOCK_CLOSE = /^layout_(chapter|spread|page|section|component)_close$/;
2303
+ var FOLDED = {
2304
+ ß: "ss",
2305
+ æ: "ae",
2306
+ œ: "oe",
2307
+ ø: "o",
2308
+ đ: "d",
2309
+ ð: "d",
2310
+ þ: "th",
2311
+ ł: "l",
2312
+ ı: "i"
2313
+ };
2314
+ function nameSlug(text) {
2315
+ return slugify(text.replace(/['’]/g, "").replace(/[ßæœøđðþłı]/gi, (c) => FOLDED[c.toLowerCase()] ?? c));
2316
+ }
2317
+ function nameFromSrc(src) {
2318
+ if (/^data:/i.test(src))
2319
+ return "";
2320
+ const path2 = src.split(/[?#]/, 1)[0] ?? "";
2321
+ const base = path2.split(/[\\/]/).pop() ?? "";
2322
+ let decoded = base;
2323
+ try {
2324
+ decoded = decodeURIComponent(base);
2325
+ } catch {}
2326
+ return nameSlug(decoded.replace(/\.[^.]*$/, ""));
2327
+ }
2328
+ function takeExplicitName(token) {
2329
+ const data = token.attrGet("data-name");
2330
+ const plain = token.attrGet("name");
2331
+ if (plain !== null && token.attrs)
2332
+ token.attrs = token.attrs.filter(([k]) => k !== "name");
2333
+ if (data)
2334
+ return data;
2335
+ if (plain) {
2336
+ token.attrSet("data-name", plain);
2337
+ return plain;
2338
+ }
2339
+ return "";
2340
+ }
2341
+ function inlineText2(inline) {
2342
+ if (!inline || inline.type !== "inline")
2343
+ return "";
2344
+ const parts = [];
2345
+ for (const child of inline.children ?? []) {
2346
+ if (child.type === "text" || child.type === "text_special" || child.type === "code_inline" || child.type === "image") {
2347
+ parts.push(child.content);
2348
+ } else if (child.type === "softbreak" || child.type === "hardbreak") {
2349
+ parts.push(" ");
2350
+ }
2351
+ }
2352
+ return parts.join("");
2353
+ }
2354
+ function claim(used, slug) {
2355
+ let name = slug;
2356
+ for (let n = 2;used.has(name); n++)
2357
+ name = `${slug}-${n}`;
2358
+ used.add(name);
2359
+ return name;
2360
+ }
2361
+ function nameBlocksAndHeadings(tokens) {
2362
+ const blocks = [];
2363
+ const open = [];
2364
+ const headings = [];
2365
+ const headingSlugs = new Map;
2366
+ for (let i = 0;i < tokens.length; i++) {
2367
+ const tok = tokens[i];
2368
+ const opened = BLOCK_OPEN.exec(tok.type);
2369
+ if (opened) {
2370
+ const block = { token: tok, type: opened[1], heading: "" };
2371
+ blocks.push(block);
2372
+ open.push(block);
2373
+ continue;
2374
+ }
2375
+ const closed = BLOCK_CLOSE.exec(tok.type);
2376
+ if (closed) {
2377
+ for (let j = open.length - 1;j >= 0; j--) {
2378
+ if (open[j].type === closed[1]) {
2379
+ open.splice(j, 1);
2380
+ break;
2381
+ }
2382
+ }
2383
+ continue;
2384
+ }
2385
+ if (tok.type === "heading_open") {
2386
+ const slug = nameSlug(inlineText2(tokens[i + 1]));
2387
+ headings.push(tok);
2388
+ headingSlugs.set(tok, slug);
2389
+ const owner = open[open.length - 1];
2390
+ if (owner && !owner.heading)
2391
+ owner.heading = slug;
2392
+ }
2393
+ }
2394
+ const usedBlocks = new Set;
2395
+ const usedHeadings = new Set;
2396
+ const unnamedBlocks = new Set;
2397
+ for (const block of blocks) {
2398
+ const explicit = block.token.attrGet("data-name");
2399
+ if (explicit)
2400
+ usedBlocks.add(explicit);
2401
+ else
2402
+ unnamedBlocks.add(block);
2403
+ }
2404
+ const unnamedHeadings = [];
2405
+ for (const heading of headings) {
2406
+ const explicit = takeExplicitName(heading);
2407
+ if (explicit)
2408
+ usedHeadings.add(explicit);
2409
+ else
2410
+ unnamedHeadings.push(heading);
2411
+ }
2412
+ let previousSection = null;
2413
+ for (const block of blocks) {
2414
+ const { token } = block;
2415
+ if (block.type === "section") {
2416
+ if (token.meta?.continued && previousSection) {
2417
+ const inherited = previousSection.token.attrGet("data-name");
2418
+ if (inherited)
2419
+ token.attrSet("data-name", inherited);
2420
+ previousSection = block;
2421
+ continue;
2422
+ }
2423
+ previousSection = block;
2424
+ }
2425
+ if (!unnamedBlocks.has(block))
2426
+ continue;
2427
+ const slug = nameSlug(token.attrGet("data-label") ?? "") || block.heading;
2428
+ if (slug)
2429
+ token.attrSet("data-name", claim(usedBlocks, slug));
2430
+ }
2431
+ for (const heading of unnamedHeadings) {
2432
+ const slug = headingSlugs.get(heading);
2433
+ if (slug)
2434
+ heading.attrSet("data-name", claim(usedHeadings, slug));
2435
+ }
2436
+ }
2437
+ function gpBlockNames(md) {
2438
+ md.core.ruler.push("gp_block_names", (state) => nameBlocksAndHeadings(state.tokens));
2439
+ }
2440
+
2441
+ // src/lib/markdown/table-align.ts
2442
+ var ALIGN = /^text-align:(left|center|right)$/;
2443
+ function gpTableAlign(md) {
2444
+ md.core.ruler.after("block", "gp_table_align", (state) => {
2445
+ for (const token of state.tokens) {
2446
+ if (token.type !== "th_open" && token.type !== "td_open")
2447
+ continue;
2448
+ const match = ALIGN.exec(token.attrGet("style") ?? "");
2449
+ if (!match)
2450
+ continue;
2451
+ token.attrs = (token.attrs ?? []).filter(([key]) => key !== "style");
2452
+ token.attrSet("data-align", match[1]);
2453
+ }
2454
+ });
2455
+ }
2456
+ var TABLE_ALIGN_CSS = `
2457
+ th[data-align="left"], td[data-align="left"] { text-align: left; }
2458
+ th[data-align="center"], td[data-align="center"] { text-align: center; }
2459
+ th[data-align="right"], td[data-align="right"] { text-align: right; }
2460
+ `;
2461
+
2295
2462
  // src/lib/markdown/renderer.ts
2296
2463
  import markdownItSourceMap from "markdown-it-source-map";
2297
2464
  import markdownItDeflist from "markdown-it-deflist";
@@ -2458,6 +2625,10 @@ function registerImageRule(md) {
2458
2625
  if (srcIdx >= 0 && token.attrs) {
2459
2626
  const src = token.attrs[srcIdx][1];
2460
2627
  if (src) {
2628
+ const explicit = takeExplicitName(token);
2629
+ const named = explicit || nameFromSrc(src);
2630
+ if (named)
2631
+ token.attrSet("data-name", named);
2461
2632
  const bucket = env ?? {};
2462
2633
  (bucket.imageRefs ??= []).push(src);
2463
2634
  const cls = token.attrGet("class");
@@ -2833,6 +3004,8 @@ function createMarkdownRenderer(customPlugins) {
2833
3004
  const declaredMarkers = buildDeclaredMarkerRegistry(declaredMarkerSources);
2834
3005
  md.use(plugin, { declaredMarkers });
2835
3006
  md.use(gpPinScope);
3007
+ md.use(gpBlockNames);
3008
+ md.use(gpTableAlign);
2836
3009
  registerImageRule(md);
2837
3010
  if (customPlugins && customPlugins.length > 0) {
2838
3011
  applyPlugins(md, customPlugins);
@@ -4004,15 +4177,6 @@ async function rollbackNpmPluginInstall(installed) {
4004
4177
  // src/lib/extension-manager.ts
4005
4178
  import { gt as semverGt, rcompare as semverRcompare } from "semver";
4006
4179
 
4007
- // src/lib/slug.ts
4008
- function slugify(name, fallback = "") {
4009
- return name.normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || fallback;
4010
- }
4011
- function prettify(slug) {
4012
- const spaced = slug.replace(/[-_]+/g, " ").trim();
4013
- return spaced.charAt(0).toUpperCase() + spaced.slice(1);
4014
- }
4015
-
4016
4180
  // src/lib/project-gitignore.ts
4017
4181
  import { readFile as readFile5, writeFile } from "node:fs/promises";
4018
4182
  import path4 from "node:path";
@@ -6186,6 +6350,7 @@ var ruleRemoteUrls = "printsafe/no-remote-urls";
6186
6350
  var ruleRiskyProps = "printsafe/no-risky-print-effects";
6187
6351
  var ruleSyntax = "printsafe/syntax-error";
6188
6352
  var rulePageContainment = "printsafe/page-containment";
6353
+ var rulePreviewCannotFollow = "printsafe/preview-cannot-follow";
6189
6354
  function extractUrls(value) {
6190
6355
  const urls = [];
6191
6356
  const re = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
@@ -6331,6 +6496,62 @@ function clipsDescendants(decl) {
6331
6496
  const v = decl.value.trim().toLowerCase();
6332
6497
  return v !== "" && !isInertValue(p, v);
6333
6498
  }
6499
+ var PREVIEW_AT_RULES = new Set(["media", "supports", "layer", "scope", "container", "document"]);
6500
+ var FORCED_BREAK_VALUE = /^(page|left|right|recto|verso)$/i;
6501
+ var LEGACY_FORCED_VALUE = /^(always|left|right)$/i;
6502
+ function previewBlindSpot(decl) {
6503
+ const prop = decl.prop.toLowerCase();
6504
+ const value = decl.value.trim();
6505
+ const written = `${decl.prop}: ${decl.value}`;
6506
+ const isBreak = prop === "break-before" || prop === "break-after";
6507
+ const isLegacy = prop === "page-break-before" || prop === "page-break-after";
6508
+ if (!isBreak && !isLegacy && prop !== "page")
6509
+ return null;
6510
+ const rules = [];
6511
+ for (let p = decl.parent;p; p = p.parent) {
6512
+ if (p.type === "rule")
6513
+ rules.push(p);
6514
+ else if (p.type === "atrule") {
6515
+ const name = p.name.toLowerCase();
6516
+ if (!PREVIEW_AT_RULES.has(name))
6517
+ return null;
6518
+ }
6519
+ }
6520
+ if (rules.length === 0)
6521
+ return null;
6522
+ const consequence = "the PDF honours it but the live preview does not show it, so the two page counts drift apart";
6523
+ if (isLegacy) {
6524
+ if (!LEGACY_FORCED_VALUE.test(value))
6525
+ return null;
6526
+ const modern = prop.replace("page-break-", "break-");
6527
+ const siblings = decl.parent.nodes ?? [];
6528
+ if (siblings.some((n) => n.type === "decl" && n.prop.toLowerCase() === modern))
6529
+ return null;
6530
+ const keyword = /^always$/i.test(value) ? "page" : value.toLowerCase();
6531
+ return `"${written}" is the legacy spelling, and the live preview reads only ${modern}: ${consequence}. Write "${modern}: ${keyword}".`;
6532
+ }
6533
+ const viaVar = /\bvar\(/i.test(value);
6534
+ if (isBreak && !viaVar && !FORCED_BREAK_VALUE.test(value))
6535
+ return null;
6536
+ if (prop === "page" && !viaVar && (value === "" || /^auto$/i.test(value)))
6537
+ return null;
6538
+ const what = prop === "page" ? "page assignment" : "forced break";
6539
+ if (viaVar) {
6540
+ return `The ${what} "${written}" uses var(): the live preview reads it only as a literal ${prop === "page" ? "page name" : "keyword (page, left, right, recto or verso)"}, so ${consequence}. Write the literal value.`;
6541
+ }
6542
+ const parent = decl.parent;
6543
+ if (rules.length >= 2 || parent.type === "atrule") {
6544
+ const outer = rules[rules.length - 1].selector;
6545
+ return `The ${what} "${written}" is inside a nested rule in "${outer}": the live preview reads only top-level rules and rules inside @media, @supports, @layer, @scope, @container and @document, so ${consequence}. Move it to a flat rule of its own.`;
6546
+ }
6547
+ let prev = decl.prev();
6548
+ while (prev && prev.type === "comment")
6549
+ prev = prev.prev();
6550
+ if (prev && (prev.type === "rule" || prev.type === "atrule" && prev.nodes)) {
6551
+ return `The ${what} "${written}" comes right after a nested rule in "${rules[0].selector}": the live preview's CSS reader drops the first declaration after a nested block, so ${consequence}. Move the declaration above the nested rule.`;
6552
+ }
6553
+ return null;
6554
+ }
6334
6555
  function nodeLoc(node) {
6335
6556
  return {
6336
6557
  line: node.source?.start?.line ?? 1,
@@ -6443,6 +6664,12 @@ function checkCssRaw(css, from) {
6443
6664
  });
6444
6665
  }
6445
6666
  });
6667
+ root.walkDecls((decl) => {
6668
+ const message = previewBlindSpot(decl);
6669
+ if (message) {
6670
+ warnings.push({ rule: rulePreviewCannotFollow, severity: "warning", message, ...nodeLoc(decl) });
6671
+ }
6672
+ });
6446
6673
  root.walkRules((rule) => {
6447
6674
  if (rule.parent?.type === "atrule") {
6448
6675
  const at = rule.parent.name.toLowerCase();
@@ -7621,10 +7848,117 @@ registerCheck(check25);
7621
7848
 
7622
7849
  // src/checks/source/layout-markers.ts
7623
7850
  import { readFile as readFile18 } from "node:fs/promises";
7851
+
7852
+ // src/lib/unknown-classes.ts
7853
+ import postcss4 from "postcss";
7854
+ var EMITTED = new Set([
7855
+ "chapter",
7856
+ "spread",
7857
+ "page",
7858
+ "section",
7859
+ "footnotes",
7860
+ "footnotes-sep",
7861
+ "footnote-ref",
7862
+ "footnote-item",
7863
+ "footnote-backref"
7864
+ ]);
7865
+ function classesInTokens(tokens) {
7866
+ const uses = new Map;
7867
+ const note = (token, line) => {
7868
+ const cls = token.attrs?.find(([key]) => key === "class")?.[1];
7869
+ if (!cls)
7870
+ return;
7871
+ for (const c of cls.split(/\s+/)) {
7872
+ if (!c || c.startsWith("gp-") || EMITTED.has(c) || /^chapter-\d+$/.test(c))
7873
+ continue;
7874
+ const seen = uses.get(c);
7875
+ if (seen)
7876
+ seen.count++;
7877
+ else
7878
+ uses.set(c, { line, count: 1 });
7879
+ }
7880
+ };
7881
+ for (const token of tokens) {
7882
+ const meta = token.meta;
7883
+ const line = token.map ? token.map[0] + 1 : typeof meta?.line === "number" ? meta.line : 0;
7884
+ note(token, line);
7885
+ for (const child of token.children ?? [])
7886
+ note(child, line);
7887
+ }
7888
+ return uses;
7889
+ }
7890
+ var CLASS_RE = /\.((?:[\w -￿-]|\\.)+)/g;
7891
+ function addSelectorClasses(selector, into) {
7892
+ const bare = selector.replace(/\[(?:[^\]"']|"[^"]*"|'[^']*')*\]/g, "").replace(/"[^"]*"|'[^']*'/g, "");
7893
+ for (const m of bare.matchAll(CLASS_RE)) {
7894
+ const name = m[1].replace(/\\(.)/g, "$1");
7895
+ if (!/^\d/.test(name))
7896
+ into.add(name);
7897
+ }
7898
+ }
7899
+ function addCssClasses(css, into) {
7900
+ try {
7901
+ postcss4.parse(css).walk((node) => {
7902
+ if (node.type === "rule")
7903
+ addSelectorClasses(node.selector, into);
7904
+ else if (node.type === "atrule") {
7905
+ const at = node;
7906
+ if (/^(scope|container)$/i.test(at.name))
7907
+ addSelectorClasses(at.params, into);
7908
+ }
7909
+ });
7910
+ } catch {
7911
+ addSelectorClasses(css.replace(/\/\*[\s\S]*?\*\//g, ""), into);
7912
+ }
7913
+ }
7914
+ function editDistance3(a, b) {
7915
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
7916
+ for (let i = 1;i <= a.length; i++) {
7917
+ const cur = [i];
7918
+ for (let j = 1;j <= b.length; j++) {
7919
+ cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
7920
+ }
7921
+ prev = cur;
7922
+ }
7923
+ return prev[b.length];
7924
+ }
7925
+ function nearestKnownClass(cls, known) {
7926
+ const limit = cls.length >= 6 ? 2 : 1;
7927
+ let best = null;
7928
+ let bestDistance = limit + 1;
7929
+ for (const candidate of known) {
7930
+ if (Math.abs(candidate.length - cls.length) > limit)
7931
+ continue;
7932
+ const d = editDistance3(cls, candidate);
7933
+ if (d > 0 && d < bestDistance) {
7934
+ best = candidate;
7935
+ bestDistance = d;
7936
+ }
7937
+ }
7938
+ return best;
7939
+ }
7940
+
7941
+ // src/checks/source/layout-markers.ts
7942
+ async function collectKnownClasses(ctx, plugins) {
7943
+ const names = new Set;
7944
+ addCssClasses(MARKER_CSS, names);
7945
+ addCssClasses(GUTTERPRESS_CSS, names);
7946
+ for (const p of plugins)
7947
+ if (p.css)
7948
+ addCssClasses(p.css, names);
7949
+ const roots = [...ctx.cssFiles ?? [], ...plugins.flatMap((p) => p.styles ?? [])];
7950
+ const sheets = (await collectStyleDependencies(ctx.inputDir, roots)).filter((f) => /\.css$/i.test(f));
7951
+ for (const sheet of sheets) {
7952
+ try {
7953
+ addCssClasses(await readFile18(sheet, "utf8"), names);
7954
+ } catch {}
7955
+ }
7956
+ return names;
7957
+ }
7624
7958
  var check26 = {
7625
7959
  id: "source.markdown.layout-markers",
7626
7960
  name: "Layout Markers",
7627
- description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class",
7961
+ description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class and any other class no stylesheet mentions",
7628
7962
  category: "source",
7629
7963
  phase: "pre-build",
7630
7964
  async run(ctx) {
@@ -7636,11 +7970,31 @@ var check26 = {
7636
7970
  results.push(inspectionFailed(check26.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
7637
7971
  });
7638
7972
  const md = createMarkdownRenderer(plugins);
7973
+ let known;
7974
+ const knownClasses = () => known ??= collectKnownClasses(ctx, plugins);
7639
7975
  for (const file of files) {
7640
7976
  try {
7641
7977
  const content = await readFile18(file, "utf8");
7642
7978
  const env = {};
7643
- md.render(content, env);
7979
+ const tokens = md.parse(content, env);
7980
+ md.renderer.render(tokens, md.options, env);
7981
+ const uses = classesInTokens(tokens);
7982
+ if (uses.size > 0) {
7983
+ const names = await knownClasses();
7984
+ const warnedLines = new Set((env.layoutWarnings ?? []).map((w) => w.line));
7985
+ for (const [cls, use] of uses) {
7986
+ if (names.has(cls) || warnedLines.has(use.line))
7987
+ continue;
7988
+ const guess = nearestKnownClass(cls, names);
7989
+ results.push(finding(check26.id, {
7990
+ severity: "warning",
7991
+ message: `Class ".${cls}" is not in any stylesheet (Gutterpress's own, the plugins', or this book's), so it styles nothing.` + (guess ? ` Did you mean ".${guess}"?` : "") + (use.count > 1 ? ` Used ${use.count} times in this file; the first is reported.` : ""),
7992
+ file,
7993
+ ...use.line > 0 ? { line: use.line } : {},
7994
+ code: "unknown_class"
7995
+ }));
7996
+ }
7997
+ }
7644
7998
  for (const w of env.layoutWarnings ?? []) {
7645
7999
  results.push(finding(check26.id, {
7646
8000
  severity: w.severity ?? "warning",
@@ -8615,6 +8969,8 @@ ${GUTTERPRESS_CSS.trim()}
8615
8969
  }`,
8616
8970
  pluginCss ? `/* extension css */
8617
8971
  ${pluginCss.trim()}` : null,
8972
+ `/* column alignment */
8973
+ ${TABLE_ALIGN_CSS.trim()}`,
8618
8974
  projectCss ? `/* project css */
8619
8975
  ${projectCss.trim()}` : null
8620
8976
  ].filter(Boolean).join(`
@@ -9726,7 +10082,7 @@ class PdfOutput {
9726
10082
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
9727
10083
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
9728
10084
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
9729
- const { buildNativePdf } = await import("./engine-jkf4pxz9.js");
10085
+ const { buildNativePdf } = await import("./engine-6zfj0jte.js");
9730
10086
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
9731
10087
  title: config.title,
9732
10088
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10952,7 +11308,7 @@ import { constants as FS, existsSync as existsSync12 } from "node:fs";
10952
11308
  import path18 from "node:path";
10953
11309
 
10954
11310
  // src/lib/page-size.ts
10955
- import postcss4 from "postcss";
11311
+ import postcss5 from "postcss";
10956
11312
  import { Scalar as Scalar2 } from "yaml";
10957
11313
  function isPositivePoints(value) {
10958
11314
  return typeof value === "number" && Number.isFinite(value) && value > 0;