gutterpress 0.11.22-alpha.1 → 0.11.22-alpha.3

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 (44) hide show
  1. package/dist/api/index.js +6 -6
  2. package/dist/{build-se85j9ec.js → build-shby4bg7.js} +9 -9
  3. package/dist/{cli-pfrg1r9x.js → cli-0s8sy5n9.js} +1 -1
  4. package/dist/{cli-fbwqv7p4.js → cli-17g57c61.js} +173 -61
  5. package/dist/{cli-sth219v1.js → cli-2ppw6q9v.js} +26 -6
  6. package/dist/{cli-5553w972.js → cli-3p7jejyd.js} +1 -1
  7. package/dist/{cli-8hccw62p.js → cli-9pk65fqf.js} +2 -2
  8. package/dist/{cli-yejbb9f8.js → cli-d0x3aq49.js} +1 -1
  9. package/dist/{cli-gmm3bdeq.js → cli-hndkg7bn.js} +2 -2
  10. package/dist/{cli-gc9av5hs.js → cli-nqvrp9wk.js} +1 -1
  11. package/dist/{cli-9vy4b6ec.js → cli-sge5cp21.js} +4 -4
  12. package/dist/cli.js +12 -12
  13. package/dist/{doctor-bv2t82zd.js → doctor-c6k6ycek.js} +3 -3
  14. package/dist/engine/shared/print-area.d.ts +77 -1
  15. package/dist/{engine-h4ehmrvf.js → engine-5p8tn9b4.js} +2 -2
  16. package/dist/{engine-qwkyhrej.js → engine-9710cd7x.js} +3 -3
  17. package/dist/{ext-a8ndp142.js → ext-t8re6kc7.js} +9 -9
  18. package/dist/{extension-restore-926mv2qk.js → extension-restore-3gsj0qxp.js} +3 -3
  19. package/dist/{extension-restore-q1f7njec.js → extension-restore-4rqvzzw4.js} +5 -5
  20. package/dist/{gutterpress-viewer-w7njnkj7.js → gutterpress-viewer-w9jzx6sb.js} +56 -52
  21. package/dist/{index-gmfh6n05.js → index-2d8j0enc.js} +2 -2
  22. package/dist/{index-sth219v1.js → index-2ppw6q9v.js} +26 -6
  23. package/dist/{index-5h9a38jn.js → index-apc8w9w8.js} +55 -55
  24. package/dist/{index-dhknsvfz.js → index-jsz0s3dy.js} +2 -2
  25. package/dist/{index-tkjvs1gf.js → index-p1a596m0.js} +162 -58
  26. package/dist/{index-38e18a7j.js → index-thmwa4f5.js} +1 -1
  27. package/dist/{index-dm7hx46j.js → index-xe83m9mc.js} +3 -3
  28. package/dist/index.js +7 -7
  29. package/dist/lib/markdown/assemble.d.ts +3 -2
  30. package/dist/lib/page-size.d.ts +3 -4
  31. package/dist/lib/pdf-inspect.d.ts +12 -0
  32. package/dist/lib/project-scaffold.d.ts +3 -3
  33. package/dist/{manifest.schema-n292sb77.json → manifest.schema-nvawq0xv.json} +2 -2
  34. package/dist/{new-nadp2hvb.js → new-w3wdd615.js} +9 -9
  35. package/dist/{pdf-inspect-zdmy7qca.js → pdf-inspect-wnw4za71.js} +1 -1
  36. package/dist/{plugins-yeh4shd7.js → plugins-8x9rsf21.js} +1 -1
  37. package/dist/{plugins-gy4q9vaa.js → plugins-cx4qwn7g.js} +3 -3
  38. package/dist/{preflight-c6q25xqa.js → preflight-aj2tpnxe.js} +9 -9
  39. package/dist/{preview-y8chv4cb.js → preview-dn5655f1.js} +9 -9
  40. package/dist/{publish-3qwvxsxa.js → publish-e0p9wgpd.js} +9 -9
  41. package/dist/render.js +586 -0
  42. package/dist/schema/manifest.types.d.ts +8 -3
  43. package/dist/{validate-w108kk87.js → validate-6wvf4mhr.js} +9 -9
  44. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -2507,6 +2507,17 @@ function lastPageMode(print) {
2507
2507
  var WHICH_VALUES = new Set(["first", "start", "last", "first-except"]);
2508
2508
 
2509
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
+ }
2510
2521
  function printAreaMeta(page) {
2511
2522
  const area = JSON.stringify({
2512
2523
  width: page.width,
@@ -2521,7 +2532,578 @@ function printAreaMeta(page) {
2521
2532
  return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
2522
2533
  }
2523
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
+
2524
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
+ }
2525
3107
  async function assembleBookHtml(opts) {
2526
3108
  const title = opts.title ?? "Document";
2527
3109
  const projectCss = opts.projectCss ?? "";
@@ -2559,6 +3141,8 @@ async function assembleBookHtml(opts) {
2559
3141
  imageRefs.add(ref);
2560
3142
  opts.onImageRefs([...imageRefs]);
2561
3143
  }
3144
+ const pageSize = defaultPageSizeCss(`${pluginCss}
3145
+ ${projectCss}`, opts.printArea);
2562
3146
  const inlineCss = [
2563
3147
  "@layer gp.marker, gp.vocab;",
2564
3148
  `/* gutterpress markers */
@@ -2570,6 +3154,8 @@ ${TWO_VERSIONS_CSS.trim()}
2570
3154
  @layer gp.vocab {
2571
3155
  ${GUTTERPRESS_CSS.trim()}
2572
3156
  }`,
3157
+ pageSize ? `/* page size (manifest) */
3158
+ ${pageSize}` : null,
2573
3159
  pluginCss ? `/* extension css */
2574
3160
  ${pluginCss.trim()}` : null,
2575
3161
  `/* column alignment */
@@ -191,8 +191,11 @@ export interface GutterpressManifest {
191
191
  * borders, rules — stays at least this far inside the trim on the top,
192
192
  * bottom and OUTER edge; at the binding edge it uses `gutter`. DriveThruRPG's
193
193
  * "keep non-bleeding art 0.25in from the outside edges" (`dtrpg` sets 18).
194
- * An image pinned to the top, bottom or outer edge (`.gp-pin`) sits on this
195
- * line unless it carries `.gp-flush`. Absent = the same as `safe`.
194
+ * The preview's overlay outlines it and flags art outside it, and
195
+ * `pdf.print.safe-margins` fails a built PDF with an image closer, or one
196
+ * that stops in the bleed band. An image pinned to the top, bottom or outer
197
+ * edge (`.gp-pin`) sits on this line unless it carries `.gp-flush`. Absent =
198
+ * the same as `safe`.
196
199
  */
197
200
  artSafe?: number;
198
201
  /**
@@ -203,7 +206,9 @@ export interface GutterpressManifest {
203
206
  * from the spine than the printer's minimum says so here, not in its CSS
204
207
  * (the Field Guide keeps 0.75in = 54 where DriveThruRPG asks for 0.5in).
205
208
  * An image pinned to the binding edge sits on it. The other edges keep
206
- * `safe` and `artSafe`. Absent = the same as `safe`; presets leave it unset.
209
+ * `safe` and `artSafe`. Absent = the same as `safe`; `dtrpg` sets 36
210
+ * (DriveThruRPG's 0.5in at the binding, the same as its `safe`), `book`
211
+ * and `custom` none.
207
212
  */
208
213
  gutter?: number;
209
214
  };
@@ -1,26 +1,26 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-fbwqv7p4.js";
4
- import"./cli-yejbb9f8.js";
3
+ } from "./cli-17g57c61.js";
4
+ import"./cli-d0x3aq49.js";
5
5
  import"./cli-ht1bcqdd.js";
6
- import"./cli-8hccw62p.js";
7
- import"./cli-sth219v1.js";
6
+ import"./cli-9pk65fqf.js";
7
+ import"./cli-2ppw6q9v.js";
8
8
  import"./cli-eyyggect.js";
9
9
  import"./cli-asap4t1z.js";
10
10
  import"./cli-habkc5ng.js";
11
11
  import"./cli-v5mp7a6q.js";
12
- import"./cli-9vy4b6ec.js";
13
- import"./cli-pfrg1r9x.js";
12
+ import"./cli-sge5cp21.js";
13
+ import"./cli-0s8sy5n9.js";
14
14
  import {
15
15
  log
16
- } from "./cli-5553w972.js";
16
+ } from "./cli-3p7jejyd.js";
17
17
  import {
18
18
  UsageError,
19
19
  rejectExtraPositionals,
20
20
  rejectUnknownFlags
21
21
  } from "./cli-d8xjns66.js";
22
- import"./cli-gc9av5hs.js";
23
- import"./cli-gmm3bdeq.js";
22
+ import"./cli-nqvrp9wk.js";
23
+ import"./cli-hndkg7bn.js";
24
24
  import {
25
25
  BuildError,
26
26
  EXIT_CODES
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gutterpress",
3
- "version": "0.11.22-alpha.1",
3
+ "version": "0.11.22-alpha.3",
4
4
  "description": "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5
5
  "author": "itlackey",
6
6
  "license": "MPL-2.0",