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
@@ -29,7 +29,7 @@ import {
29
29
  uniqueExtensionId,
30
30
  withFetchTimeout,
31
31
  writeManifestDoc
32
- } from "./index-dm7hx46j.js";
32
+ } from "./index-xe83m9mc.js";
33
33
  import {
34
34
  EXTENSION_MANIFEST_FILENAME,
35
35
  GUTTERPRESS_CSS,
@@ -64,19 +64,21 @@ import {
64
64
  setLogLevel,
65
65
  slugify,
66
66
  warn
67
- } from "./index-38e18a7j.js";
67
+ } from "./index-thmwa4f5.js";
68
68
  import {
69
69
  blackTextToGray
70
70
  } from "./index-ht1bcqdd.js";
71
71
  import {
72
72
  MARGIN_BOX_IGNORED_PROPERTIES,
73
+ extract,
73
74
  lastPageMode,
74
- parseSize
75
- } from "./index-5h9a38jn.js";
75
+ parseSize,
76
+ resolvePage
77
+ } from "./index-apc8w9w8.js";
76
78
  import {
77
79
  BuildError,
78
80
  getAssetPath
79
- } from "./index-dhknsvfz.js";
81
+ } from "./index-jsz0s3dy.js";
80
82
  import {
81
83
  countLinkAnnotations,
82
84
  getImageResolutions,
@@ -89,7 +91,7 @@ import {
89
91
  loadPdf,
90
92
  pageHasMarks,
91
93
  retainPdfCache
92
- } from "./index-sth219v1.js";
94
+ } from "./index-2ppw6q9v.js";
93
95
  import {
94
96
  INSTALL_HINTS,
95
97
  enhancedPath,
@@ -184,6 +186,17 @@ function canonicalChapterId(p) {
184
186
  }
185
187
 
186
188
  // src/engine/shared/print-area.ts
189
+ function isPositivePoints(value) {
190
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
191
+ }
192
+ var round3 = (n) => Math.round(n * 1000) / 1000;
193
+ function cssLength(pt) {
194
+ const inches = round3(pt / 72);
195
+ return Math.abs(inches * 72 - pt) < 0.000001 ? `${inches}in` : `${round3(pt)}pt`;
196
+ }
197
+ function formatPageSize(page) {
198
+ return `${cssLength(page.width)} ${cssLength(page.height)}`;
199
+ }
187
200
  function printAreaMeta(page) {
188
201
  const area = JSON.stringify({
189
202
  width: page.width,
@@ -197,8 +210,71 @@ function printAreaMeta(page) {
197
210
  });
198
211
  return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
199
212
  }
213
+ function pageBoxes(area, side, scale = 1) {
214
+ const w = area.width * scale;
215
+ const h = area.height * scale;
216
+ const b = Math.max(0, area.bleed) * scale;
217
+ const s = Math.max(0, area.safe) * scale;
218
+ const a = Math.max(0, area.artSafe) * scale;
219
+ const g = Math.max(0, area.gutter) * scale;
220
+ const [textLeft, textRight] = side === "verso" ? [s, g] : [g, s];
221
+ const [artLeft, artRight] = side === "verso" ? [a, g] : [g, a];
222
+ const trim = { x: side === "verso" ? b : 0, y: b, w: Math.max(0, w - b), h: Math.max(0, h - 2 * b) };
223
+ return {
224
+ sheet: { x: 0, y: 0, w, h },
225
+ trim,
226
+ safe: {
227
+ x: trim.x + textLeft,
228
+ y: trim.y + s,
229
+ w: Math.max(0, trim.w - textLeft - textRight),
230
+ h: Math.max(0, trim.h - 2 * s)
231
+ },
232
+ safeArt: {
233
+ x: trim.x + artLeft,
234
+ y: trim.y + a,
235
+ w: Math.max(0, trim.w - artLeft - artRight),
236
+ h: Math.max(0, trim.h - 2 * a)
237
+ },
238
+ bleed: b
239
+ };
240
+ }
241
+ var SEVERITY = { inside: 0, unsafe: 1, bleed: 2, "off-page": 3 };
242
+ function judgeRect(r, boxes, rule, tol = 0) {
243
+ const { sheet, trim } = boxes;
244
+ const safe = rule === "art" ? boxes.safeArt : boxes.safe;
245
+ const sides = [
246
+ ["left", -r.x, -safe.x, -trim.x, -sheet.x],
247
+ ["right", r.x + r.w, safe.x + safe.w, trim.x + trim.w, sheet.x + sheet.w],
248
+ ["top", -r.y, -safe.y, -trim.y, -sheet.y],
249
+ ["bottom", r.y + r.h, safe.y + safe.h, trim.y + trim.h, sheet.y + sheet.h]
250
+ ];
251
+ let worst = { kind: "inside", gap: 0, need: 0, edge: null };
252
+ let beyond = 0;
253
+ for (const [edge, ink, limit, trimEdge, sheetEdge] of sides) {
254
+ const past = ink - limit;
255
+ if (past <= tol || rule === "art" && ink >= sheetEdge - tol)
256
+ continue;
257
+ const kind = ink > sheetEdge + tol ? "off-page" : ink > trimEdge + tol ? "bleed" : "unsafe";
258
+ if (SEVERITY[kind] > SEVERITY[worst.kind] || kind === worst.kind && past > beyond) {
259
+ worst = { kind, gap: trimEdge - ink, need: trimEdge - limit, edge };
260
+ beyond = past;
261
+ }
262
+ }
263
+ return worst;
264
+ }
200
265
 
201
266
  // src/lib/markdown/assemble.ts
267
+ function defaultPageSizeCss(css, page) {
268
+ if (!page || !isPositivePoints(page.width) || !isPositivePoints(page.height))
269
+ return null;
270
+ try {
271
+ if (resolvePage(extract(css)).decls.size !== undefined)
272
+ return null;
273
+ } catch {
274
+ return null;
275
+ }
276
+ return `@page { size: ${formatPageSize(page)}; }`;
277
+ }
202
278
  async function assembleBookHtml(opts) {
203
279
  const title = opts.title ?? "Document";
204
280
  const projectCss = opts.projectCss ?? "";
@@ -236,6 +312,8 @@ async function assembleBookHtml(opts) {
236
312
  imageRefs.add(ref);
237
313
  opts.onImageRefs([...imageRefs]);
238
314
  }
315
+ const pageSize = defaultPageSizeCss(`${pluginCss}
316
+ ${projectCss}`, opts.printArea);
239
317
  const inlineCss = [
240
318
  "@layer gp.marker, gp.vocab;",
241
319
  `/* gutterpress markers */
@@ -247,6 +325,8 @@ ${TWO_VERSIONS_CSS.trim()}
247
325
  @layer gp.vocab {
248
326
  ${GUTTERPRESS_CSS.trim()}
249
327
  }`,
328
+ pageSize ? `/* page size (manifest) */
329
+ ${pageSize}` : null,
250
330
  pluginCss ? `/* extension css */
251
331
  ${pluginCss.trim()}` : null,
252
332
  `/* column alignment */
@@ -1827,31 +1907,39 @@ var TOLERANCE_PT = 0.5;
1827
1907
  var ABOVE_BASELINE = 0.8;
1828
1908
  var BELOW_BASELINE = 0.2;
1829
1909
  var inches = (pt) => `${+(pt / 72).toFixed(2)}in`;
1910
+ var size = (r) => `${(r.w / 72).toFixed(1)} x ${(r.h / 72).toFixed(1)}in`;
1911
+ var fromPdf = (x, y, w, h, sheetHeight) => ({
1912
+ x,
1913
+ y: sheetHeight - y - h,
1914
+ w,
1915
+ h
1916
+ });
1830
1917
  var check4 = {
1831
1918
  id: "pdf.print.safe-margins",
1832
1919
  name: "Safe margins",
1833
- description: "Fails when text sits closer to the trim than `page.safe` (DriveThruRPG: 0.5in on every edge), or than `page.gutter` at the binding edge where one is set. Text only: art is judged in the preview's safe-area overlay, not here. Skipped when `page.safe` is not set",
1920
+ description: "Fails when text sits closer to the trim than `page.safe` (DriveThruRPG: 0.5in on every edge) or an image that does not bleed closer than `page.artSafe` (DriveThruRPG: 0.25in on the top, bottom and outside edges), both held to `page.gutter` at the binding edge where one is set; and when an image crosses the trim without reaching the sheet's edge (it ends in the bleed band). Text and images: filled backgrounds and borders are judged in the preview's safe-area overlay, not here. Skipped when `page.safe` is not set",
1834
1921
  category: "pdf",
1835
1922
  phase: "post-build",
1836
1923
  async run(ctx) {
1837
- const { width, height, bleed = 0, safe = 0, gutter = safe } = ctx.config.page;
1924
+ const { width, height, bleed = 0, safe = 0, artSafe = safe, gutter = safe } = ctx.config.page;
1838
1925
  if (!ctx.pdfPath || !safe)
1839
1926
  return [];
1840
1927
  const doc = await loadPdf(ctx.pdfPath);
1841
1928
  if (!doc) {
1842
- return [inspectionFailed(check4.id, "Could not parse the PDF's text positions.", { file: ctx.pdfPath })];
1929
+ return [inspectionFailed(check4.id, "Could not parse the PDF's text and image positions.", { file: ctx.pdfPath })];
1843
1930
  }
1931
+ const area = { width, height, bleed, safe, artSafe, gutter };
1844
1932
  const rule = ctx.config.spec?.label ?? "page.safe";
1933
+ const textRule = gutter === safe ? `${rule}: text must stay at least ${inches(safe)} inside the trim on every edge.` : `${rule}: text must stay at least ${inches(safe)} inside the trim, ${inches(gutter)} at the binding.`;
1934
+ const artRule = gutter === artSafe ? `${rule}: art that does not bleed must stay at least ${inches(artSafe)} inside the trim on every edge.` : `${rule}: art that does not bleed must stay at least ${inches(artSafe)} inside the trim, ${inches(gutter)} at the binding.`;
1845
1935
  const results = [];
1846
1936
  try {
1937
+ const { imagesByPage } = await getOpPass(doc);
1847
1938
  for (let pageNum = 1;pageNum <= doc.numPages; pageNum++) {
1848
- const recto = pageNum % 2 === 1;
1849
- const trim = {
1850
- left: recto ? 0 : bleed,
1851
- right: width - (recto ? bleed : 0),
1852
- bottom: bleed,
1853
- top: height - bleed
1854
- };
1939
+ const side = pageNum % 2 === 1 ? "recto" : "verso";
1940
+ const boxes = pageBoxes(area, side);
1941
+ const binding = side === "recto" ? "left" : "right";
1942
+ const edgeName = (edge) => edge === "top" || edge === "bottom" ? edge : edge === binding ? "binding" : "outside";
1855
1943
  const { items } = await (await doc.getPage(pageNum)).getTextContent();
1856
1944
  for (const item of items) {
1857
1945
  if (!("str" in item) || !item.str.trim())
@@ -1866,28 +1954,55 @@ var check4 = {
1866
1954
  ys.push(f + s * along * b + u * d);
1867
1955
  }
1868
1956
  }
1869
- const gaps = [
1870
- [recto ? "binding" : "outside", Math.min(...xs) - trim.left],
1871
- [recto ? "outside" : "binding", trim.right - Math.max(...xs)],
1872
- ["bottom", Math.min(...ys) - trim.bottom],
1873
- ["top", trim.top - Math.max(...ys)]
1874
- ];
1875
- const [edge, shortfall] = gaps.map(([name, gap]) => [name, (name === "binding" ? gutter : safe) - gap]).reduce((worst, g) => g[1] > worst[1] ? g : worst);
1876
- if (shortfall <= TOLERANCE_PT)
1957
+ const x0 = Math.min(...xs);
1958
+ const y0 = Math.min(...ys);
1959
+ const box = fromPdf(x0, y0, Math.max(...xs) - x0, Math.max(...ys) - y0, height);
1960
+ const { kind, edge, gap, need } = judgeRect(box, boxes, "text", TOLERANCE_PT);
1961
+ if (kind === "inside")
1877
1962
  continue;
1963
+ const shortfall = need - gap;
1878
1964
  const text = item.str.trim();
1879
1965
  results.push(finding(check4.id, {
1880
1966
  severity: "error",
1881
- message: `Page ${pageNum}: "${text.slice(0, 40)}${text.length > 40 ? "…" : ""}" is ${inches(shortfall)} too close to the ${edge} edge.`,
1967
+ message: `Page ${pageNum}: "${text.slice(0, 40)}${text.length > 40 ? "…" : ""}" is ${inches(shortfall)} too close to the ${edgeName(edge)} edge.`,
1882
1968
  file: ctx.pdfPath,
1883
- detail: gutter === safe ? `${rule}: text must stay at least ${inches(safe)} inside the trim on every edge.` : `${rule}: text must stay at least ${inches(safe)} inside the trim, ${inches(gutter)} at the binding.`,
1969
+ detail: textRule,
1884
1970
  code: "text-outside-safe-margin",
1885
- data: { page: pageNum, edge, shortfallPt: +shortfall.toFixed(2) }
1971
+ data: { page: pageNum, edge: edgeName(edge), shortfallPt: +shortfall.toFixed(2) }
1972
+ }));
1973
+ }
1974
+ for (const image of imagesByPage.get(pageNum) ?? []) {
1975
+ const { x, y, w, h } = image.bounds;
1976
+ const box = fromPdf(x, y, w, h, height);
1977
+ const { kind, edge, gap, need } = judgeRect(box, boxes, "art", TOLERANCE_PT);
1978
+ if (kind === "inside")
1979
+ continue;
1980
+ const name = edgeName(edge);
1981
+ if (kind === "bleed") {
1982
+ const short = boxes.bleed + gap;
1983
+ results.push(finding(check4.id, {
1984
+ severity: "error",
1985
+ message: `Page ${pageNum}: a ${size(box)} image ends in the bleed band on the ${name} edge, ${inches(short)} short of the sheet's edge.`,
1986
+ file: ctx.pdfPath,
1987
+ detail: `${rule}: art either stays ${inches(artSafe)} inside the trim or bleeds, running past it to the sheet's edge; art that stops between the two can leave a white sliver when the page is cut.`,
1988
+ code: "art-ends-in-bleed-band",
1989
+ data: { page: pageNum, edge: name, shortfallPt: +short.toFixed(2) }
1990
+ }));
1991
+ continue;
1992
+ }
1993
+ const shortfall = need - gap;
1994
+ results.push(finding(check4.id, {
1995
+ severity: "error",
1996
+ message: `Page ${pageNum}: a ${size(box)} image is ${inches(shortfall)} too close to the ${name} edge.`,
1997
+ file: ctx.pdfPath,
1998
+ detail: artRule,
1999
+ code: "art-outside-safe-margin",
2000
+ data: { page: pageNum, edge: name, shortfallPt: +shortfall.toFixed(2) }
1886
2001
  }));
1887
2002
  }
1888
2003
  }
1889
2004
  } catch {
1890
- return [inspectionFailed(check4.id, "Could not read the PDF's text positions.", { file: ctx.pdfPath })];
2005
+ return [inspectionFailed(check4.id, "Could not read the PDF's text and image positions.", { file: ctx.pdfPath })];
1891
2006
  }
1892
2007
  return results;
1893
2008
  }
@@ -2300,16 +2415,16 @@ var check10 = {
2300
2415
  if (!doc) {
2301
2416
  return [inspectionFailed(check10.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
2302
2417
  }
2303
- const size = ctx.config.print.signature;
2418
+ const size2 = ctx.config.print.signature;
2304
2419
  const pages = doc.numPages;
2305
- if ((pages + 1) % size === 0)
2420
+ if ((pages + 1) % size2 === 0)
2306
2421
  return [];
2307
- const data = { pages, signature: size };
2308
- if (pages % size !== 0) {
2422
+ const data = { pages, signature: size2 };
2423
+ if (pages % size2 !== 0) {
2309
2424
  return [
2310
2425
  finding(check10.id, {
2311
2426
  severity: "error",
2312
- message: `Page count ${pages} does not fit a signature of ${size}: it must be a multiple of ${size}, or one short of one.`,
2427
+ message: `Page count ${pages} does not fit a signature of ${size2}: it must be a multiple of ${size2}, or one short of one.`,
2313
2428
  file: ctx.pdfPath,
2314
2429
  detail: "Set `print.signature` in the manifest and rebuild; the build pads the interior for you.",
2315
2430
  code: "signature-page-count",
@@ -2330,7 +2445,7 @@ var check10 = {
2330
2445
  severity: "error",
2331
2446
  message: `The last page (${pages}) has content, but the printer needs it blank for its own information.`,
2332
2447
  file: ctx.pdfPath,
2333
- detail: "The build adds that page for you (`print.lastPage: blank`, or `short-by-one` to stop a page short of a multiple " + `of ${size} and let the printer add it). Check that the interior was built with this manifest, and that nothing in it ` + "forces content onto the final page.",
2448
+ detail: "The build adds that page for you (`print.lastPage: blank`, or `short-by-one` to stop a page short of a multiple " + `of ${size2} and let the printer add it). Check that the interior was built with this manifest, and that nothing in it ` + "forces content onto the final page.",
2334
2449
  code: "signature-last-page",
2335
2450
  data
2336
2451
  })
@@ -5553,7 +5668,7 @@ class PdfOutput {
5553
5668
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
5554
5669
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
5555
5670
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
5556
- const { buildNativePdf } = await import("./engine-qwkyhrej.js");
5671
+ const { buildNativePdf } = await import("./engine-9710cd7x.js");
5557
5672
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
5558
5673
  title: config.title,
5559
5674
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -5590,7 +5705,7 @@ class PdfOutput {
5590
5705
  }
5591
5706
  const flattenPages = config.pdfx.flattenPages;
5592
5707
  if (transparent.length > 0 && config.pdfx.transparency === "preserve-text") {
5593
- const { unsplittableTextReasons } = await import("./pdf-inspect-zdmy7qca.js");
5708
+ const { unsplittableTextReasons } = await import("./pdf-inspect-wnw4za71.js");
5594
5709
  const reasons = await unsplittableTextReasons(rawBytes, transparent).catch((err) => {
5595
5710
  log.warn(`pdfx.transparency: preserve-text could not inspect this PDF's text (${err instanceof Error ? err.message : String(err)}), so every page that uses transparency is flattened whole, as with flatten-page.`);
5596
5711
  return null;
@@ -7099,9 +7214,6 @@ import { mkdir as mkdir5, readFile as readFile24, writeFile as writeFile7 } from
7099
7214
  import path13 from "node:path";
7100
7215
  import postcss5 from "postcss";
7101
7216
  import { isMap, Scalar } from "yaml";
7102
- function isPositivePoints(value) {
7103
- return typeof value === "number" && Number.isFinite(value) && value > 0;
7104
- }
7105
7217
  function writePageBounds(doc, page) {
7106
7218
  const tolerance = page.tolerance ?? numberAt(doc, ["page", "tolerance"]);
7107
7219
  if (!isMap(doc.get("page", true)))
@@ -7140,14 +7252,6 @@ function pageSizeForChoice(choice) {
7140
7252
  const base = PRESETS[choice.preset].page;
7141
7253
  return { width: base.width, height: base.height };
7142
7254
  }
7143
- var round3 = (n) => Math.round(n * 1000) / 1000;
7144
- function cssLength(pt) {
7145
- const inches2 = round3(pt / 72);
7146
- return Math.abs(inches2 * 72 - pt) < 0.000001 ? `${inches2}in` : `${round3(pt)}pt`;
7147
- }
7148
- function formatPageSize(page) {
7149
- return `${cssLength(page.width)} ${cssLength(page.height)}`;
7150
- }
7151
7255
  var SAME_SIZE_PT = 0.5;
7152
7256
  function sameSize(a, b) {
7153
7257
  return Math.abs(a.width - b.width) < SAME_SIZE_PT && Math.abs(a.height - b.height) < SAME_SIZE_PT;
@@ -7213,9 +7317,9 @@ var relDisplay2 = (projectDir, abs) => path13.relative(projectDir, abs).split(pa
7213
7317
  async function findCssSize(projectDir, sheets) {
7214
7318
  let found = null;
7215
7319
  for (const file of sheets) {
7216
- const size = readPageSizeFromCss(await readFile24(file, "utf8"));
7217
- if (size)
7218
- found = { file, size };
7320
+ const size2 = readPageSizeFromCss(await readFile24(file, "utf8"));
7321
+ if (size2)
7322
+ found = { file, size: size2 };
7219
7323
  }
7220
7324
  return found;
7221
7325
  }
@@ -7235,7 +7339,7 @@ async function readPageSetup(projectDir) {
7235
7339
  };
7236
7340
  }
7237
7341
  async function setPageSetup(projectDir, choice) {
7238
- const size = pageSizeForChoice(choice);
7342
+ const size2 = pageSizeForChoice(choice);
7239
7343
  const sheets = await ownStylesheets(projectDir);
7240
7344
  const found = await findCssSize(projectDir, sheets);
7241
7345
  const newSheet = path13.join(projectDir, "styles", "book.css");
@@ -7243,14 +7347,14 @@ async function setPageSetup(projectDir, choice) {
7243
7347
  const before = existsSync8(target) ? await readFile24(target, "utf8") : "";
7244
7348
  let after;
7245
7349
  try {
7246
- after = setPageSizeInCss(before, size);
7350
+ after = setPageSizeInCss(before, size2);
7247
7351
  } catch (e) {
7248
7352
  throw new UsageError(`Could not update the page size: ${relDisplay2(projectDir, target)} has a CSS error ` + `(${e instanceof Error ? e.message : String(e)}). Fix it, then try again.`);
7249
7353
  }
7250
7354
  const { doc, file } = await loadManifestDoc(projectDir);
7251
7355
  doc.set("preset", choice.preset);
7252
7356
  if (choice.preset === "custom")
7253
- writePageBounds(doc, size);
7357
+ writePageBounds(doc, size2);
7254
7358
  else
7255
7359
  deletePageBounds(doc);
7256
7360
  if (target === newSheet && !sheets.includes(newSheet)) {
@@ -8875,10 +8979,10 @@ function isSmallBody(body) {
8875
8979
  return false;
8876
8980
  let total = 0;
8877
8981
  for (const c of body) {
8878
- const size = c?.byteLength;
8879
- if (typeof size !== "number")
8982
+ const size2 = c?.byteLength;
8983
+ if (typeof size2 !== "number")
8880
8984
  return false;
8881
- total += size;
8985
+ total += size2;
8882
8986
  }
8883
8987
  return total <= SMALL_BODY_MAX_BYTES;
8884
8988
  }
@@ -12520,7 +12624,7 @@ async function resolvePublishProvider(id, book) {
12520
12624
  const ext = await bookExtensions(book);
12521
12625
  const npmEntry = ext.extensions.some((c) => !c.path && c.name === id);
12522
12626
  if (npmEntry) {
12523
- const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-926mv2qk.js");
12627
+ const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-3gsj0qxp.js");
12524
12628
  await restoreForCommand2(book.projectDir, { manifestPath: book.manifestPath, failFast: true });
12525
12629
  }
12526
12630
  const publishers = await discover(ext);
@@ -12558,7 +12662,7 @@ function infoFor(p) {
12558
12662
  }
12559
12663
  function pluginProvider(p) {
12560
12664
  let loading;
12561
- const load = () => loading ??= import("./plugins-yeh4shd7.js").then((m) => m.loadPublishFunction(p));
12665
+ const load = () => loading ??= import("./plugins-8x9rsf21.js").then((m) => m.loadPublishFunction(p));
12562
12666
  return {
12563
12667
  info: infoFor(p),
12564
12668
  async authenticate() {
@@ -37,7 +37,7 @@ import { satisfies, validRange } from "semver";
37
37
  // package.json
38
38
  var package_default = {
39
39
  name: "gutterpress",
40
- version: "0.11.22-alpha.1",
40
+ version: "0.11.22-alpha.3",
41
41
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
42
42
  author: "itlackey",
43
43
  license: "MPL-2.0",
@@ -9,12 +9,12 @@ import {
9
9
  readExtensionMeta,
10
10
  resolveExtension,
11
11
  slugify
12
- } from "./index-38e18a7j.js";
12
+ } from "./index-thmwa4f5.js";
13
13
  import {
14
14
  BuildError,
15
15
  EXIT_CODES,
16
16
  getAssetsDir
17
- } from "./index-dhknsvfz.js";
17
+ } from "./index-jsz0s3dy.js";
18
18
  import {
19
19
  PLUGINS_DIR,
20
20
  VENDORED_NPM_DIR,
@@ -183,7 +183,7 @@ var DTRPG_PRESET = {
183
183
  bleed: 9,
184
184
  safe: 36,
185
185
  artSafe: 18,
186
- gutter: undefined
186
+ gutter: 36
187
187
  },
188
188
  ink: {
189
189
  maxTac: 240,
package/dist/index.js CHANGED
@@ -116,7 +116,7 @@ import {
116
116
  substituteVariables,
117
117
  syncProject,
118
118
  testRemoteAccess
119
- } from "./index-tkjvs1gf.js";
119
+ } from "./index-p1a596m0.js";
120
120
  import {
121
121
  BUILT_IN_STYLE_SET_IDS,
122
122
  BUNDLED_EXTENSIONS,
@@ -154,7 +154,7 @@ import {
154
154
  setExtensionEnabled,
155
155
  updateExtensions,
156
156
  validateProjectExtensions
157
- } from "./index-dm7hx46j.js";
157
+ } from "./index-xe83m9mc.js";
158
158
  import {
159
159
  EXTENSION_MANIFEST_FILENAME,
160
160
  assertExtensionContained,
@@ -165,16 +165,16 @@ import {
165
165
  pathEscapesFolder,
166
166
  readExtensionMeta,
167
167
  resolveExtension
168
- } from "./index-38e18a7j.js";
168
+ } from "./index-thmwa4f5.js";
169
169
  import"./index-ht1bcqdd.js";
170
170
  import {
171
171
  BUILD_DIAGNOSTIC_CODES
172
- } from "./index-gmfh6n05.js";
173
- import"./index-5h9a38jn.js";
172
+ } from "./index-2d8j0enc.js";
173
+ import"./index-apc8w9w8.js";
174
174
  import {
175
175
  BuildError
176
- } from "./index-dhknsvfz.js";
177
- import"./index-sth219v1.js";
176
+ } from "./index-jsz0s3dy.js";
177
+ import"./index-2ppw6q9v.js";
178
178
  import {
179
179
  DEFAULT_PRINT_OPTS,
180
180
  assertMilestone,
@@ -132,8 +132,9 @@ export interface AssembleBookHtmlOptions {
132
132
  * preset id and printer spec — written as `<meta name="gp-print-area">`
133
133
  * (engine/shared/print-area.ts) into EVERY assembled book, preview, build
134
134
  * and publish alike: the preview's print overlays judge the ink against it,
135
- * and both engines place pinned art on its art-safe line. Metadata only:
136
- * nothing renders from it but what reads it.
135
+ * and both engines place pinned art on its art-safe line. Metadata only,
136
+ * with one exception: its `width` x `height` is also the page size the book
137
+ * prints at when no stylesheet sets one — see {@link defaultPageSizeCss}.
137
138
  */
138
139
  printArea?: PrintAreaSource;
139
140
  plugins?: LoadedPlugin[];
@@ -1,5 +1,7 @@
1
1
  import type { Document } from "yaml";
2
2
  import { type PresetId } from "./presets.ts";
3
+ import { formatPageSize, isPositivePoints } from "../engine/shared/print-area.ts";
4
+ export { formatPageSize, isPositivePoints };
3
5
  /** Page bounds written to the manifest's `page:` block (points; 72pt = 1in). */
4
6
  export interface CustomPageOptions {
5
7
  /** Trim width in points (72pt = 1in). */
@@ -28,12 +30,11 @@ export interface PageSetup {
28
30
  bounds: (PageSizePoints & {
29
31
  tolerance: number;
30
32
  }) | null;
31
- /** What the book's stylesheets print at, or null when no plain `@page` sets a size. */
33
+ /** What the book's stylesheets print at, or null when no plain `@page` sets a size (the book then prints at `bounds`). */
32
34
  css: (PageSizePoints & {
33
35
  file: string;
34
36
  }) | null;
35
37
  }
36
- export declare function isPositivePoints(value: unknown): value is number;
37
38
  /**
38
39
  * Write the manifest's `page:` bounds. `tolerance` defaults to whatever the
39
40
  * block already carries, so re-sizing a book never silently resets a
@@ -43,8 +44,6 @@ export declare function isPositivePoints(value: unknown): value is number;
43
44
  export declare function writePageBounds(doc: Document, page: CustomPageOptions): void;
44
45
  /** The size (points) a choice means: `custom` brings its own, the others are the preset's. */
45
46
  export declare function pageSizeForChoice(choice: PageSizeChoice): PageSizePoints;
46
- /** The `size` value written for a page: `8.5in 11in`, `595pt 842pt`. */
47
- export declare function formatPageSize(page: PageSizePoints): string;
48
47
  /** The size the LAST plain `@page { size }` in the stylesheet sets (cascade: last wins), or null. */
49
48
  export declare function readPageSizeFromCss(css: string): PageSizePoints | null;
50
49
  /**
@@ -122,6 +122,18 @@ export interface ImageRef {
122
122
  */
123
123
  x: number;
124
124
  y: number;
125
+ /**
126
+ * The drawn rect in points, y up from the sheet's bottom: the unit square
127
+ * under the CTM as its axis-aligned bounds, so it holds for an image drawn
128
+ * flipped or rotated where `x`/`y`/`placedW`/`placedH` do not. The
129
+ * `pdf.print.safe-margins` check judges it against the art rule.
130
+ */
131
+ bounds: {
132
+ x: number;
133
+ y: number;
134
+ w: number;
135
+ h: number;
136
+ };
125
137
  /** 1-based page number. */
126
138
  page: number;
127
139
  }
@@ -64,9 +64,9 @@ export interface CreateProjectOptions {
64
64
  /**
65
65
  * Page geometry written into the generated manifest (points; 72pt = 1in).
66
66
  * REQUIRED when `preset` is `"custom"` (it has no built-in trim); allowed
67
- * with any preset to override its trim. These are the validation bounds
68
- * the built PDF is checked against — the actual trim comes from the
69
- * stylesheet's `@page` rule, and the two should match.
67
+ * with any preset to override its trim. This is the size the book prints
68
+ * at, unless a stylesheet sets its own plain `@page { size }` (#415), and
69
+ * the size the built PDF is checked against — so the two should match.
70
70
  */
71
71
  customPage?: CustomPageOptions;
72
72
  /** Version-history mode for the new project. Defaults to `"local-git"`. */
@@ -234,13 +234,13 @@
234
234
  },
235
235
  "artSafe": {
236
236
  "type": "number",
237
- "description": "The ART rule, in points: how far inside the trim art that does not bleed (images, backgrounds, borders, rules) must stay on the top, bottom and OUTER edge; at the binding edge art uses `gutter`. For DriveThruRPG it is the \"non-bleeding art at least 0.25in from the outside edges (0.5in at the binding)\" rule. Art that runs out to the page's edge is a bleed and is not judged on that edge. The preview's overlay outlines it (dotted) and flags art outside it, and an image pinned to the top, bottom or outer edge (`.gp-pin`) sits on it unless it carries `.gp-flush`. Absent = the same as `safe`. Preset default: dtrpg 18 (0.25in); book and custom none.",
237
+ "description": "The ART rule, in points: how far inside the trim art that does not bleed (images, backgrounds, borders, rules) must stay on the top, bottom and OUTER edge; at the binding edge art uses `gutter`. For DriveThruRPG it is the \"non-bleeding art at least 0.25in from the outside edges (0.5in at the binding)\" rule. Art that runs out to the page's edge is a bleed and is not judged on that edge. The preview's overlay outlines it (dotted) and flags art outside it, the `pdf.print.safe-margins` check fails a PDF with an image closer (or one that stops in the bleed band), and an image pinned to the top, bottom or outer edge (`.gp-pin`) sits on it unless it carries `.gp-flush`. Absent = the same as `safe`. Preset default: dtrpg 18 (0.25in); book and custom none.",
238
238
  "minimum": 0,
239
239
  "examples": [18]
240
240
  },
241
241
  "gutter": {
242
242
  "type": "number",
243
- "description": "The binding allowance, in points: how far content stays from the spine, measured from the trim at the binding edge (the left edge of a right-hand page, the right edge of a left-hand one). It stands in for `safe` there, for text and for art alike: the preview's overlay draws and judges that edge against it, the `pdf.print.safe-margins` check fails text closer to the spine than it, and an image pinned to the binding edge (`.gp-pin`) sits on it. The other edges keep `safe` and `artSafe`. Use it when your book keeps its text further from the spine than the printer's minimum: the Field Guide keeps 0.75in (54) where DriveThruRPG asks for 0.5in. Absent = the same as `safe`. Presets leave it unset.",
243
+ "description": "The binding allowance, in points: how far content stays from the spine, measured from the trim at the binding edge (the left edge of a right-hand page, the right edge of a left-hand one). It stands in for `safe` there, for text and for art alike: the preview's overlay draws and judges that edge against it, the `pdf.print.safe-margins` check fails text or an image closer to the spine than it, and an image pinned to the binding edge (`.gp-pin`) sits on it. The other edges keep `safe` and `artSafe`. Use it when your book keeps its text further from the spine than the printer's minimum: the Field Guide keeps 0.75in (54) where DriveThruRPG asks for 0.5in. Absent = the same as `safe`. Preset default: dtrpg 36 (0.5in, DriveThruRPG's rule for text and non-bleeding art at the binding — the same as its `safe`, declared so a book inherits it visibly); book and custom none.",
244
244
  "minimum": 0,
245
245
  "examples": [54]
246
246
  }
@@ -3,13 +3,13 @@ import {
3
3
  EXTENSION_KINDS,
4
4
  scaffoldExtension,
5
5
  scaffoldProject
6
- } from "./cli-fbwqv7p4.js";
6
+ } from "./cli-17g57c61.js";
7
7
  import {
8
8
  resolveGhostscript
9
- } from "./cli-yejbb9f8.js";
9
+ } from "./cli-d0x3aq49.js";
10
10
  import"./cli-ht1bcqdd.js";
11
- import"./cli-8hccw62p.js";
12
- import"./cli-sth219v1.js";
11
+ import"./cli-9pk65fqf.js";
12
+ import"./cli-2ppw6q9v.js";
13
13
  import"./cli-eyyggect.js";
14
14
  import {
15
15
  isToolAvailable
@@ -21,16 +21,16 @@ import {
21
21
  PRESET_IDS,
22
22
  TARGETS,
23
23
  TARGET_IDS
24
- } from "./cli-9vy4b6ec.js";
25
- import"./cli-pfrg1r9x.js";
26
- import"./cli-5553w972.js";
24
+ } from "./cli-sge5cp21.js";
25
+ import"./cli-0s8sy5n9.js";
26
+ import"./cli-3p7jejyd.js";
27
27
  import {
28
28
  UsageError,
29
29
  rejectExtraPositionals,
30
30
  rejectUnknownFlags
31
31
  } from "./cli-d8xjns66.js";
32
- import"./cli-gc9av5hs.js";
33
- import"./cli-gmm3bdeq.js";
32
+ import"./cli-nqvrp9wk.js";
33
+ import"./cli-hndkg7bn.js";
34
34
  import {
35
35
  EXIT_CODES
36
36
  } from "./cli-46ycxe6r.js";
@@ -14,7 +14,7 @@ import {
14
14
  pendingGraceDestroyCount,
15
15
  retainPdfCache,
16
16
  unsplittableTextReasons
17
- } from "./cli-sth219v1.js";
17
+ } from "./cli-2ppw6q9v.js";
18
18
  import"./cli-37x76zdn.js";
19
19
  export {
20
20
  unsplittableTextReasons,
@@ -8,7 +8,7 @@ import {
8
8
  loadPlugins,
9
9
  loadPluginsWithCss,
10
10
  loadPublishFunction
11
- } from "./index-38e18a7j.js";
11
+ } from "./index-thmwa4f5.js";
12
12
  import"./index-r7jcsk6h.js";
13
13
  import"./index-37x76zdn.js";
14
14
  export {
@@ -8,9 +8,9 @@ import {
8
8
  loadPlugins,
9
9
  loadPluginsWithCss,
10
10
  loadPublishFunction
11
- } from "./cli-pfrg1r9x.js";
12
- import"./cli-5553w972.js";
13
- import"./cli-gc9av5hs.js";
11
+ } from "./cli-0s8sy5n9.js";
12
+ import"./cli-3p7jejyd.js";
13
+ import"./cli-nqvrp9wk.js";
14
14
  import"./cli-h3phdstf.js";
15
15
  import"./cli-37x76zdn.js";
16
16
  export {