gutterpress 0.11.21 → 0.11.22-alpha.2

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 (62) hide show
  1. package/README.md +11 -0
  2. package/dist/api/index.d.ts +5 -1
  3. package/dist/api/index.js +68 -47
  4. package/dist/{build-0neyk5h6.js → build-84retk4k.js} +16 -10
  5. package/dist/{index-sth219v1.js → cli-2ppw6q9v.js} +26 -6
  6. package/dist/cli-3twpesca.js +103 -0
  7. package/dist/cli-5et23z83.js +2089 -0
  8. package/dist/{cli-j79451n6.js → cli-5qhjwne5.js} +5 -76
  9. package/dist/cli-80t93x2r.js +75 -0
  10. package/dist/{cli-j0drzb6f.js → cli-885pcze8.js} +885 -5481
  11. package/dist/{cli-b57adna7.js → cli-9pk65fqf.js} +9 -201
  12. package/dist/{cli-sqkccnm7.js → cli-d8xjns66.js} +1 -102
  13. package/dist/cli-h3phdstf.js +200 -0
  14. package/dist/{index-qf7y4d85.js → cli-habkc5ng.js} +12 -258
  15. package/dist/cli-hndkg7bn.js +196 -0
  16. package/dist/cli-pn3z0y9p.js +3073 -0
  17. package/dist/cli.js +15 -13
  18. package/dist/{doctor-fptq8tvn.js → doctor-fmzpg7ag.js} +7 -4
  19. package/dist/engine/shared/print-area.d.ts +77 -1
  20. package/dist/{engine-x07q927h.js → engine-5p8tn9b4.js} +4 -3
  21. package/dist/{engine-0b8bt05n.js → engine-9710cd7x.js} +3 -2
  22. package/dist/{ext-gp624t5c.js → ext-hsdnad5c.js} +21 -14
  23. package/dist/extension-restore-05pae3cp.js +16 -0
  24. package/dist/extension-restore-3r7phztr.js +20 -0
  25. package/dist/{gutterpress-viewer-w7njnkj7.js → gutterpress-viewer-w9jzx6sb.js} +56 -52
  26. package/dist/{index-t7r8g75c.js → index-2d8j0enc.js} +4 -2
  27. package/dist/{cli-sth219v1.js → index-2ppw6q9v.js} +26 -6
  28. package/dist/{index-mr7zs8mv.js → index-3wcr69ba.js} +5579 -10055
  29. package/dist/{cli-5qr3z0qa.js → index-93pqkvty.js} +63 -207
  30. package/dist/{index-mr72vt8n.js → index-apc8w9w8.js} +55 -267
  31. package/dist/index-ew7cyk7c.js +2208 -0
  32. package/dist/index-jsz0s3dy.js +213 -0
  33. package/dist/index-kx03e4m4.js +3244 -0
  34. package/dist/index-r7jcsk6h.js +200 -0
  35. package/dist/index.js +71 -50
  36. package/dist/lib/extension-manifest.d.ts +22 -1
  37. package/dist/lib/markdown/assemble.d.ts +3 -2
  38. package/dist/lib/markdown/plugins.d.ts +20 -0
  39. package/dist/lib/markdown/two-versions.d.ts +32 -0
  40. package/dist/lib/page-size.d.ts +3 -4
  41. package/dist/lib/pdf-inspect.d.ts +12 -0
  42. package/dist/lib/project-scaffold.d.ts +3 -3
  43. package/dist/lib/publish/plugin-provider.d.ts +16 -0
  44. package/dist/lib/publish/run-publish.d.ts +13 -5
  45. package/dist/lib/publish/types.d.ts +34 -2
  46. package/dist/lib/text-diff.d.ts +43 -0
  47. package/dist/lib/two-versions-markers.d.ts +26 -0
  48. package/dist/lib/version-changes.d.ts +87 -0
  49. package/dist/{manifest.schema-n292sb77.json → manifest.schema-nvawq0xv.json} +2 -2
  50. package/dist/{new-dre6aw05.js → new-fqtt022k.js} +23 -16
  51. package/dist/{pdf-inspect-zdmy7qca.js → pdf-inspect-wnw4za71.js} +1 -1
  52. package/dist/plugins-gfvsgnxb.js +24 -0
  53. package/dist/plugins-nfxr831f.js +26 -0
  54. package/dist/{preflight-1k1zkg1p.js → preflight-gr9mscjw.js} +18 -11
  55. package/dist/{preview-dtrksvxf.js → preview-52w9pdps.js} +19 -13
  56. package/dist/{publish-g1ytnrmr.js → publish-esmcyhpb.js} +40 -23
  57. package/dist/render.js +706 -0
  58. package/dist/schema/manifest.types.d.ts +8 -3
  59. package/dist/{source-provider-pzmz0br9.js → source-provider-2r74a7wp.js} +2 -1
  60. package/dist/{source-provider-qbthnmqw.js → source-provider-qbfknhyy.js} +2 -1
  61. package/dist/{validate-yrrq8639.js → validate-vt1d7h6k.js} +16 -10
  62. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -1477,6 +1477,124 @@ var GP_CLASSES = new Set([
1477
1477
  "gp-continued"
1478
1478
  ]);
1479
1479
 
1480
+ // src/lib/two-versions-markers.ts
1481
+ var YOUR_VERSION_LABEL = "your version";
1482
+ var ONLINE_VERSION_LABEL = "online version";
1483
+ var TWO_VERSIONS_OPEN = `<<<<<<< ${YOUR_VERSION_LABEL}`;
1484
+ var TWO_VERSIONS_SEPARATOR = "=======";
1485
+ var TWO_VERSIONS_CLOSE = `>>>>>>> ${ONLINE_VERSION_LABEL}`;
1486
+
1487
+ // src/lib/markdown/two-versions.ts
1488
+ var TWO_VERSIONS_CSS = `
1489
+ .gp-two-versions {
1490
+ margin: 1em 0;
1491
+ border: 2px dashed #b7791f;
1492
+ border-radius: 6px;
1493
+ break-inside: avoid;
1494
+ }
1495
+ .gp-two-versions > .gp-version {
1496
+ padding: 0.5em 0.9em;
1497
+ }
1498
+ .gp-two-versions > .gp-version + .gp-version {
1499
+ border-top: 2px dashed #b7791f;
1500
+ }
1501
+ .gp-two-versions > .gp-version > .gp-version-text {
1502
+ margin: 0;
1503
+ white-space: pre-wrap;
1504
+ overflow-wrap: anywhere;
1505
+ font-size: 0.85em;
1506
+ }
1507
+ .gp-two-versions > .gp-version::before {
1508
+ content: attr(data-label);
1509
+ display: block;
1510
+ margin-bottom: 0.35em;
1511
+ font-family: system-ui, sans-serif;
1512
+ font-size: 0.7em;
1513
+ font-weight: 700;
1514
+ letter-spacing: 0.06em;
1515
+ text-transform: uppercase;
1516
+ color: #b7791f;
1517
+ }
1518
+ `;
1519
+ function lineText(state, line) {
1520
+ return state.src.slice(state.bMarks[line] + state.tShift[line], state.eMarks[line]);
1521
+ }
1522
+ var MARKER_LINE = /^@[a-z]/;
1523
+ function hasMarkerLine(state, from, to) {
1524
+ for (let line = from;line < to; line++) {
1525
+ if (MARKER_LINE.test(lineText(state, line)))
1526
+ return true;
1527
+ }
1528
+ return false;
1529
+ }
1530
+ function pushVerbatim(state, from, to) {
1531
+ const raw = state.getLines(from, to, 0, false);
1532
+ const token = state.push("html_block", "", 0);
1533
+ token.map = [from, to];
1534
+ token.block = true;
1535
+ token.content = `<pre class="gp-version-text">${state.md.utils.escapeHtml(raw)}</pre>
1536
+ `;
1537
+ }
1538
+ function pushVersion(state, which, label, from, to) {
1539
+ const open = state.push("two_version_open", "div", 1);
1540
+ open.attrSet("class", `gp-version gp-version-${which}`);
1541
+ open.attrSet("data-label", label);
1542
+ open.map = [from, to];
1543
+ open.block = true;
1544
+ if (hasMarkerLine(state, from, to)) {
1545
+ pushVerbatim(state, from, to);
1546
+ } else {
1547
+ state.parentType = "container";
1548
+ state.lineMax = to;
1549
+ state.md.block.tokenize(state, from, to);
1550
+ }
1551
+ const close = state.push("two_version_close", "div", -1);
1552
+ close.block = true;
1553
+ }
1554
+ function twoVersions(state, startLine, endLine, silent) {
1555
+ if (state.sCount[startLine] - state.blkIndent >= 4)
1556
+ return false;
1557
+ if (state.src.charCodeAt(state.bMarks[startLine] + state.tShift[startLine]) !== 60)
1558
+ return false;
1559
+ if (lineText(state, startLine) !== TWO_VERSIONS_OPEN)
1560
+ return false;
1561
+ let separator = -1;
1562
+ let closer = -1;
1563
+ for (let line = startLine + 1;line < endLine; line++) {
1564
+ const text = lineText(state, line);
1565
+ if (separator < 0 && text === TWO_VERSIONS_SEPARATOR)
1566
+ separator = line;
1567
+ else if (text === TWO_VERSIONS_CLOSE) {
1568
+ closer = line;
1569
+ break;
1570
+ } else if (text === TWO_VERSIONS_OPEN)
1571
+ return false;
1572
+ }
1573
+ if (separator < 0 || closer < 0)
1574
+ return false;
1575
+ if (silent)
1576
+ return true;
1577
+ const oldParent = state.parentType;
1578
+ const oldLineMax = state.lineMax;
1579
+ const open = state.push("two_versions_open", "div", 1);
1580
+ open.attrSet("class", "gp-two-versions");
1581
+ open.map = [startLine, closer + 1];
1582
+ open.block = true;
1583
+ pushVersion(state, "yours", "Your version", startLine + 1, separator);
1584
+ pushVersion(state, "online", "Online version", separator + 1, closer);
1585
+ const close = state.push("two_versions_close", "div", -1);
1586
+ close.block = true;
1587
+ state.parentType = oldParent;
1588
+ state.lineMax = oldLineMax;
1589
+ state.line = closer + 1;
1590
+ return true;
1591
+ }
1592
+ function gpTwoVersions(md) {
1593
+ md.block.ruler.before("html_block", "two_versions", twoVersions, {
1594
+ alt: ["paragraph", "reference", "blockquote", "list"]
1595
+ });
1596
+ }
1597
+
1480
1598
  // src/lib/markdown/table-align.ts
1481
1599
  var ALIGN = /^text-align:(left|center|right)$/;
1482
1600
  function gpTableAlign(md) {
@@ -2352,6 +2470,7 @@ function createMarkdownRenderer(customPlugins) {
2352
2470
  md.use(gpPinScope);
2353
2471
  md.use(gpBlockNames);
2354
2472
  md.use(gpTableAlign);
2473
+ md.use(gpTwoVersions);
2355
2474
  registerImageRule(md);
2356
2475
  if (customPlugins && customPlugins.length > 0) {
2357
2476
  applyPlugins(md, customPlugins);
@@ -2388,6 +2507,17 @@ function lastPageMode(print) {
2388
2507
  var WHICH_VALUES = new Set(["first", "start", "last", "first-except"]);
2389
2508
 
2390
2509
  // src/engine/shared/print-area.ts
2510
+ function isPositivePoints(value) {
2511
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
2512
+ }
2513
+ var round3 = (n) => Math.round(n * 1000) / 1000;
2514
+ function cssLength(pt) {
2515
+ const inches = round3(pt / 72);
2516
+ return Math.abs(inches * 72 - pt) < 0.000001 ? `${inches}in` : `${round3(pt)}pt`;
2517
+ }
2518
+ function formatPageSize(page) {
2519
+ return `${cssLength(page.width)} ${cssLength(page.height)}`;
2520
+ }
2391
2521
  function printAreaMeta(page) {
2392
2522
  const area = JSON.stringify({
2393
2523
  width: page.width,
@@ -2402,7 +2532,578 @@ function printAreaMeta(page) {
2402
2532
  return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
2403
2533
  }
2404
2534
 
2535
+ // src/engine/shared/gcpm-extract.ts
2536
+ var NESTED_AT_RULES = /^@(media|supports|layer|scope|container|document)\b/i;
2537
+ function scanRules(css) {
2538
+ const out = [];
2539
+ let i = 0;
2540
+ let start = 0;
2541
+ let depth = 0;
2542
+ let bodyStart = -1;
2543
+ while (i < css.length) {
2544
+ const c = css[i];
2545
+ if (c === "/" && css[i + 1] === "*") {
2546
+ const end = css.indexOf("*/", i + 2);
2547
+ i = end === -1 ? css.length : end + 2;
2548
+ continue;
2549
+ }
2550
+ if (c === '"' || c === "'") {
2551
+ i = skipString(css, i);
2552
+ continue;
2553
+ }
2554
+ if (c === "{") {
2555
+ if (depth === 0)
2556
+ bodyStart = i;
2557
+ depth++;
2558
+ i++;
2559
+ continue;
2560
+ }
2561
+ if (c === "}") {
2562
+ depth--;
2563
+ if (depth === 0) {
2564
+ out.push({
2565
+ prelude: stripComments(css.slice(start, bodyStart)).trim(),
2566
+ body: css.slice(bodyStart + 1, i)
2567
+ });
2568
+ start = i + 1;
2569
+ }
2570
+ i++;
2571
+ continue;
2572
+ }
2573
+ if (c === ";" && depth === 0) {
2574
+ const stmt = stripComments(css.slice(start, i)).trim();
2575
+ if (stmt)
2576
+ out.push({ statement: stmt });
2577
+ start = i + 1;
2578
+ i++;
2579
+ continue;
2580
+ }
2581
+ i++;
2582
+ }
2583
+ return out;
2584
+ }
2585
+ function stripComments(s) {
2586
+ let out = "";
2587
+ let i = 0;
2588
+ while (i < s.length) {
2589
+ if (s[i] === "/" && s[i + 1] === "*") {
2590
+ const end = s.indexOf("*/", i + 2);
2591
+ i = end === -1 ? s.length : end + 2;
2592
+ continue;
2593
+ }
2594
+ if (s[i] === '"' || s[i] === "'") {
2595
+ const end = skipString(s, i);
2596
+ out += s.slice(i, end);
2597
+ i = end;
2598
+ continue;
2599
+ }
2600
+ out += s[i++];
2601
+ }
2602
+ return out;
2603
+ }
2604
+ function skipString(css, i) {
2605
+ const quote = css[i];
2606
+ i++;
2607
+ while (i < css.length) {
2608
+ if (css[i] === "\\")
2609
+ i += 2;
2610
+ else if (css[i] === quote)
2611
+ return i + 1;
2612
+ else
2613
+ i++;
2614
+ }
2615
+ return i;
2616
+ }
2617
+ function parseDeclarations(body) {
2618
+ const decls = {};
2619
+ let i = 0;
2620
+ let start = 0;
2621
+ let depth = 0;
2622
+ const push = (chunk) => {
2623
+ const s = stripComments(chunk).trim();
2624
+ if (!s)
2625
+ return;
2626
+ const colon = indexOfTopLevel(s, ":");
2627
+ if (colon <= 0)
2628
+ return;
2629
+ const rawProp = s.slice(0, colon).trim();
2630
+ const prop = rawProp.startsWith("--") ? rawProp : rawProp.toLowerCase();
2631
+ const value = s.slice(colon + 1).trim().replace(/\s*!important$/i, "");
2632
+ if (prop)
2633
+ decls[prop] = value;
2634
+ };
2635
+ while (i < body.length) {
2636
+ const c = body[i];
2637
+ if (c === "/" && body[i + 1] === "*") {
2638
+ const end = body.indexOf("*/", i + 2);
2639
+ i = end === -1 ? body.length : end + 2;
2640
+ continue;
2641
+ }
2642
+ if (c === '"' || c === "'") {
2643
+ i = skipString(body, i);
2644
+ continue;
2645
+ }
2646
+ if (c === "(" || c === "{" || c === "[")
2647
+ depth++;
2648
+ else if (c === ")" || c === "}" || c === "]")
2649
+ depth--;
2650
+ else if (c === ";" && depth === 0) {
2651
+ push(body.slice(start, i));
2652
+ start = i + 1;
2653
+ }
2654
+ i++;
2655
+ }
2656
+ push(body.slice(start));
2657
+ return decls;
2658
+ }
2659
+ function indexOfTopLevel(s, ch) {
2660
+ let depth = 0;
2661
+ for (let i = 0;i < s.length; i++) {
2662
+ const c = s[i];
2663
+ if (c === '"' || c === "'") {
2664
+ i = skipString(s, i) - 1;
2665
+ continue;
2666
+ }
2667
+ if (c === "(")
2668
+ depth++;
2669
+ else if (c === ")")
2670
+ depth--;
2671
+ else if (c === ch && depth === 0)
2672
+ return i;
2673
+ }
2674
+ return -1;
2675
+ }
2676
+ function extract(css) {
2677
+ const model = {
2678
+ pageRules: [],
2679
+ stringSets: [],
2680
+ pageAssignments: [],
2681
+ breaks: [],
2682
+ xrefs: [],
2683
+ counterResets: [],
2684
+ pageNames: [],
2685
+ warnings: []
2686
+ };
2687
+ walk(css, model);
2688
+ resolveGeometryVars(model, collectRootCustomProperties(css));
2689
+ const names = new Set;
2690
+ for (const r of model.pageRules)
2691
+ if (r.name)
2692
+ names.add(r.name);
2693
+ for (const a of model.pageAssignments)
2694
+ names.add(a.page);
2695
+ model.pageNames = [...names];
2696
+ return model;
2697
+ }
2698
+ var GEOMETRY_PROPS = [
2699
+ "size",
2700
+ "margin",
2701
+ "margin-top",
2702
+ "margin-right",
2703
+ "margin-bottom",
2704
+ "margin-left",
2705
+ "bleed",
2706
+ "marks"
2707
+ ];
2708
+ function collectRootCustomProperties(css) {
2709
+ const props = new Map;
2710
+ const walkForRoot = (body) => {
2711
+ for (const rule of scanRules(body)) {
2712
+ if ("statement" in rule)
2713
+ continue;
2714
+ const { prelude, body: ruleBody } = rule;
2715
+ if (NESTED_AT_RULES.test(prelude)) {
2716
+ walkForRoot(ruleBody);
2717
+ continue;
2718
+ }
2719
+ if (!prelude.startsWith("@")) {
2720
+ const selectors = splitTopLevel(prelude, ",");
2721
+ if (selectors.some((s) => s.trim() === ":root")) {
2722
+ const decls = parseDeclarations(ruleBody);
2723
+ for (const [k, v] of Object.entries(decls)) {
2724
+ if (k.startsWith("--"))
2725
+ props.set(k, v);
2726
+ }
2727
+ }
2728
+ }
2729
+ }
2730
+ };
2731
+ walkForRoot(css);
2732
+ return props;
2733
+ }
2734
+ function matchParen(s, open) {
2735
+ let depth = 0;
2736
+ let i = open;
2737
+ while (i < s.length) {
2738
+ const c = s[i];
2739
+ if (c === '"' || c === "'") {
2740
+ i = skipString(s, i);
2741
+ continue;
2742
+ }
2743
+ if (c === "(")
2744
+ depth++;
2745
+ else if (c === ")") {
2746
+ depth--;
2747
+ if (depth === 0)
2748
+ return i;
2749
+ }
2750
+ i++;
2751
+ }
2752
+ return -1;
2753
+ }
2754
+ function resolveVarsInValue(value, customProps, stack = new Set) {
2755
+ let out = "";
2756
+ let i = 0;
2757
+ while (i < value.length) {
2758
+ const isVarStart = /^var\(/i.test(value.slice(i, i + 4)) && (i === 0 || !/[\w-]/.test(value[i - 1]));
2759
+ if (!isVarStart) {
2760
+ out += value[i];
2761
+ i++;
2762
+ continue;
2763
+ }
2764
+ const open = i + 3;
2765
+ const close = matchParen(value, open);
2766
+ if (close === -1) {
2767
+ out += value.slice(i);
2768
+ break;
2769
+ }
2770
+ const inner = value.slice(open + 1, close);
2771
+ const commaIdx = indexOfTopLevel(inner, ",");
2772
+ const name = (commaIdx === -1 ? inner : inner.slice(0, commaIdx)).trim();
2773
+ const fallback = commaIdx === -1 ? undefined : inner.slice(commaIdx + 1).trim();
2774
+ if (!/^--[\w-]+$/.test(name)) {
2775
+ return { text: out, unresolved: `var(${inner}) is not a valid custom property reference` };
2776
+ }
2777
+ if (stack.has(name)) {
2778
+ return { text: out, unresolved: `var(${name}) is circular` };
2779
+ }
2780
+ const rootValue = customProps.get(name);
2781
+ if (rootValue !== undefined) {
2782
+ const nested = resolveVarsInValue(rootValue, customProps, new Set(stack).add(name));
2783
+ if (nested.unresolved)
2784
+ return { text: out, unresolved: nested.unresolved };
2785
+ out += nested.text;
2786
+ } else if (fallback !== undefined) {
2787
+ if (/\bvar\(/i.test(fallback)) {
2788
+ return {
2789
+ text: out,
2790
+ unresolved: `var(${name}, ${fallback}) — a fallback containing another var() is not resolved`
2791
+ };
2792
+ }
2793
+ out += fallback;
2794
+ } else {
2795
+ return {
2796
+ text: out,
2797
+ unresolved: `${name} is not defined at :root and var() has no fallback`
2798
+ };
2799
+ }
2800
+ i = close + 1;
2801
+ }
2802
+ return { text: out };
2803
+ }
2804
+ function resolveGeometryVars(model, customProps) {
2805
+ for (const rule of model.pageRules) {
2806
+ for (const prop of GEOMETRY_PROPS) {
2807
+ const value = rule.decls[prop];
2808
+ if (!value || !/\bvar\(/i.test(value))
2809
+ continue;
2810
+ const { text, unresolved } = resolveVarsInValue(value, customProps);
2811
+ if (unresolved) {
2812
+ throw new Error(`${rule.raw} { ${prop}: ${value} } — cannot resolve ${unresolved}. ` + `Define the custom property at :root, add a literal var(--x, fallback) fallback, or use a literal value.`);
2813
+ }
2814
+ const unparsable = unparsableGeometry(prop, text);
2815
+ if (unparsable) {
2816
+ throw new Error(`${rule.raw} { ${prop}: ${value} } — resolves to \`${text.trim()}\`, ${unparsable}. ` + `Use a value this engine can read (a length like 0.75in/12pt/10mm, or a named page size for \`size\`).`);
2817
+ }
2818
+ rule.decls[prop] = text;
2819
+ }
2820
+ }
2821
+ }
2822
+ function unparsableGeometry(prop, text) {
2823
+ const t = text.trim();
2824
+ if (prop === "size") {
2825
+ if (!t)
2826
+ return "which is empty";
2827
+ return parseSize(t) ? undefined : "which is not a page size";
2828
+ }
2829
+ if (prop === "margin" || prop.startsWith("margin-")) {
2830
+ if (!t)
2831
+ return "which is empty";
2832
+ const parts = t.split(/\s+/);
2833
+ if (prop !== "margin" && parts.length !== 1)
2834
+ return "which is not a single length";
2835
+ if (parts.length > 4)
2836
+ return "which is not a valid margin";
2837
+ const bad = parts.filter((p) => toPt(p) === undefined);
2838
+ if (bad.length)
2839
+ return `which this engine cannot read as a length (\`${bad[0]}\`)`;
2840
+ return;
2841
+ }
2842
+ if (prop === "bleed") {
2843
+ if (!t)
2844
+ return "which is empty";
2845
+ if (/^(auto|none)$/i.test(t))
2846
+ return;
2847
+ return toPt(t) === undefined ? "which this engine cannot read as a length" : undefined;
2848
+ }
2849
+ return;
2850
+ }
2851
+ function walk(css, model) {
2852
+ for (const rule of scanRules(css)) {
2853
+ if ("statement" in rule)
2854
+ continue;
2855
+ const { prelude, body } = rule;
2856
+ if (NESTED_AT_RULES.test(prelude)) {
2857
+ walk(body, model);
2858
+ continue;
2859
+ }
2860
+ if (/^@page\b/i.test(prelude)) {
2861
+ model.pageRules.push(parsePageRule(prelude, body));
2862
+ continue;
2863
+ }
2864
+ if (prelude.startsWith("@"))
2865
+ continue;
2866
+ parseQualifiedRule(prelude, body, model);
2867
+ }
2868
+ }
2869
+ function parsePageRule(prelude, body) {
2870
+ const sel = prelude.replace(/^@page\s*/i, "").trim();
2871
+ const nameMatch = /^([A-Za-z_][\w-]*)/.exec(sel);
2872
+ const pseudos = [...sel.matchAll(/:([A-Za-z-]+)(\([^)]*\))?/g)].map((m) => m[1] + (m[2] ?? ""));
2873
+ const rule = {
2874
+ name: nameMatch?.[1],
2875
+ pseudos,
2876
+ decls: {},
2877
+ marginBoxes: {},
2878
+ raw: `@page ${sel}`.trim()
2879
+ };
2880
+ let rest = "";
2881
+ let i = 0;
2882
+ while (i < body.length) {
2883
+ const at = findNextAtRule(body, i);
2884
+ if (at === -1) {
2885
+ rest += body.slice(i);
2886
+ break;
2887
+ }
2888
+ rest += body.slice(i, at);
2889
+ const open = body.indexOf("{", at);
2890
+ if (open === -1) {
2891
+ rest += body.slice(at);
2892
+ break;
2893
+ }
2894
+ const name = body.slice(at + 1, open).trim().toLowerCase();
2895
+ let depth = 1;
2896
+ let j = open + 1;
2897
+ while (j < body.length && depth > 0) {
2898
+ const c = body[j];
2899
+ if (c === "/" && body[j + 1] === "*") {
2900
+ const end = body.indexOf("*/", j + 2);
2901
+ j = end === -1 ? body.length : end + 2;
2902
+ continue;
2903
+ }
2904
+ if (c === '"' || c === "'") {
2905
+ j = skipString(body, j);
2906
+ continue;
2907
+ }
2908
+ if (c === "{")
2909
+ depth++;
2910
+ else if (c === "}")
2911
+ depth--;
2912
+ j++;
2913
+ }
2914
+ rule.marginBoxes[`@${name}`] = parseDeclarations(body.slice(open + 1, j - 1));
2915
+ i = j;
2916
+ }
2917
+ rule.decls = parseDeclarations(rest);
2918
+ return rule;
2919
+ }
2920
+ function findNextAtRule(body, start) {
2921
+ let i = start;
2922
+ while (i < body.length) {
2923
+ const c = body[i];
2924
+ if (c === "/" && body[i + 1] === "*") {
2925
+ const end = body.indexOf("*/", i + 2);
2926
+ i = end === -1 ? body.length : end + 2;
2927
+ continue;
2928
+ }
2929
+ if (c === '"' || c === "'") {
2930
+ i = skipString(body, i);
2931
+ continue;
2932
+ }
2933
+ if (c === "@")
2934
+ return i;
2935
+ i++;
2936
+ }
2937
+ return -1;
2938
+ }
2939
+ function parseQualifiedRule(selector, body, model) {
2940
+ const decls = parseDeclarations(body);
2941
+ for (const [prop, value] of Object.entries(decls)) {
2942
+ if (prop === "string-set") {
2943
+ for (const entry of splitTopLevel(value, ",")) {
2944
+ const m = /^\s*([A-Za-z_][\w-]*)\s+(.+)$/.exec(entry);
2945
+ if (m && m[1] !== undefined && m[2] !== undefined)
2946
+ model.stringSets.push({ selector, name: m[1], value: m[2].trim() });
2947
+ }
2948
+ } else if (prop === "page") {
2949
+ if (value && value !== "auto")
2950
+ model.pageAssignments.push({ selector, page: value.trim() });
2951
+ } else if (prop === "break-before" || prop === "break-after" || prop === "break-inside") {
2952
+ model.breaks.push({ selector, prop, value });
2953
+ } else if (prop === "counter-reset") {
2954
+ const m = /\bpage\s+(-?\d+)/.exec(value);
2955
+ if (m)
2956
+ model.counterResets.push({ selector, start: Number(m[1]) });
2957
+ } else if (prop === "content") {
2958
+ for (const fn of ["target-counter", "target-text", "leader", "string"]) {
2959
+ if (new RegExp(`\\b${fn}\\s*\\(`).test(value)) {
2960
+ model.xrefs.push({ selector, content: value, fn });
2961
+ }
2962
+ }
2963
+ }
2964
+ }
2965
+ }
2966
+ function splitTopLevel(s, sep) {
2967
+ const out = [];
2968
+ let depth = 0;
2969
+ let start = 0;
2970
+ for (let i = 0;i < s.length; i++) {
2971
+ const c = s[i];
2972
+ if (c === '"' || c === "'") {
2973
+ i = skipString(s, i) - 1;
2974
+ continue;
2975
+ }
2976
+ if (c === "(")
2977
+ depth++;
2978
+ else if (c === ")")
2979
+ depth--;
2980
+ else if (c === sep && depth === 0) {
2981
+ out.push(s.slice(start, i));
2982
+ start = i + 1;
2983
+ }
2984
+ }
2985
+ out.push(s.slice(start));
2986
+ return out.map((x) => x.trim()).filter(Boolean);
2987
+ }
2988
+ var UNITS_PER_PT = {
2989
+ pt: 1,
2990
+ px: 0.75,
2991
+ in: 72,
2992
+ pc: 12,
2993
+ cm: 72 / 2.54,
2994
+ mm: 72 / 25.4,
2995
+ q: 72 / 101.6
2996
+ };
2997
+ function toPt(value) {
2998
+ const m = /^(-?[\d.]+)([a-z%]*)$/i.exec(value.trim());
2999
+ if (!m)
3000
+ return;
3001
+ const n = Number(m[1]);
3002
+ const unit = (m[2] || "px").toLowerCase();
3003
+ const factor = UNITS_PER_PT[unit];
3004
+ return factor === undefined ? undefined : n * factor;
3005
+ }
3006
+ var PAGE_SIZES = {
3007
+ a5: [419.53, 595.28],
3008
+ a4: [595.28, 841.89],
3009
+ a3: [841.89, 1190.55],
3010
+ b5: [498.9, 708.66],
3011
+ b4: [708.66, 1000.63],
3012
+ "jis-b5": [515.91, 728.5],
3013
+ "jis-b4": [728.5, 1031.81],
3014
+ letter: [612, 792],
3015
+ legal: [612, 1008],
3016
+ ledger: [1224, 792]
3017
+ };
3018
+ function parseSize(value) {
3019
+ const parts = value.trim().split(/\s+/);
3020
+ let landscape = false;
3021
+ const lens = [];
3022
+ let named;
3023
+ for (const p of parts) {
3024
+ const key = p.toLowerCase();
3025
+ if (key === "landscape")
3026
+ landscape = true;
3027
+ else if (key === "portrait")
3028
+ continue;
3029
+ else if (PAGE_SIZES[key])
3030
+ named = PAGE_SIZES[key];
3031
+ else {
3032
+ const pt = toPt(p);
3033
+ if (pt !== undefined)
3034
+ lens.push(pt);
3035
+ }
3036
+ }
3037
+ let w, h;
3038
+ if (named)
3039
+ [w, h] = named;
3040
+ else if (lens.length === 1)
3041
+ [w, h] = [lens[0], lens[0]];
3042
+ else if (lens.length >= 2)
3043
+ [w, h] = [lens[0], lens[1]];
3044
+ else
3045
+ return;
3046
+ if (landscape && h > w)
3047
+ [w, h] = [h, w];
3048
+ return { width: w, height: h };
3049
+ }
3050
+ function parseMargin(value) {
3051
+ const parts = value.trim().split(/\s+/).map((p) => toPt(p) ?? 0);
3052
+ const a = parts[0] ?? 0;
3053
+ const b = parts[1] ?? a;
3054
+ const c = parts[2] ?? a;
3055
+ const d = parts[3] ?? b;
3056
+ return { top: a, right: b, bottom: c, left: d };
3057
+ }
3058
+ function resolvePage(model, ctx = {}) {
3059
+ const wanted = new Set(ctx.pseudos ?? []);
3060
+ const applicable = model.pageRules.filter((r) => r.name ? r.name === ctx.name : true).filter((r) => r.pseudos.every((p) => wanted.has(p))).sort((a, b) => specificity(a) - specificity(b));
3061
+ const decls = {};
3062
+ const marginBoxes = {};
3063
+ const margin = { top: 72, right: 72, bottom: 72, left: 72 };
3064
+ for (const rule of applicable) {
3065
+ for (const [prop, value] of Object.entries(rule.decls)) {
3066
+ if (prop === "margin")
3067
+ Object.assign(margin, parseMargin(value));
3068
+ else if (prop === "margin-top" || prop === "margin-right" || prop === "margin-bottom" || prop === "margin-left") {
3069
+ const side = prop.slice(7);
3070
+ margin[side] = toPt(value) ?? margin[side];
3071
+ }
3072
+ }
3073
+ Object.assign(decls, rule.decls);
3074
+ for (const [box, d] of Object.entries(rule.marginBoxes)) {
3075
+ marginBoxes[box] = { ...marginBoxes[box] ?? {}, ...d };
3076
+ }
3077
+ }
3078
+ const size = parseSize(decls.size ?? "letter") ?? { width: 612, height: 792 };
3079
+ return {
3080
+ decls,
3081
+ marginBoxes,
3082
+ geometry: {
3083
+ width: size.width,
3084
+ height: size.height,
3085
+ margin,
3086
+ bleed: decls.bleed ? toPt(decls.bleed) ?? 0 : 0,
3087
+ marks: (decls.marks ?? "none").split(/\s+/).filter((m) => m && m !== "none")
3088
+ }
3089
+ };
3090
+ }
3091
+ function specificity(r) {
3092
+ return (r.name ? 2 : 0) + r.pseudos.length;
3093
+ }
3094
+
2405
3095
  // src/lib/markdown/assemble.ts
3096
+ function defaultPageSizeCss(css, page) {
3097
+ if (!page || !isPositivePoints(page.width) || !isPositivePoints(page.height))
3098
+ return null;
3099
+ try {
3100
+ if (resolvePage(extract(css)).decls.size !== undefined)
3101
+ return null;
3102
+ } catch {
3103
+ return null;
3104
+ }
3105
+ return `@page { size: ${formatPageSize(page)}; }`;
3106
+ }
2406
3107
  async function assembleBookHtml(opts) {
2407
3108
  const title = opts.title ?? "Document";
2408
3109
  const projectCss = opts.projectCss ?? "";
@@ -2440,16 +3141,21 @@ async function assembleBookHtml(opts) {
2440
3141
  imageRefs.add(ref);
2441
3142
  opts.onImageRefs([...imageRefs]);
2442
3143
  }
3144
+ const pageSize = defaultPageSizeCss(`${pluginCss}
3145
+ ${projectCss}`, opts.printArea);
2443
3146
  const inlineCss = [
2444
3147
  "@layer gp.marker, gp.vocab;",
2445
3148
  `/* gutterpress markers */
2446
3149
  @layer gp.marker {
2447
3150
  ${MARKER_CSS.trim()}
3151
+ ${TWO_VERSIONS_CSS.trim()}
2448
3152
  }`,
2449
3153
  `/* gutterpress */
2450
3154
  @layer gp.vocab {
2451
3155
  ${GUTTERPRESS_CSS.trim()}
2452
3156
  }`,
3157
+ pageSize ? `/* page size (manifest) */
3158
+ ${pageSize}` : null,
2453
3159
  pluginCss ? `/* extension css */
2454
3160
  ${pluginCss.trim()}` : null,
2455
3161
  `/* column alignment */