gutterpress 0.11.20-alpha.2 → 0.11.20-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/api/index.js +3 -2
  2. package/dist/{build-qmjg9r2d.js → build-epdcrm2c.js} +5 -4
  3. package/dist/{cli-zacevwcy.js → cli-0ey902sx.js} +267 -455
  4. package/dist/{cli-j802s31j.js → cli-61vd4ews.js} +83 -41
  5. package/dist/{cli-7z29z4wz.js → cli-fmksazej.js} +1 -1
  6. package/dist/{cli-gh0ym80r.js → cli-h4tfmpg3.js} +121 -20
  7. package/dist/cli-sth219v1.js +418 -0
  8. package/dist/cli.js +12 -12
  9. package/dist/{doctor-fe7tcs4v.js → doctor-78es5scm.js} +2 -2
  10. package/dist/engine/compiler/build.d.ts +1 -1
  11. package/dist/engine/shared/margin-box-support.d.ts +2 -2
  12. package/dist/engine/shared/pin-edges.d.ts +127 -0
  13. package/dist/engine/shared/print-area.d.ts +68 -0
  14. package/dist/{engine-9xb1ky8d.js → engine-67f68bxs.js} +1 -1
  15. package/dist/{engine-yax79k9m.js → engine-7ha27n2b.js} +2 -2
  16. package/dist/{ext-ahwxt6gv.js → ext-9mc7sr0d.js} +5 -4
  17. package/dist/{gutterpress-agent-cazqstr1.js → gutterpress-agent-mrdnwktc.js} +78 -13
  18. package/dist/{gutterpress-viewer-retj6ym2.js → gutterpress-viewer-jr30yw8t.js} +155 -56
  19. package/dist/{index-6xwtb7cw.js → index-ad95fws8.js} +82 -40
  20. package/dist/{index-3nr5rzzp.js → index-gm5xbx5w.js} +3 -3
  21. package/dist/index-sth219v1.js +418 -0
  22. package/dist/{index-razpty47.js → index-t13p8y08.js} +493 -582
  23. package/dist/index.js +4 -3
  24. package/dist/lib/build-fingerprint.d.ts +8 -0
  25. package/dist/lib/extension-manifest.d.ts +13 -0
  26. package/dist/lib/ghostscript.d.ts +70 -16
  27. package/dist/lib/markdown/assemble.d.ts +12 -2
  28. package/dist/lib/markdown/gutterpress-css.d.ts +46 -30
  29. package/dist/lib/markdown/index.d.ts +5 -0
  30. package/dist/lib/pdf-inspect.d.ts +45 -0
  31. package/dist/{manifest.schema-74s1ykyv.json → manifest.schema-13dfax80.json} +16 -4
  32. package/dist/{new-ftd4fec4.js → new-s4f0tnya.js} +5 -4
  33. package/dist/pdf-inspect-zdmy7qca.js +35 -0
  34. package/dist/{preflight-8sqarcsr.js → preflight-7ck4rbsq.js} +5 -4
  35. package/dist/preview/file-watcher.d.ts +4 -10
  36. package/dist/{preview-q76xn95h.js → preview-j91sxadd.js} +5 -4
  37. package/dist/{publish-7gdy6gbf.js → publish-f12xaenm.js} +5 -4
  38. package/dist/render.js +57 -6
  39. package/dist/schema/manifest.types.d.ts +28 -8
  40. package/dist/test-helpers/testkit.d.ts +2 -2
  41. package/dist/{validate-e3ryegce.js → validate-pb5gysvr.js} +5 -4
  42. package/package.json +1 -1
  43. package/dist/engine/shared/flush.d.ts +0 -59
@@ -20,17 +20,11 @@ import type { PrintPadding } from '../engine/shared/synthesis.ts';
20
20
  *
21
21
  * The book always paginates as ONE document, exactly as the print path does:
22
22
  * no per-source wrappers or forced breaks are added, so preview pagination
23
- * never diverges from the PDF where a source file begins mid-page.
23
+ * never diverges from the PDF where a source file begins mid-page. The
24
+ * project's print area is not added here: the markdown assembly writes it
25
+ * into every book, the preview's included (`AssembleBookHtmlOptions.printArea`).
24
26
  */
25
- export declare function injectPreviewScripts(html: string, page?: {
26
- width: number;
27
- height: number;
28
- bleed?: number;
29
- safe?: number;
30
- artSafe?: number;
31
- preset?: string;
32
- spec?: PrintSpec;
33
- }): string;
27
+ export declare function injectPreviewScripts(html: string): string;
34
28
  /**
35
29
  * Generate HTML from markdown and write book.html to the temp directory.
36
30
  * The desktop's iframe loads `book.html` via a relative URL — same name in
@@ -6,10 +6,10 @@ import {
6
6
  runBuild,
7
7
  splitOutPath,
8
8
  startPreviewServer
9
- } from "./cli-zacevwcy.js";
9
+ } from "./cli-0ey902sx.js";
10
10
  import {
11
11
  log
12
- } from "./cli-gh0ym80r.js";
12
+ } from "./cli-h4tfmpg3.js";
13
13
  import {
14
14
  UsageError,
15
15
  parseFormat,
@@ -17,12 +17,13 @@ import {
17
17
  rejectExtraPositionals,
18
18
  rejectUnknownFlags,
19
19
  resolvePort
20
- } from "./cli-7z29z4wz.js";
20
+ } from "./cli-fmksazej.js";
21
21
  import"./cli-ht1bcqdd.js";
22
- import"./cli-j802s31j.js";
22
+ import"./cli-61vd4ews.js";
23
23
  import {
24
24
  BuildError
25
25
  } from "./cli-46ycxe6r.js";
26
+ import"./cli-sth219v1.js";
26
27
  import"./cli-eyyggect.js";
27
28
  import"./cli-asap4t1z.js";
28
29
  import"./cli-5qr3z0qa.js";
@@ -11,21 +11,22 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-zacevwcy.js";
14
+ } from "./cli-0ey902sx.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-gh0ym80r.js";
18
+ } from "./cli-h4tfmpg3.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-7z29z4wz.js";
23
+ } from "./cli-fmksazej.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-j802s31j.js";
25
+ import"./cli-61vd4ews.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";
29
+ import"./cli-sth219v1.js";
29
30
  import"./cli-eyyggect.js";
30
31
  import"./cli-asap4t1z.js";
31
32
  import"./cli-5qr3z0qa.js";
package/dist/render.js CHANGED
@@ -1231,7 +1231,9 @@ var GUTTERPRESS_CSS = `
1231
1231
  cap at equal specificity */
1232
1232
  .gp-small { width: 25%; max-width: 100%; }
1233
1233
  .gp-medium { width: 50%; max-width: 100%; }
1234
- .gp-large { width: 75%; max-width: 100%; }
1234
+ .gp-large { width: 60%; max-width: 100%; }
1235
+ .gp-xl { width: 80%; max-width: 100%; }
1236
+ .gp-xxl { width: 90%; max-width: 100%; }
1235
1237
 
1236
1238
  /* float clearance presets — consumed by var(--gp-gap) in the float rules
1237
1239
  above and by .gp-shape's shape-margin below; --gp-gap itself is
@@ -1289,10 +1291,16 @@ img.gp-shape {
1289
1291
  }
1290
1292
 
1291
1293
  /* pin — within the nearest positioned ancestor (.page/.spread, rule above).
1292
- inset:0 and the explicit centers are load-bearing; see doctrine comment. */
1294
+ The four insets are the pin box: 0 (the text block's edge) unless an
1295
+ engine set --gp-pin-<edge> on the page root for an edge a pin uses (the
1296
+ art-safe line). Non-auto insets and the explicit centers are
1297
+ load-bearing; see doctrine comment. */
1293
1298
  .gp-pin {
1294
1299
  position: absolute;
1295
- inset: 0;
1300
+ top: var(--gp-pin-top, 0px);
1301
+ right: var(--gp-pin-right, 0px);
1302
+ bottom: var(--gp-pin-bottom, 0px);
1303
+ left: var(--gp-pin-left, 0px);
1296
1304
  align-self: center;
1297
1305
  justify-self: center;
1298
1306
  margin: 0;
@@ -1307,6 +1315,16 @@ img.gp-shape {
1307
1315
  .gp-left { justify-self: start; }
1308
1316
  .gp-right { justify-self: end; }
1309
1317
 
1318
+ /* the escape hatch — the paper's edge, past the printer's safe area: the
1319
+ pin box loses its art-safe insets. Set on the pin itself, so it beats the
1320
+ values the page root hands down. */
1321
+ .gp-pin.gp-flush {
1322
+ --gp-pin-top: 0px;
1323
+ --gp-pin-right: 0px;
1324
+ --gp-pin-bottom: 0px;
1325
+ --gp-pin-left: 0px;
1326
+ }
1327
+
1310
1328
  /* wrapper-margin neutralization (same pattern and rationale as the
1311
1329
  .gp-bleed paragraph-margin note in the doctrine comment; for pin, the
1312
1330
  emptied paragraph would otherwise leave a phantom margin gap in flow) */
@@ -1416,6 +1434,8 @@ var GP_CLASSES = new Set([
1416
1434
  "gp-small",
1417
1435
  "gp-medium",
1418
1436
  "gp-large",
1437
+ "gp-xl",
1438
+ "gp-xxl",
1419
1439
  "gp-tight",
1420
1440
  "gp-loose",
1421
1441
  "gp-columns",
@@ -1432,6 +1452,7 @@ var GP_CLASSES = new Set([
1432
1452
  "gp-pin",
1433
1453
  "gp-top",
1434
1454
  "gp-bottom",
1455
+ "gp-flush",
1435
1456
  "gp-behind",
1436
1457
  "gp-base",
1437
1458
  "gp-raised",
@@ -1445,8 +1466,7 @@ var GP_CLASSES = new Set([
1445
1466
  "gp-alert-caution",
1446
1467
  "gp-page-break",
1447
1468
  "gp-column-break",
1448
- "gp-continued",
1449
- "gp-flush"
1469
+ "gp-continued"
1450
1470
  ]);
1451
1471
 
1452
1472
  // src/lib/markdown/table-align.ts
@@ -1550,6 +1570,21 @@ function hasPinClass(token) {
1550
1570
  const cls = token.attrGet && token.attrGet("class");
1551
1571
  return typeof cls === "string" && cls.split(/\s+/).includes("gp-pin");
1552
1572
  }
1573
+ var PIN_EDGES = ["gp-top", "gp-right", "gp-bottom", "gp-left"];
1574
+ var FLUSH_ART_MSG = "`.gp-flush` puts this image on the paper's edge, past the printer's safe area. Printers trim up to 0.125in there, so keep it for art that is meant to bleed off the page.";
1575
+ function checkFlushArt(token, env, line) {
1576
+ if (token.type !== "image")
1577
+ return;
1578
+ const cls = token.attrGet && token.attrGet("class");
1579
+ if (typeof cls !== "string")
1580
+ return;
1581
+ const classes = cls.split(/\s+/);
1582
+ if (!classes.includes("gp-pin") || !classes.includes("gp-flush"))
1583
+ return;
1584
+ if (!classes.some((c) => PIN_EDGES.includes(c)))
1585
+ return;
1586
+ warn2(env, line, "flush_art", FLUSH_ART_MSG);
1587
+ }
1553
1588
  function elementNameFor(token) {
1554
1589
  switch (token.type) {
1555
1590
  case "layout_chapter_open":
@@ -1609,6 +1644,7 @@ function gpPinScope(md) {
1609
1644
  if (token.type === "inline" && token.children) {
1610
1645
  for (const child of token.children) {
1611
1646
  checkUnknownGpClasses(child, state.env, line);
1647
+ checkFlushArt(child, state.env, line);
1612
1648
  }
1613
1649
  }
1614
1650
  }
@@ -2343,6 +2379,20 @@ function lastPageMode(print) {
2343
2379
  }
2344
2380
  var WHICH_VALUES = new Set(["first", "start", "last", "first-except"]);
2345
2381
 
2382
+ // src/engine/shared/print-area.ts
2383
+ function printAreaMeta(page) {
2384
+ const area = JSON.stringify({
2385
+ width: page.width,
2386
+ height: page.height,
2387
+ bleed: page.bleed ?? 0,
2388
+ safe: page.safe ?? 0,
2389
+ artSafe: page.artSafe,
2390
+ preset: page.preset,
2391
+ spec: page.spec
2392
+ });
2393
+ return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
2394
+ }
2395
+
2346
2396
  // src/lib/markdown/assemble.ts
2347
2397
  async function assembleBookHtml(opts) {
2348
2398
  const title = opts.title ?? "Document";
@@ -2408,7 +2458,8 @@ ${projectCss.trim()}` : null
2408
2458
  <html lang="en"${printAttrs}>
2409
2459
  <head>
2410
2460
  <meta charset="UTF-8">
2411
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
2461
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">${opts.printArea ? `
2462
+ ${printAreaMeta(opts.printArea)}` : ""}
2412
2463
  <title>${title}</title>${preloadTags}
2413
2464
  <style data-project-css>
2414
2465
  ${inlineCss}
@@ -119,8 +119,24 @@ export interface GutterpressManifest {
119
119
  /** `k-only` prints near-black text on the black plate alone (#331). */
120
120
  blackText?: "icc" | "k-only";
121
121
  /**
122
- * Resolution (DPI) of the picture Ghostscript makes of a page it flattens
123
- * because the page uses transparency. Unset: Ghostscript's default (720).
122
+ * A page that uses transparency, which PDF/X cannot hold (#385).
123
+ * `flatten-page` (default): Ghostscript rasterizes the whole page, text
124
+ * included. `preserve-text`: flatten the artwork only and keep the text
125
+ * live on top; a page whose text cannot be split (translucent or blended,
126
+ * Type 3, pattern-filled or a clip) or that has a repeating or conic
127
+ * gradient falls back to the whole-page flattening.
128
+ */
129
+ transparency?: "flatten-page" | "preserve-text";
130
+ /**
131
+ * `preserve-text` only: pages (1-based, as the build reports them) left to
132
+ * Ghostscript's whole-page flattening, e.g. a page whose look depends on a
133
+ * cover or tint painted over its text.
134
+ */
135
+ flattenPages?: number[];
136
+ /**
137
+ * Resolution (DPI) of every picture made from a transparent page: the
138
+ * whole page Ghostscript flattens, and the `preserve-text` artwork.
139
+ * Unset: Ghostscript's default (720) and 300 for `preserve-text` artwork.
124
140
  */
125
141
  rasterDpi?: number;
126
142
  };
@@ -155,17 +171,18 @@ export interface GutterpressManifest {
155
171
  * A bleed (pt) the page `width` x `height` ALREADY CONTAINS, on the top,
156
172
  * bottom and OUTER edges only (the binding edge has none) — how
157
173
  * DriveThruRPG's interior file is cut (8.625 x 11.25in for an 8.5 x 11in
158
- * book). It changes nothing in the PDF: it only tells the preview's safe
159
- * area & bleed overlay and `pdf.print.safe-margins` where the trim is.
174
+ * book). By itself it changes nothing in the PDF: it tells the preview's
175
+ * safe area & bleed overlay, `pdf.print.safe-margins` and pinned art
176
+ * (`.gp-pin` on an edge sits on the art-safe line) where the trim is.
160
177
  */
161
178
  bleed?: number;
162
179
  /**
163
180
  * The TEXT rule (pt): glyphs stay at least this far inside the trim on
164
181
  * EVERY edge — DriveThruRPG's "keep text 0.5in inside the trim" (`dtrpg`
165
182
  * sets 36). The preview's overlay outlines it and flags text outside it,
166
- * and `pdf.print.safe-margins` fails a built PDF whose text is closer. Like
167
- * `bleed`, it changes nothing in the PDF, and nothing in a book's CSS moves
168
- * it.
183
+ * and `pdf.print.safe-margins` fails a built PDF whose text is closer. An
184
+ * image pinned to the binding edge sits on it (and to any edge, where
185
+ * `artSafe` is not set); nothing in a book's CSS moves it.
169
186
  */
170
187
  safe?: number;
171
188
  /**
@@ -173,7 +190,8 @@ export interface GutterpressManifest {
173
190
  * borders, rules — stays at least this far inside the trim on the top,
174
191
  * bottom and OUTER edge; at the binding edge it uses `safe`. DriveThruRPG's
175
192
  * "keep non-bleeding art 0.25in from the outside edges" (`dtrpg` sets 18).
176
- * Absent = the same as `safe`.
193
+ * An image pinned to the top, bottom or outer edge (`.gp-pin`) sits on this
194
+ * line unless it carries `.gp-flush`. Absent = the same as `safe`.
177
195
  */
178
196
  artSafe?: number;
179
197
  };
@@ -290,6 +308,8 @@ export interface ResolvedConfig {
290
308
  icc: string;
291
309
  stripAnnotations: boolean;
292
310
  blackText: "icc" | "k-only";
311
+ transparency: "flatten-page" | "preserve-text";
312
+ flattenPages: number[];
293
313
  rasterDpi?: number;
294
314
  };
295
315
  print: {
@@ -106,8 +106,8 @@ export interface GrayRaster {
106
106
  }
107
107
  /** Ghostscript's path, or `null`. Callers self-skip, as the PDF/X suites do. */
108
108
  export declare function resolveRasterizer(): Promise<string | null>;
109
- /** Page 1 of `pdfPath` as an 8-bit grayscale raster at 100 dpi. */
110
- export declare function rasterizePdfPage(gsBin: string, pdfPath: string, outDir: string, tag: string): GrayRaster;
109
+ /** Page `page` (default 1) of `pdfPath` as an 8-bit grayscale raster at 100 dpi. */
110
+ export declare function rasterizePdfPage(gsBin: string, pdfPath: string, outDir: string, tag: string, page?: number): GrayRaster;
111
111
  /**
112
112
  * Mean absolute per-pixel difference. `0` means the two prints are identical —
113
113
  * i.e. the declaration under test changed nothing at all.
@@ -1,20 +1,21 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-zacevwcy.js";
3
+ } from "./cli-0ey902sx.js";
4
4
  import {
5
5
  log
6
- } from "./cli-gh0ym80r.js";
6
+ } from "./cli-h4tfmpg3.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-7z29z4wz.js";
11
+ } from "./cli-fmksazej.js";
12
12
  import"./cli-ht1bcqdd.js";
13
- import"./cli-j802s31j.js";
13
+ import"./cli-61vd4ews.js";
14
14
  import {
15
15
  BuildError,
16
16
  EXIT_CODES
17
17
  } from "./cli-46ycxe6r.js";
18
+ import"./cli-sth219v1.js";
18
19
  import"./cli-eyyggect.js";
19
20
  import"./cli-asap4t1z.js";
20
21
  import"./cli-5qr3z0qa.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gutterpress",
3
- "version": "0.11.20-alpha.2",
3
+ "version": "0.11.20-alpha.4",
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",
@@ -1,59 +0,0 @@
1
- /**
2
- * `.gp-flush` policy — shared by the compiler and the viewer, like every rule
3
- * that DECIDES something (see synthesis.ts's header).
4
- *
5
- * A pinned image reaches the paper only if the page's own margin is removed
6
- * on that edge: Chromium's printable area IS the page area — MEASURED, a
7
- * pinned box pulled into the margin with negative insets fragments onto the
8
- * NEXT sheet, one moved there with a transform is clipped away entirely, and
9
- * a margin box in a ~zero margin (1px, with padding/height compensation)
10
- * simply does not render. So flush is implemented as page GEOMETRY, per page:
11
- *
12
- * - The COMPILER aliases the flush root's own page context under a
13
- * generated name (verbatim rule copies — never a resolved flatten, which
14
- * is the "re-implement the @page cascade" trap tier2's history records),
15
- * zeroes the flushed margins on the alias, and assigns the root to it.
16
- * - The VIEWER keeps the author's page context and adjusts the strip
17
- * geometry in JS from these same functions.
18
- *
19
- * Margin boxes live IN the margin, so the flushed edge's furniture (folio,
20
- * running head) cannot render natively there — MEASURED, no compensation
21
- * trick survives. Both renderers therefore keep painting that furniture
22
- * themselves at its ORIGINAL coordinates: the viewer already synthesizes all
23
- * furniture as DOM; the compiler injects the flushed edge's boxes into the
24
- * page root as positioned elements with engine-resolved text. Nothing the
25
- * author declared is lost — that is the contract this module's box map
26
- * exists to keep.
27
- */
28
- import type { PageGeometry } from "./gcpm-extract.ts";
29
- export declare const FLUSH_EDGES: readonly ["top", "right", "bottom", "left"];
30
- export type FlushEdge = (typeof FLUSH_EDGES)[number];
31
- /** Minimal DOM shape needed by `flushEdgesIn` — keeps this module node-safe. */
32
- interface QueryRoot {
33
- querySelector(sel: string): unknown;
34
- }
35
- /**
36
- * Which edges the flush pins inside `root` ask for, in canonical t/r/b/l
37
- * order. A `.gp-flush` without `.gp-pin` or without an edge word is inert by
38
- * construction — every selector here requires all three.
39
- */
40
- export declare function flushEdgesIn(root: QueryRoot): FlushEdge[];
41
- /** Canonical short key for an edge set: "b", "rb", "trbl"… (t/r/b/l order). */
42
- export declare function flushKey(edges: readonly FlushEdge[]): string;
43
- /**
44
- * Generated page name for (author page context, edge set). The `gp--` double
45
- * dash marks an engine-generated page (the `gp--blank` convention); these
46
- * exist only in builds that actually contain a flush pin, which is why the
47
- * width check may treat them as real author contexts.
48
- */
49
- export declare function flushPageName(authorPage: string | undefined, edges: readonly FlushEdge[]): string;
50
- /** The page's margins with the flushed edges freed. */
51
- export declare function flushMargins(margin: PageGeometry["margin"], edges: readonly FlushEdge[]): PageGeometry["margin"];
52
- /**
53
- * Margin boxes that live (wholly or partly) in a flushed edge's margin area —
54
- * the set both renderers must keep painting themselves. A corner box belongs
55
- * to BOTH of its edges: `bottom-left-corner` dies when either the bottom or
56
- * the left margin is freed.
57
- */
58
- export declare function marginBoxesOnEdges(edges: readonly FlushEdge[]): string[];
59
- export {};