gutterpress 0.11.23-alpha.2 → 0.11.23-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 (55) hide show
  1. package/README.md +6 -7
  2. package/dist/{README-8ef336pp.md → README-8db7btxb.md} +13 -19
  3. package/dist/api/index.d.ts +1 -1
  4. package/dist/api/index.js +4 -4
  5. package/dist/{build-encgnv0c.js → build-m78jcpf7.js} +7 -7
  6. package/dist/{cli-xq6djqbj.js → cli-1h17ktsg.js} +84 -113
  7. package/dist/{cli-kh8v4vvw.js → cli-5jzyw06s.js} +1 -1
  8. package/dist/{cli-sdmv4bba.js → cli-h4x81x97.js} +112 -8
  9. package/dist/{cli-evg8sbht.js → cli-n2tsxqpn.js} +1 -1
  10. package/dist/{cli-aqh6brew.js → cli-s6djx4ma.js} +1 -1
  11. package/dist/{cli-pwwfxdgb.js → cli-t8be9yeb.js} +9 -20
  12. package/dist/{cli-pz4bn98t.js → cli-xsrffqzt.js} +19 -13
  13. package/dist/cli.js +12 -12
  14. package/dist/{doctor-5gtpgpd1.js → doctor-j71jqce3.js} +3 -3
  15. package/dist/engine/shared/gcpm-extract.d.ts +8 -0
  16. package/dist/engine/shared/print-area.d.ts +16 -4
  17. package/dist/{engine-tn13gk0b.js → engine-1r6q80tq.js} +2 -2
  18. package/dist/{engine-mc5br8g8.js → engine-m4y10shc.js} +1 -1
  19. package/dist/{ext-wn2a5afq.js → ext-fq2hg9va.js} +7 -7
  20. package/dist/{extension-restore-s3x1618g.js → extension-restore-9dh7c4tp.js} +2 -2
  21. package/dist/{extension-restore-g8jnmmzq.js → extension-restore-tmxtzkm0.js} +4 -4
  22. package/dist/{gutterpress-viewer-701vf4qp.js → gutterpress-viewer-mafsby5k.js} +9 -2
  23. package/dist/{index-2bjcghze.js → index-0wee4408.js} +9 -20
  24. package/dist/{index-x3ema2w3.js → index-7pck1q9c.js} +124 -17
  25. package/dist/{index-0skhjx3e.js → index-nedd07bk.js} +19 -13
  26. package/dist/{index-4x6y8krm.js → index-nvmf22rt.js} +1 -1
  27. package/dist/{index-j5z86r4a.js → index-prqvz3xm.js} +213 -276
  28. package/dist/index.js +5 -5
  29. package/dist/lib/extension-manager.d.ts +8 -7
  30. package/dist/lib/extension-manifest.d.ts +1 -13
  31. package/dist/lib/manifest.d.ts +15 -1
  32. package/dist/lib/markdown/assemble.d.ts +4 -3
  33. package/dist/lib/page-size.d.ts +34 -27
  34. package/dist/lib/printsafe.d.ts +1 -0
  35. package/dist/lib/project-templates.d.ts +9 -0
  36. package/dist/manifest-8afhrk30.yaml +19 -0
  37. package/dist/manifest-d4f9q79e.yaml +23 -0
  38. package/dist/{manifest-eagfhba6.yaml → manifest-gf13sjp8.yaml} +10 -2
  39. package/dist/manifest-my7mdv8j.yaml +23 -0
  40. package/dist/{manifest.schema-t7nx2stg.json → manifest.schema-650pe0d1.json} +15 -3
  41. package/dist/{new-mhs46pw3.js → new-s4zcaepg.js} +7 -7
  42. package/dist/{package-aenwjew3.json → package-awwkt7sa.json} +0 -1
  43. package/dist/{page-rules-nsj3aw42.css → page-rules-fj09xct5.css} +12 -40
  44. package/dist/{plugin.test.js-xgxrd8hf.tpl → plugin.test.js-gxzqqfdh.tpl} +0 -1
  45. package/dist/{plugins-1sxe0dkz.js → plugins-4vaf27n9.js} +3 -3
  46. package/dist/{plugins-2cek4mz1.js → plugins-80emnf31.js} +1 -1
  47. package/dist/{preflight-3k2b1x8e.js → preflight-c7w1xdn3.js} +7 -7
  48. package/dist/{preview-6p9v6rht.js → preview-mzytcwyk.js} +7 -7
  49. package/dist/{publish-qgsngy8s.js → publish-fez14ad4.js} +7 -7
  50. package/dist/render.js +24 -561
  51. package/dist/schema/manifest.types.d.ts +22 -0
  52. package/dist/{tokens-7984461t.css → tokens-ye64p899.css} +5 -12
  53. package/dist/{validate-0v7xw6wa.js → validate-p2chr6hr.js} +7 -7
  54. package/package.json +1 -1
  55. package/dist/manifest-n1gh3qw5.yaml +0 -9
@@ -10,12 +10,12 @@ import {
10
10
  resolvePublishFormat,
11
11
  resolvePublishProvider,
12
12
  runPublish
13
- } from "./cli-xq6djqbj.js";
13
+ } from "./cli-1h17ktsg.js";
14
14
  import {
15
15
  FileTokenStore
16
- } from "./cli-evg8sbht.js";
16
+ } from "./cli-n2tsxqpn.js";
17
17
  import"./cli-ht1bcqdd.js";
18
- import"./cli-pz4bn98t.js";
18
+ import"./cli-xsrffqzt.js";
19
19
  import"./cli-2ppw6q9v.js";
20
20
  import"./cli-eyyggect.js";
21
21
  import"./cli-asap4t1z.js";
@@ -23,17 +23,17 @@ import"./cli-a35w7wzg.js";
23
23
  import"./cli-v5mp7a6q.js";
24
24
  import {
25
25
  loadManifestWithPath
26
- } from "./cli-sdmv4bba.js";
27
- import"./cli-pwwfxdgb.js";
26
+ } from "./cli-h4x81x97.js";
27
+ import"./cli-t8be9yeb.js";
28
28
  import {
29
29
  log
30
- } from "./cli-aqh6brew.js";
30
+ } from "./cli-s6djx4ma.js";
31
31
  import {
32
32
  UsageError,
33
33
  rejectExtraPositionals,
34
34
  rejectUnknownFlags
35
35
  } from "./cli-d8xjns66.js";
36
- import"./cli-kh8v4vvw.js";
36
+ import"./cli-5jzyw06s.js";
37
37
  import {
38
38
  BuildError,
39
39
  EXIT_CODES
package/dist/render.js CHANGED
@@ -2991,458 +2991,6 @@ function printAreaMeta(page) {
2991
2991
  }
2992
2992
 
2993
2993
  // src/engine/shared/gcpm-extract.ts
2994
- var NESTED_AT_RULES = /^@(media|supports|layer|scope|container|document)\b/i;
2995
- function scanRules(css) {
2996
- const out = [];
2997
- let i = 0;
2998
- let start = 0;
2999
- let depth = 0;
3000
- let bodyStart = -1;
3001
- while (i < css.length) {
3002
- const c = css[i];
3003
- if (c === "/" && css[i + 1] === "*") {
3004
- const end = css.indexOf("*/", i + 2);
3005
- i = end === -1 ? css.length : end + 2;
3006
- continue;
3007
- }
3008
- if (c === '"' || c === "'") {
3009
- i = skipString(css, i);
3010
- continue;
3011
- }
3012
- if (c === "{") {
3013
- if (depth === 0)
3014
- bodyStart = i;
3015
- depth++;
3016
- i++;
3017
- continue;
3018
- }
3019
- if (c === "}") {
3020
- depth--;
3021
- if (depth === 0) {
3022
- out.push({
3023
- prelude: stripComments(css.slice(start, bodyStart)).trim(),
3024
- body: css.slice(bodyStart + 1, i)
3025
- });
3026
- start = i + 1;
3027
- }
3028
- i++;
3029
- continue;
3030
- }
3031
- if (c === ";" && depth === 0) {
3032
- const stmt = stripComments(css.slice(start, i)).trim();
3033
- if (stmt)
3034
- out.push({ statement: stmt });
3035
- start = i + 1;
3036
- i++;
3037
- continue;
3038
- }
3039
- i++;
3040
- }
3041
- return out;
3042
- }
3043
- function stripComments(s) {
3044
- let out = "";
3045
- let i = 0;
3046
- while (i < s.length) {
3047
- if (s[i] === "/" && s[i + 1] === "*") {
3048
- const end = s.indexOf("*/", i + 2);
3049
- i = end === -1 ? s.length : end + 2;
3050
- continue;
3051
- }
3052
- if (s[i] === '"' || s[i] === "'") {
3053
- const end = skipString(s, i);
3054
- out += s.slice(i, end);
3055
- i = end;
3056
- continue;
3057
- }
3058
- out += s[i++];
3059
- }
3060
- return out;
3061
- }
3062
- function skipString(css, i) {
3063
- const quote = css[i];
3064
- i++;
3065
- while (i < css.length) {
3066
- if (css[i] === "\\")
3067
- i += 2;
3068
- else if (css[i] === quote)
3069
- return i + 1;
3070
- else
3071
- i++;
3072
- }
3073
- return i;
3074
- }
3075
- function parseDeclarations(body) {
3076
- const decls = {};
3077
- let i = 0;
3078
- let start = 0;
3079
- let depth = 0;
3080
- const push = (chunk) => {
3081
- const s = stripComments(chunk).trim();
3082
- if (!s)
3083
- return;
3084
- const colon = indexOfTopLevel(s, ":");
3085
- if (colon <= 0)
3086
- return;
3087
- const rawProp = s.slice(0, colon).trim();
3088
- const prop = rawProp.startsWith("--") ? rawProp : rawProp.toLowerCase();
3089
- const value = s.slice(colon + 1).trim().replace(/\s*!important$/i, "");
3090
- if (prop)
3091
- decls[prop] = value;
3092
- };
3093
- while (i < body.length) {
3094
- const c = body[i];
3095
- if (c === "/" && body[i + 1] === "*") {
3096
- const end = body.indexOf("*/", i + 2);
3097
- i = end === -1 ? body.length : end + 2;
3098
- continue;
3099
- }
3100
- if (c === '"' || c === "'") {
3101
- i = skipString(body, i);
3102
- continue;
3103
- }
3104
- if (c === "(" || c === "{" || c === "[")
3105
- depth++;
3106
- else if (c === ")" || c === "}" || c === "]")
3107
- depth--;
3108
- else if (c === ";" && depth === 0) {
3109
- push(body.slice(start, i));
3110
- start = i + 1;
3111
- }
3112
- i++;
3113
- }
3114
- push(body.slice(start));
3115
- return decls;
3116
- }
3117
- function indexOfTopLevel(s, ch) {
3118
- let depth = 0;
3119
- for (let i = 0;i < s.length; i++) {
3120
- const c = s[i];
3121
- if (c === '"' || c === "'") {
3122
- i = skipString(s, i) - 1;
3123
- continue;
3124
- }
3125
- if (c === "(")
3126
- depth++;
3127
- else if (c === ")")
3128
- depth--;
3129
- else if (c === ch && depth === 0)
3130
- return i;
3131
- }
3132
- return -1;
3133
- }
3134
- function extract(css) {
3135
- const model = {
3136
- pageRules: [],
3137
- stringSets: [],
3138
- pageAssignments: [],
3139
- breaks: [],
3140
- xrefs: [],
3141
- counterResets: [],
3142
- pageNames: [],
3143
- warnings: []
3144
- };
3145
- walk(css, model);
3146
- resolveGeometryVars(model, collectRootCustomProperties(css));
3147
- const names = new Set;
3148
- for (const r of model.pageRules)
3149
- if (r.name)
3150
- names.add(r.name);
3151
- for (const a of model.pageAssignments)
3152
- names.add(a.page);
3153
- model.pageNames = [...names];
3154
- return model;
3155
- }
3156
- var GEOMETRY_PROPS = [
3157
- "size",
3158
- "margin",
3159
- "margin-top",
3160
- "margin-right",
3161
- "margin-bottom",
3162
- "margin-left",
3163
- "bleed",
3164
- "marks"
3165
- ];
3166
- function collectRootCustomProperties(css) {
3167
- const props = new Map;
3168
- const walkForRoot = (body) => {
3169
- for (const rule of scanRules(body)) {
3170
- if ("statement" in rule)
3171
- continue;
3172
- const { prelude, body: ruleBody } = rule;
3173
- if (NESTED_AT_RULES.test(prelude)) {
3174
- walkForRoot(ruleBody);
3175
- continue;
3176
- }
3177
- if (!prelude.startsWith("@")) {
3178
- const selectors = splitTopLevel(prelude, ",");
3179
- if (selectors.some((s) => s.trim() === ":root")) {
3180
- const decls = parseDeclarations(ruleBody);
3181
- for (const [k, v] of Object.entries(decls)) {
3182
- if (k.startsWith("--"))
3183
- props.set(k, v);
3184
- }
3185
- }
3186
- }
3187
- }
3188
- };
3189
- walkForRoot(css);
3190
- return props;
3191
- }
3192
- function matchParen(s, open) {
3193
- let depth = 0;
3194
- let i = open;
3195
- while (i < s.length) {
3196
- const c = s[i];
3197
- if (c === '"' || c === "'") {
3198
- i = skipString(s, i);
3199
- continue;
3200
- }
3201
- if (c === "(")
3202
- depth++;
3203
- else if (c === ")") {
3204
- depth--;
3205
- if (depth === 0)
3206
- return i;
3207
- }
3208
- i++;
3209
- }
3210
- return -1;
3211
- }
3212
- function resolveVarsInValue(value, customProps, stack = new Set) {
3213
- let out = "";
3214
- let i = 0;
3215
- while (i < value.length) {
3216
- const isVarStart = /^var\(/i.test(value.slice(i, i + 4)) && (i === 0 || !/[\w-]/.test(value[i - 1]));
3217
- if (!isVarStart) {
3218
- out += value[i];
3219
- i++;
3220
- continue;
3221
- }
3222
- const open = i + 3;
3223
- const close = matchParen(value, open);
3224
- if (close === -1) {
3225
- out += value.slice(i);
3226
- break;
3227
- }
3228
- const inner = value.slice(open + 1, close);
3229
- const commaIdx = indexOfTopLevel(inner, ",");
3230
- const name = (commaIdx === -1 ? inner : inner.slice(0, commaIdx)).trim();
3231
- const fallback = commaIdx === -1 ? undefined : inner.slice(commaIdx + 1).trim();
3232
- if (!/^--[\w-]+$/.test(name)) {
3233
- return { text: out, unresolved: `var(${inner}) is not a valid custom property reference` };
3234
- }
3235
- if (stack.has(name)) {
3236
- return { text: out, unresolved: `var(${name}) is circular` };
3237
- }
3238
- const rootValue = customProps.get(name);
3239
- if (rootValue !== undefined) {
3240
- const nested = resolveVarsInValue(rootValue, customProps, new Set(stack).add(name));
3241
- if (nested.unresolved)
3242
- return { text: out, unresolved: nested.unresolved };
3243
- out += nested.text;
3244
- } else if (fallback !== undefined) {
3245
- if (/\bvar\(/i.test(fallback)) {
3246
- return {
3247
- text: out,
3248
- unresolved: `var(${name}, ${fallback}) — a fallback containing another var() is not resolved`
3249
- };
3250
- }
3251
- out += fallback;
3252
- } else {
3253
- return {
3254
- text: out,
3255
- unresolved: `${name} is not defined at :root and var() has no fallback`
3256
- };
3257
- }
3258
- i = close + 1;
3259
- }
3260
- return { text: out };
3261
- }
3262
- function resolveGeometryVars(model, customProps) {
3263
- for (const rule of model.pageRules) {
3264
- for (const prop of GEOMETRY_PROPS) {
3265
- const value = rule.decls[prop];
3266
- if (!value || !/\bvar\(/i.test(value))
3267
- continue;
3268
- const { text, unresolved } = resolveVarsInValue(value, customProps);
3269
- if (unresolved) {
3270
- 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.`);
3271
- }
3272
- const unparsable = unparsableGeometry(prop, text);
3273
- if (unparsable) {
3274
- 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\`).`);
3275
- }
3276
- rule.decls[prop] = text;
3277
- }
3278
- }
3279
- }
3280
- function unparsableGeometry(prop, text) {
3281
- const t = text.trim();
3282
- if (prop === "size") {
3283
- if (!t)
3284
- return "which is empty";
3285
- return parseSize(t) ? undefined : "which is not a page size";
3286
- }
3287
- if (prop === "margin" || prop.startsWith("margin-")) {
3288
- if (!t)
3289
- return "which is empty";
3290
- const parts = t.split(/\s+/);
3291
- if (prop !== "margin" && parts.length !== 1)
3292
- return "which is not a single length";
3293
- if (parts.length > 4)
3294
- return "which is not a valid margin";
3295
- const bad = parts.filter((p) => toPt(p) === undefined);
3296
- if (bad.length)
3297
- return `which this engine cannot read as a length (\`${bad[0]}\`)`;
3298
- return;
3299
- }
3300
- if (prop === "bleed") {
3301
- if (!t)
3302
- return "which is empty";
3303
- if (/^(auto|none)$/i.test(t))
3304
- return;
3305
- return toPt(t) === undefined ? "which this engine cannot read as a length" : undefined;
3306
- }
3307
- return;
3308
- }
3309
- function walk(css, model) {
3310
- for (const rule of scanRules(css)) {
3311
- if ("statement" in rule)
3312
- continue;
3313
- const { prelude, body } = rule;
3314
- if (NESTED_AT_RULES.test(prelude)) {
3315
- walk(body, model);
3316
- continue;
3317
- }
3318
- if (/^@page\b/i.test(prelude)) {
3319
- model.pageRules.push(parsePageRule(prelude, body));
3320
- continue;
3321
- }
3322
- if (prelude.startsWith("@"))
3323
- continue;
3324
- parseQualifiedRule(prelude, body, model);
3325
- }
3326
- }
3327
- function parsePageRule(prelude, body) {
3328
- const sel = prelude.replace(/^@page\s*/i, "").trim();
3329
- const nameMatch = /^([A-Za-z_][\w-]*)/.exec(sel);
3330
- const pseudos = [...sel.matchAll(/:([A-Za-z-]+)(\([^)]*\))?/g)].map((m) => m[1] + (m[2] ?? ""));
3331
- const rule = {
3332
- name: nameMatch?.[1],
3333
- pseudos,
3334
- decls: {},
3335
- marginBoxes: {},
3336
- raw: `@page ${sel}`.trim()
3337
- };
3338
- let rest = "";
3339
- let i = 0;
3340
- while (i < body.length) {
3341
- const at = findNextAtRule(body, i);
3342
- if (at === -1) {
3343
- rest += body.slice(i);
3344
- break;
3345
- }
3346
- rest += body.slice(i, at);
3347
- const open = body.indexOf("{", at);
3348
- if (open === -1) {
3349
- rest += body.slice(at);
3350
- break;
3351
- }
3352
- const name = body.slice(at + 1, open).trim().toLowerCase();
3353
- let depth = 1;
3354
- let j = open + 1;
3355
- while (j < body.length && depth > 0) {
3356
- const c = body[j];
3357
- if (c === "/" && body[j + 1] === "*") {
3358
- const end = body.indexOf("*/", j + 2);
3359
- j = end === -1 ? body.length : end + 2;
3360
- continue;
3361
- }
3362
- if (c === '"' || c === "'") {
3363
- j = skipString(body, j);
3364
- continue;
3365
- }
3366
- if (c === "{")
3367
- depth++;
3368
- else if (c === "}")
3369
- depth--;
3370
- j++;
3371
- }
3372
- rule.marginBoxes[`@${name}`] = parseDeclarations(body.slice(open + 1, j - 1));
3373
- i = j;
3374
- }
3375
- rule.decls = parseDeclarations(rest);
3376
- return rule;
3377
- }
3378
- function findNextAtRule(body, start) {
3379
- let i = start;
3380
- while (i < body.length) {
3381
- const c = body[i];
3382
- if (c === "/" && body[i + 1] === "*") {
3383
- const end = body.indexOf("*/", i + 2);
3384
- i = end === -1 ? body.length : end + 2;
3385
- continue;
3386
- }
3387
- if (c === '"' || c === "'") {
3388
- i = skipString(body, i);
3389
- continue;
3390
- }
3391
- if (c === "@")
3392
- return i;
3393
- i++;
3394
- }
3395
- return -1;
3396
- }
3397
- function parseQualifiedRule(selector, body, model) {
3398
- const decls = parseDeclarations(body);
3399
- for (const [prop, value] of Object.entries(decls)) {
3400
- if (prop === "string-set") {
3401
- for (const entry of splitTopLevel(value, ",")) {
3402
- const m = /^\s*([A-Za-z_][\w-]*)\s+(.+)$/.exec(entry);
3403
- if (m && m[1] !== undefined && m[2] !== undefined)
3404
- model.stringSets.push({ selector, name: m[1], value: m[2].trim() });
3405
- }
3406
- } else if (prop === "page") {
3407
- if (value && value !== "auto")
3408
- model.pageAssignments.push({ selector, page: value.trim() });
3409
- } else if (prop === "break-before" || prop === "break-after" || prop === "break-inside") {
3410
- model.breaks.push({ selector, prop, value });
3411
- } else if (prop === "counter-reset") {
3412
- const m = /\bpage\s+(-?\d+)/.exec(value);
3413
- if (m)
3414
- model.counterResets.push({ selector, start: Number(m[1]) });
3415
- } else if (prop === "content") {
3416
- for (const fn of ["target-counter", "target-text", "leader", "string"]) {
3417
- if (new RegExp(`\\b${fn}\\s*\\(`).test(value)) {
3418
- model.xrefs.push({ selector, content: value, fn });
3419
- }
3420
- }
3421
- }
3422
- }
3423
- }
3424
- function splitTopLevel(s, sep) {
3425
- const out = [];
3426
- let depth = 0;
3427
- let start = 0;
3428
- for (let i = 0;i < s.length; i++) {
3429
- const c = s[i];
3430
- if (c === '"' || c === "'") {
3431
- i = skipString(s, i) - 1;
3432
- continue;
3433
- }
3434
- if (c === "(")
3435
- depth++;
3436
- else if (c === ")")
3437
- depth--;
3438
- else if (c === sep && depth === 0) {
3439
- out.push(s.slice(start, i));
3440
- start = i + 1;
3441
- }
3442
- }
3443
- out.push(s.slice(start));
3444
- return out.map((x) => x.trim()).filter(Boolean);
3445
- }
3446
2994
  var UNITS_PER_PT = {
3447
2995
  pt: 1,
3448
2996
  px: 0.75,
@@ -3452,115 +3000,28 @@ var UNITS_PER_PT = {
3452
3000
  mm: 72 / 25.4,
3453
3001
  q: 72 / 101.6
3454
3002
  };
3455
- function toPt(value) {
3456
- const m = /^(-?[\d.]+)([a-z%]*)$/i.exec(value.trim());
3457
- if (!m)
3458
- return;
3459
- const n = Number(m[1]);
3460
- const unit = (m[2] || "px").toLowerCase();
3461
- const factor = UNITS_PER_PT[unit];
3462
- return factor === undefined ? undefined : n * factor;
3463
- }
3464
- var PAGE_SIZES = {
3465
- a5: [419.53, 595.28],
3466
- a4: [595.28, 841.89],
3467
- a3: [841.89, 1190.55],
3468
- b5: [498.9, 708.66],
3469
- b4: [708.66, 1000.63],
3470
- "jis-b5": [515.91, 728.5],
3471
- "jis-b4": [728.5, 1031.81],
3472
- letter: [612, 792],
3473
- legal: [612, 1008],
3474
- ledger: [1224, 792]
3475
- };
3476
- function parseSize(value) {
3477
- const parts = value.trim().split(/\s+/);
3478
- let landscape = false;
3479
- const lens = [];
3480
- let named;
3481
- for (const p of parts) {
3482
- const key = p.toLowerCase();
3483
- if (key === "landscape")
3484
- landscape = true;
3485
- else if (key === "portrait")
3486
- continue;
3487
- else if (PAGE_SIZES[key])
3488
- named = PAGE_SIZES[key];
3489
- else {
3490
- const pt = toPt(p);
3491
- if (pt !== undefined)
3492
- lens.push(pt);
3493
- }
3494
- }
3495
- let w, h;
3496
- if (named)
3497
- [w, h] = named;
3498
- else if (lens.length === 1)
3499
- [w, h] = [lens[0], lens[0]];
3500
- else if (lens.length >= 2)
3501
- [w, h] = [lens[0], lens[1]];
3502
- else
3503
- return;
3504
- if (landscape && h > w)
3505
- [w, h] = [h, w];
3506
- return { width: w, height: h };
3507
- }
3508
- function parseMargin(value) {
3509
- const parts = value.trim().split(/\s+/).map((p) => toPt(p) ?? 0);
3510
- const a = parts[0] ?? 0;
3511
- const b = parts[1] ?? a;
3512
- const c = parts[2] ?? a;
3513
- const d = parts[3] ?? b;
3514
- return { top: a, right: b, bottom: c, left: d };
3515
- }
3516
- function resolvePage(model, ctx = {}) {
3517
- const wanted = new Set(ctx.pseudos ?? []);
3518
- 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));
3519
- const decls = {};
3520
- const marginBoxes = {};
3521
- const margin = { top: 72, right: 72, bottom: 72, left: 72 };
3522
- for (const rule of applicable) {
3523
- for (const [prop, value] of Object.entries(rule.decls)) {
3524
- if (prop === "margin")
3525
- Object.assign(margin, parseMargin(value));
3526
- else if (prop === "margin-top" || prop === "margin-right" || prop === "margin-bottom" || prop === "margin-left") {
3527
- const side = prop.slice(7);
3528
- margin[side] = toPt(value) ?? margin[side];
3529
- }
3530
- }
3531
- Object.assign(decls, rule.decls);
3532
- for (const [box, d] of Object.entries(rule.marginBoxes)) {
3533
- marginBoxes[box] = { ...marginBoxes[box] ?? {}, ...d };
3534
- }
3535
- }
3536
- const size = parseSize(decls.size ?? "letter") ?? { width: 612, height: 792 };
3537
- return {
3538
- decls,
3539
- marginBoxes,
3540
- geometry: {
3541
- width: size.width,
3542
- height: size.height,
3543
- margin,
3544
- bleed: decls.bleed ? toPt(decls.bleed) ?? 0 : 0,
3545
- marks: (decls.marks ?? "none").split(/\s+/).filter((m) => m && m !== "none")
3546
- }
3547
- };
3548
- }
3549
- function specificity(r) {
3550
- return (r.name ? 2 : 0) + r.pseudos.length;
3551
- }
3003
+ var DEFAULT_PAGE_MARGIN_PT = 72;
3552
3004
 
3553
3005
  // src/lib/markdown/assemble.ts
3554
- function defaultPageSizeCss(css, page) {
3006
+ function pageGeometryCss(page) {
3555
3007
  if (!page || !isPositivePoints(page.width) || !isPositivePoints(page.height))
3556
3008
  return null;
3557
- try {
3558
- if (resolvePage(extract(css)).decls.size !== undefined)
3559
- return null;
3560
- } catch {
3009
+ const size = `size: ${formatPageSize(page)};`;
3010
+ const m = page.margin;
3011
+ if (!m)
3012
+ return `@page { ${size} }`;
3013
+ const [top, bottom, inside, outside] = [m.top, m.bottom, m.inside, m.outside].map(cssLength);
3014
+ const recto = `margin: ${top} ${outside} ${bottom} ${inside};`;
3015
+ const verso = `margin: ${top} ${inside} ${bottom} ${outside};`;
3016
+ return [`@page { ${size} ${recto} }`, `@page :left { ${verso} }`, `@page :right { ${recto} }`].join(`
3017
+ `);
3018
+ }
3019
+ function defaultPageMarginCss(page) {
3020
+ if (!page || page.margin)
3561
3021
  return null;
3562
- }
3563
- return `@page { size: ${formatPageSize(page)}; }`;
3022
+ return `@layer gp.vocab {
3023
+ @page { margin: ${cssLength(DEFAULT_PAGE_MARGIN_PT)}; }
3024
+ }`;
3564
3025
  }
3565
3026
  async function assembleBookHtml(opts) {
3566
3027
  const title = opts.title ?? "Document";
@@ -3600,8 +3061,8 @@ async function assembleBookHtml(opts) {
3600
3061
  imageRefs.add(ref);
3601
3062
  opts.onImageRefs([...imageRefs]);
3602
3063
  }
3603
- const pageSize = defaultPageSizeCss(`${pluginCss}
3604
- ${projectCss}`, opts.printArea);
3064
+ const pageGeometry = pageGeometryCss(opts.printArea);
3065
+ const defaultMargin = defaultPageMarginCss(opts.printArea);
3605
3066
  const inlineCss = [
3606
3067
  "@layer gp.marker, gp.vocab;",
3607
3068
  `/* gutterpress markers */
@@ -3613,14 +3074,16 @@ ${TWO_VERSIONS_CSS.trim()}
3613
3074
  @layer gp.vocab {
3614
3075
  ${GUTTERPRESS_CSS.trim()}
3615
3076
  }`,
3616
- pageSize ? `/* page size (manifest) */
3617
- ${pageSize}` : null,
3077
+ defaultMargin ? `/* page margins (default) */
3078
+ ${defaultMargin}` : null,
3618
3079
  pluginCss ? `/* extension css */
3619
3080
  ${pluginCss.trim()}` : null,
3620
3081
  `/* column alignment */
3621
3082
  ${TABLE_ALIGN_CSS.trim()}`,
3622
3083
  projectCss ? `/* project css */
3623
- ${projectCss.trim()}` : null
3084
+ ${projectCss.trim()}` : null,
3085
+ pageGeometry ? `/* page geometry (manifest) */
3086
+ ${pageGeometry}` : null
3624
3087
  ].filter(Boolean).join(`
3625
3088
 
3626
3089
  `);
@@ -88,6 +88,17 @@ interface PublishSettings {
88
88
  format?: "pdf" | "html";
89
89
  };
90
90
  }
91
+ /**
92
+ * Page margins in points: the space between the trim and the text on each
93
+ * edge. `inside` is the binding edge (the left of a right-hand page, the
94
+ * right of a left-hand one), `outside` the other. All four together.
95
+ */
96
+ export interface PageMargin {
97
+ top: number;
98
+ bottom: number;
99
+ inside: number;
100
+ outside: number;
101
+ }
91
102
  export interface GutterpressManifest {
92
103
  title?: string;
93
104
  authors?: string[];
@@ -217,6 +228,15 @@ export interface GutterpressManifest {
217
228
  * and `custom` none.
218
229
  */
219
230
  gutter?: number;
231
+ /**
232
+ * Page margins (pt), the space between the trim and the text on each
233
+ * edge — `inside` is the binding edge, `outside` the other. Setup >
234
+ * Details in the app writes it. When set, the book prints with these
235
+ * margins and any `@page` margin in a stylesheet is overridden; left
236
+ * out, the stylesheets' `@page` margins apply (a look's design), else
237
+ * 1in. All four edges are required together.
238
+ */
239
+ margin?: PageMargin;
220
240
  };
221
241
  ink?: {
222
242
  maxTac?: number;
@@ -364,6 +384,8 @@ export interface ResolvedConfig {
364
384
  artSafe?: number;
365
385
  /** The binding edge's inset from the trim in pt, text and art alike (see the manifest field); absent = same as `safe`. */
366
386
  gutter?: number;
387
+ /** The margins the book prints with, in pt (see the manifest field); absent = the stylesheets' `@page` margins, else 1in. */
388
+ margin?: PageMargin;
367
389
  };
368
390
  ink: {
369
391
  maxTac: number;