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.
- package/dist/api/index.js +6 -6
- package/dist/{build-se85j9ec.js → build-shby4bg7.js} +9 -9
- package/dist/{cli-pfrg1r9x.js → cli-0s8sy5n9.js} +1 -1
- package/dist/{cli-fbwqv7p4.js → cli-17g57c61.js} +173 -61
- package/dist/{cli-sth219v1.js → cli-2ppw6q9v.js} +26 -6
- package/dist/{cli-5553w972.js → cli-3p7jejyd.js} +1 -1
- package/dist/{cli-8hccw62p.js → cli-9pk65fqf.js} +2 -2
- package/dist/{cli-yejbb9f8.js → cli-d0x3aq49.js} +1 -1
- package/dist/{cli-gmm3bdeq.js → cli-hndkg7bn.js} +2 -2
- package/dist/{cli-gc9av5hs.js → cli-nqvrp9wk.js} +1 -1
- package/dist/{cli-9vy4b6ec.js → cli-sge5cp21.js} +4 -4
- package/dist/cli.js +12 -12
- package/dist/{doctor-bv2t82zd.js → doctor-c6k6ycek.js} +3 -3
- package/dist/engine/shared/print-area.d.ts +77 -1
- package/dist/{engine-h4ehmrvf.js → engine-5p8tn9b4.js} +2 -2
- package/dist/{engine-qwkyhrej.js → engine-9710cd7x.js} +3 -3
- package/dist/{ext-a8ndp142.js → ext-t8re6kc7.js} +9 -9
- package/dist/{extension-restore-926mv2qk.js → extension-restore-3gsj0qxp.js} +3 -3
- package/dist/{extension-restore-q1f7njec.js → extension-restore-4rqvzzw4.js} +5 -5
- package/dist/{gutterpress-viewer-w7njnkj7.js → gutterpress-viewer-w9jzx6sb.js} +56 -52
- package/dist/{index-gmfh6n05.js → index-2d8j0enc.js} +2 -2
- package/dist/{index-sth219v1.js → index-2ppw6q9v.js} +26 -6
- package/dist/{index-5h9a38jn.js → index-apc8w9w8.js} +55 -55
- package/dist/{index-dhknsvfz.js → index-jsz0s3dy.js} +2 -2
- package/dist/{index-tkjvs1gf.js → index-p1a596m0.js} +162 -58
- package/dist/{index-38e18a7j.js → index-thmwa4f5.js} +1 -1
- package/dist/{index-dm7hx46j.js → index-xe83m9mc.js} +3 -3
- package/dist/index.js +7 -7
- package/dist/lib/markdown/assemble.d.ts +3 -2
- package/dist/lib/page-size.d.ts +3 -4
- package/dist/lib/pdf-inspect.d.ts +12 -0
- package/dist/lib/project-scaffold.d.ts +3 -3
- package/dist/{manifest.schema-n292sb77.json → manifest.schema-nvawq0xv.json} +2 -2
- package/dist/{new-nadp2hvb.js → new-w3wdd615.js} +9 -9
- package/dist/{pdf-inspect-zdmy7qca.js → pdf-inspect-wnw4za71.js} +1 -1
- package/dist/{plugins-yeh4shd7.js → plugins-8x9rsf21.js} +1 -1
- package/dist/{plugins-gy4q9vaa.js → plugins-cx4qwn7g.js} +3 -3
- package/dist/{preflight-c6q25xqa.js → preflight-aj2tpnxe.js} +9 -9
- package/dist/{preview-y8chv4cb.js → preview-dn5655f1.js} +9 -9
- package/dist/{publish-3qwvxsxa.js → publish-e0p9wgpd.js} +9 -9
- package/dist/render.js +586 -0
- package/dist/schema/manifest.types.d.ts +8 -3
- package/dist/{validate-w108kk87.js → validate-6wvf4mhr.js} +9 -9
- 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, "&").replace(/'/g, "'")}'>`;
|
|
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
|
-
*
|
|
195
|
-
*
|
|
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`;
|
|
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-
|
|
4
|
-
import"./cli-
|
|
3
|
+
} from "./cli-17g57c61.js";
|
|
4
|
+
import"./cli-d0x3aq49.js";
|
|
5
5
|
import"./cli-ht1bcqdd.js";
|
|
6
|
-
import"./cli-
|
|
7
|
-
import"./cli-
|
|
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-
|
|
13
|
-
import"./cli-
|
|
12
|
+
import"./cli-sge5cp21.js";
|
|
13
|
+
import"./cli-0s8sy5n9.js";
|
|
14
14
|
import {
|
|
15
15
|
log
|
|
16
|
-
} from "./cli-
|
|
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-
|
|
23
|
-
import"./cli-
|
|
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.
|
|
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",
|