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
@@ -6,20 +6,22 @@ import {
6
6
  defaultConfigDir,
7
7
  error,
8
8
  findPostScriptShadingPages,
9
- hasLiveTransparency,
9
+ flattenTransparentPages,
10
10
  info,
11
11
  log,
12
+ planTransparentPages,
12
13
  resolveGhostscript,
13
14
  setLogLevel,
14
15
  stampCreator,
15
16
  stripAnnotations,
17
+ transparentPages,
16
18
  warn
17
- } from "./cli-gh0ym80r.js";
19
+ } from "./cli-h4tfmpg3.js";
18
20
  import {
19
21
  DEBOUNCE,
20
22
  UsageError,
21
23
  resolvePort
22
- } from "./cli-7z29z4wz.js";
24
+ } from "./cli-fmksazej.js";
23
25
  import {
24
26
  blackTextToGray
25
27
  } from "./cli-ht1bcqdd.js";
@@ -28,11 +30,24 @@ import {
28
30
  getAssetPath,
29
31
  getAssetsDir,
30
32
  lastPageMode
31
- } from "./cli-j802s31j.js";
33
+ } from "./cli-61vd4ews.js";
32
34
  import {
33
35
  BuildError,
34
36
  EXIT_CODES
35
37
  } from "./cli-46ycxe6r.js";
38
+ import {
39
+ countLinkAnnotations,
40
+ getImageResolutions,
41
+ getOpPass,
42
+ getOutlineCount,
43
+ getPageLabels,
44
+ getPageSize,
45
+ getTextPass,
46
+ isLoadable,
47
+ loadPdf,
48
+ pageHasMarks,
49
+ retainPdfCache
50
+ } from "./cli-sth219v1.js";
36
51
  import {
37
52
  INSTALL_HINTS,
38
53
  execCapture,
@@ -122,6 +137,8 @@ var DTRPG_PRESET = {
122
137
  icc: "profiles/CGATS21_CRPC1.icc",
123
138
  stripAnnotations: true,
124
139
  blackText: "k-only",
140
+ transparency: "flatten-page",
141
+ flattenPages: [],
125
142
  rasterDpi: undefined
126
143
  },
127
144
  page: {
@@ -207,6 +224,8 @@ var BOOK_PRESET = {
207
224
  icc: "profiles/CGATS21_CRPC1.icc",
208
225
  stripAnnotations: true,
209
226
  blackText: "icc",
227
+ transparency: "flatten-page",
228
+ flattenPages: [],
210
229
  rasterDpi: undefined
211
230
  },
212
231
  page: {
@@ -606,6 +625,14 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
606
625
  if (lastPage !== undefined && lastPage !== "blank" && lastPage !== "short-by-one") {
607
626
  throw new UsageError(`Manifest field \`print.lastPage\` must be "blank" or "short-by-one", got ${JSON.stringify(lastPage)}.`);
608
627
  }
628
+ const transparency = c.pdfx?.transparency ?? m.pdfx?.transparency;
629
+ if (transparency !== undefined && transparency !== "flatten-page" && transparency !== "preserve-text") {
630
+ throw new UsageError(`Manifest field \`pdfx.transparency\` must be "flatten-page" or "preserve-text", got ${JSON.stringify(transparency)}.`);
631
+ }
632
+ const flattenPages = c.pdfx?.flattenPages ?? m.pdfx?.flattenPages;
633
+ if (flattenPages !== undefined && !(Array.isArray(flattenPages) && flattenPages.every((n) => Number.isInteger(n) && n >= 1))) {
634
+ throw new UsageError(`Manifest field \`pdfx.flattenPages\` must be a list of page numbers (1, 2, …), got ${JSON.stringify(flattenPages)}.`);
635
+ }
609
636
  const rasterDpi = c.pdfx?.rasterDpi ?? m.pdfx?.rasterDpi;
610
637
  if (rasterDpi !== undefined && !(Number.isInteger(rasterDpi) && rasterDpi >= 72 && rasterDpi <= 1200)) {
611
638
  throw new UsageError(`Manifest field \`pdfx.rasterDpi\` must be a whole number from 72 to 1200, got ${JSON.stringify(rasterDpi)}.`);
@@ -674,6 +701,7 @@ function resolveDeclaredStyles(rawStyles, baseDir, subject) {
674
701
  // src/lib/extension-manifest.ts
675
702
  import { readFile as readFile2 } from "node:fs/promises";
676
703
  import path from "node:path";
704
+ import { satisfies, validRange } from "semver";
677
705
  var EXTENSION_MANIFEST_FILENAME = "package.json";
678
706
  var GUTTERPRESS_KEY = "gutterpress";
679
707
  function object(value) {
@@ -697,6 +725,7 @@ function metaFromPackageJson(raw) {
697
725
  ...str(pkg.description) ? { description: str(pkg.description) } : {},
698
726
  ...keywords ? { keywords } : {},
699
727
  ...str(pkg.main) ? { main: str(pkg.main) } : {},
728
+ ...str(object(pkg.engines)?.gutterpress) ? { gutterpressRange: str(object(pkg.engines)?.gutterpress) } : {},
700
729
  ..."preview" in gp ? { preview: str(gp.preview) ?? null } : {},
701
730
  ...Array.isArray(gp.styles) ? { styles: declaredList(gp.styles) } : {},
702
731
  ...str(gp.tokensFile) ? { tokensFile: str(gp.tokensFile) } : {},
@@ -733,6 +762,17 @@ function extensionCarries(meta) {
733
762
  function pathEscapesFolder(rel) {
734
763
  return path.isAbsolute(rel) || rel.split(/[\\/]/).includes("..");
735
764
  }
765
+ function assertSupportsThisGutterpress(meta, ref) {
766
+ const range = meta.gutterpressRange;
767
+ if (range === undefined)
768
+ return;
769
+ if (!validRange(range)) {
770
+ throw new Error(`Extension "${ref}" has an invalid engines.gutterpress range "${range}".`);
771
+ }
772
+ if (!satisfies(PACKAGE_VERSION, range, { includePrerelease: true })) {
773
+ throw new Error(`Extension "${ref}" needs Gutterpress ${range} (its package.json engines.gutterpress); ` + `this is Gutterpress ${PACKAGE_VERSION}. Update Gutterpress, or pin an older version of the extension.`);
774
+ }
775
+ }
736
776
  function assertExtensionContained(meta) {
737
777
  if (meta.engineStyles !== undefined) {
738
778
  throw new Error("The extension's metadata declares `engineStyles`, which was removed — " + "move its entries to the end of `styles`.");
@@ -2004,7 +2044,9 @@ var GUTTERPRESS_CSS = `
2004
2044
  cap at equal specificity */
2005
2045
  .gp-small { width: 25%; max-width: 100%; }
2006
2046
  .gp-medium { width: 50%; max-width: 100%; }
2007
- .gp-large { width: 75%; max-width: 100%; }
2047
+ .gp-large { width: 60%; max-width: 100%; }
2048
+ .gp-xl { width: 80%; max-width: 100%; }
2049
+ .gp-xxl { width: 90%; max-width: 100%; }
2008
2050
 
2009
2051
  /* float clearance presets — consumed by var(--gp-gap) in the float rules
2010
2052
  above and by .gp-shape's shape-margin below; --gp-gap itself is
@@ -2062,10 +2104,16 @@ img.gp-shape {
2062
2104
  }
2063
2105
 
2064
2106
  /* pin — within the nearest positioned ancestor (.page/.spread, rule above).
2065
- inset:0 and the explicit centers are load-bearing; see doctrine comment. */
2107
+ The four insets are the pin box: 0 (the text block's edge) unless an
2108
+ engine set --gp-pin-<edge> on the page root for an edge a pin uses (the
2109
+ art-safe line). Non-auto insets and the explicit centers are
2110
+ load-bearing; see doctrine comment. */
2066
2111
  .gp-pin {
2067
2112
  position: absolute;
2068
- inset: 0;
2113
+ top: var(--gp-pin-top, 0px);
2114
+ right: var(--gp-pin-right, 0px);
2115
+ bottom: var(--gp-pin-bottom, 0px);
2116
+ left: var(--gp-pin-left, 0px);
2069
2117
  align-self: center;
2070
2118
  justify-self: center;
2071
2119
  margin: 0;
@@ -2080,6 +2128,16 @@ img.gp-shape {
2080
2128
  .gp-left { justify-self: start; }
2081
2129
  .gp-right { justify-self: end; }
2082
2130
 
2131
+ /* the escape hatch — the paper's edge, past the printer's safe area: the
2132
+ pin box loses its art-safe insets. Set on the pin itself, so it beats the
2133
+ values the page root hands down. */
2134
+ .gp-pin.gp-flush {
2135
+ --gp-pin-top: 0px;
2136
+ --gp-pin-right: 0px;
2137
+ --gp-pin-bottom: 0px;
2138
+ --gp-pin-left: 0px;
2139
+ }
2140
+
2083
2141
  /* wrapper-margin neutralization (same pattern and rationale as the
2084
2142
  .gp-bleed paragraph-margin note in the doctrine comment; for pin, the
2085
2143
  emptied paragraph would otherwise leave a phantom margin gap in flow) */
@@ -2189,6 +2247,8 @@ var GP_CLASSES = new Set([
2189
2247
  "gp-small",
2190
2248
  "gp-medium",
2191
2249
  "gp-large",
2250
+ "gp-xl",
2251
+ "gp-xxl",
2192
2252
  "gp-tight",
2193
2253
  "gp-loose",
2194
2254
  "gp-columns",
@@ -2205,6 +2265,7 @@ var GP_CLASSES = new Set([
2205
2265
  "gp-pin",
2206
2266
  "gp-top",
2207
2267
  "gp-bottom",
2268
+ "gp-flush",
2208
2269
  "gp-behind",
2209
2270
  "gp-base",
2210
2271
  "gp-raised",
@@ -2218,8 +2279,7 @@ var GP_CLASSES = new Set([
2218
2279
  "gp-alert-caution",
2219
2280
  "gp-page-break",
2220
2281
  "gp-column-break",
2221
- "gp-continued",
2222
- "gp-flush"
2282
+ "gp-continued"
2223
2283
  ]);
2224
2284
 
2225
2285
  // src/lib/markdown/gp-pin-scope.js
@@ -2256,6 +2316,21 @@ function hasPinClass(token) {
2256
2316
  const cls = token.attrGet && token.attrGet("class");
2257
2317
  return typeof cls === "string" && cls.split(/\s+/).includes("gp-pin");
2258
2318
  }
2319
+ var PIN_EDGES = ["gp-top", "gp-right", "gp-bottom", "gp-left"];
2320
+ 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.";
2321
+ function checkFlushArt(token, env, line) {
2322
+ if (token.type !== "image")
2323
+ return;
2324
+ const cls = token.attrGet && token.attrGet("class");
2325
+ if (typeof cls !== "string")
2326
+ return;
2327
+ const classes = cls.split(/\s+/);
2328
+ if (!classes.includes("gp-pin") || !classes.includes("gp-flush"))
2329
+ return;
2330
+ if (!classes.some((c) => PIN_EDGES.includes(c)))
2331
+ return;
2332
+ warn3(env, line, "flush_art", FLUSH_ART_MSG);
2333
+ }
2259
2334
  function elementNameFor(token) {
2260
2335
  switch (token.type) {
2261
2336
  case "layout_chapter_open":
@@ -2315,6 +2390,7 @@ function gpPinScope(md) {
2315
2390
  if (token.type === "inline" && token.children) {
2316
2391
  for (const child of token.children) {
2317
2392
  checkUnknownGpClasses(child, state.env, line);
2393
+ checkFlushArt(child, state.env, line);
2318
2394
  }
2319
2395
  }
2320
2396
  }
@@ -3342,6 +3418,7 @@ This folder still has a ${stale}. Gutterpress no longer reads it — ` + `descri
3342
3418
  async function loadExtensionFromDir(extensionDir, config, pluginRef) {
3343
3419
  const meta = await readExtensionMeta(extensionDir);
3344
3420
  assertExtensionContained(meta);
3421
+ assertSupportsThisGutterpress(meta, pluginRef);
3345
3422
  const resolved = resolveExtension(extensionDir, meta, `Plugin "${pluginRef}"`);
3346
3423
  const extensionStyles = resolved.styles ?? [];
3347
3424
  const name = config.name ?? meta.name ?? pluginRef;
@@ -3416,6 +3493,7 @@ async function loadPlugin(config, baseDir) {
3416
3493
  if (loaded.packageDir) {
3417
3494
  const meta = await readExtensionMeta(loaded.packageDir);
3418
3495
  assertExtensionContained(meta);
3496
+ assertSupportsThisGutterpress(meta, pluginRef);
3419
3497
  const resolved = resolveExtension(loaded.packageDir, { ...meta, main: undefined }, `Plugin "${pluginRef}"`);
3420
3498
  if (resolved.markdown && loaded.entryPath && resolve3(resolved.markdown) !== resolve3(loaded.entryPath)) {
3421
3499
  throw new Error(`its package.json declares gutterpress.markdown "${meta.markdown}" but the package entry is "${relative(loaded.packageDir, loaded.entryPath)}" — an npm extension's ` + "markdown-it plugin is its package entry, so declare that file or drop the field.");
@@ -3539,7 +3617,7 @@ import path3 from "node:path";
3539
3617
  import { Transform } from "node:stream";
3540
3618
  import { pipeline } from "node:stream/promises";
3541
3619
  import { createGunzip } from "node:zlib";
3542
- import { maxSatisfying, satisfies, validRange } from "semver";
3620
+ import { maxSatisfying, satisfies as satisfies2, validRange as validRange2 } from "semver";
3543
3621
 
3544
3622
  // src/lib/fetch-timeout.ts
3545
3623
  class FetchPassthroughError extends Error {
@@ -3780,7 +3858,7 @@ async function selectPackage(ctx, name, selector) {
3780
3858
  version = selector;
3781
3859
  } else if (typeof tags[selector] === "string") {
3782
3860
  version = tags[selector];
3783
- } else if (validRange(selector)) {
3861
+ } else if (validRange2(selector)) {
3784
3862
  version = maxSatisfying(Object.keys(versions), selector) ?? undefined;
3785
3863
  }
3786
3864
  if (!version || !isExactNpmVersion(version)) {
@@ -3998,7 +4076,7 @@ async function installPackage(ctx, name, selector, parentPackagePath, ancestors,
3998
4076
  }
3999
4077
  throw error2;
4000
4078
  }
4001
- const ancestor = ancestors.find((candidate) => candidate.name === name && (candidate.version === selected.version || (selector ? satisfies(candidate.version, selector) : false)));
4079
+ const ancestor = ancestors.find((candidate) => candidate.name === name && (candidate.version === selected.version || (selector ? satisfies2(candidate.version, selector) : false)));
4002
4080
  if (ancestor)
4003
4081
  return { path: ancestor.path };
4004
4082
  if (ctx.records.length >= ctx.limits.totalPackages) {
@@ -4899,346 +4977,6 @@ function inspectionFailed(checkId, message, extra = {}) {
4899
4977
  };
4900
4978
  }
4901
4979
 
4902
- // src/lib/pdf-inspect.ts
4903
- import { stat as stat2, readFile as readFile7 } from "node:fs/promises";
4904
- import { getDocumentProxy } from "unpdf";
4905
- import * as pdfjs from "unpdf/pdfjs";
4906
- function getOps(mod) {
4907
- if ("OPS" in mod && mod.OPS && typeof mod.OPS === "object") {
4908
- return mod.OPS;
4909
- }
4910
- throw new Error("unpdf/pdfjs missing OPS export");
4911
- }
4912
- var OPS = getOps(pdfjs);
4913
- var docCache = new Map;
4914
- var DOC_CACHE_MAX = 8;
4915
- function destroyEntry(entry) {
4916
- entry.doc.then((d) => d.destroy()).catch(() => {});
4917
- }
4918
- var EVICT_DESTROY_GRACE_MS = 60000;
4919
- var gracePending = new Map;
4920
- function destroyEntryAfterGrace(entry) {
4921
- const t = setTimeout(() => {
4922
- gracePending.delete(t);
4923
- destroyEntry(entry);
4924
- }, EVICT_DESTROY_GRACE_MS);
4925
- t.unref?.();
4926
- gracePending.set(t, entry);
4927
- }
4928
- function evictLru() {
4929
- while (docCache.size > DOC_CACHE_MAX) {
4930
- const oldestKey = docCache.keys().next().value;
4931
- if (oldestKey === undefined)
4932
- break;
4933
- const evicted = docCache.get(oldestKey);
4934
- docCache.delete(oldestKey);
4935
- if (evicted)
4936
- destroyEntryAfterGrace(evicted);
4937
- }
4938
- }
4939
- async function loadPdf(path6) {
4940
- let st;
4941
- try {
4942
- st = await stat2(path6);
4943
- } catch {
4944
- return null;
4945
- }
4946
- const hit = docCache.get(path6);
4947
- if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
4948
- docCache.delete(path6);
4949
- docCache.set(path6, hit);
4950
- try {
4951
- return await hit.doc;
4952
- } catch {
4953
- return null;
4954
- }
4955
- }
4956
- if (hit)
4957
- destroyEntry(hit);
4958
- const docPromise = readFile7(path6).then((buf) => getDocumentProxy(new Uint8Array(buf)));
4959
- docCache.delete(path6);
4960
- docCache.set(path6, { mtimeMs: st.mtimeMs, size: st.size, doc: docPromise });
4961
- evictLru();
4962
- try {
4963
- return await docPromise;
4964
- } catch {
4965
- if (docCache.get(path6)?.doc === docPromise)
4966
- docCache.delete(path6);
4967
- return null;
4968
- }
4969
- }
4970
- var activeRetains = 0;
4971
- function retainPdfCache() {
4972
- activeRetains++;
4973
- let released = false;
4974
- return () => {
4975
- if (released)
4976
- return;
4977
- released = true;
4978
- activeRetains--;
4979
- if (activeRetains === 0)
4980
- clearPdfCache();
4981
- };
4982
- }
4983
- function clearPdfCache() {
4984
- for (const [t, entry] of gracePending) {
4985
- clearTimeout(t);
4986
- destroyEntry(entry);
4987
- }
4988
- gracePending.clear();
4989
- for (const entry of docCache.values())
4990
- destroyEntry(entry);
4991
- docCache.clear();
4992
- }
4993
- function getPageSize(page) {
4994
- const vp = page.getViewport({ scale: 1 });
4995
- return { w: vp.width, h: vp.height };
4996
- }
4997
- async function getOutlineCount(doc) {
4998
- try {
4999
- const outline = await doc.getOutline();
5000
- return outline ? outline.length : 0;
5001
- } catch {
5002
- return 0;
5003
- }
5004
- }
5005
- async function getPageLabels(doc) {
5006
- try {
5007
- return await doc.getPageLabels();
5008
- } catch {
5009
- return null;
5010
- }
5011
- }
5012
- async function countLinkAnnotations(doc, stopAtFirst = false) {
5013
- let count = 0;
5014
- for (let i = 1;i <= doc.numPages; i++) {
5015
- try {
5016
- const page = await doc.getPage(i);
5017
- const annots = await page.getAnnotations();
5018
- for (const a of annots) {
5019
- if (a.subtype === "Link") {
5020
- count++;
5021
- if (stopAtFirst)
5022
- return count;
5023
- }
5024
- }
5025
- } catch {}
5026
- }
5027
- return count;
5028
- }
5029
- var textCache = new WeakMap;
5030
- function getTextPass(doc) {
5031
- let p = textCache.get(doc);
5032
- if (!p) {
5033
- p = (async () => {
5034
- const textByPage = [];
5035
- const positions = [];
5036
- const runsByPage = [];
5037
- for (let i = 1;i <= doc.numPages; i++) {
5038
- try {
5039
- const page = await doc.getPage(i);
5040
- const tc = await page.getTextContent();
5041
- let pageText = "";
5042
- const runs = [];
5043
- for (const item of tc.items) {
5044
- const it = item;
5045
- if (typeof it.str !== "string")
5046
- continue;
5047
- pageText += it.str;
5048
- if (it.transform && it.str.trim().length > 0) {
5049
- const x = it.transform[4];
5050
- const y = it.transform[5];
5051
- positions.push({ x, y });
5052
- runs.push({ s: it.str, x, y, w: it.width ?? 0, h: it.height ?? 0 });
5053
- }
5054
- }
5055
- textByPage.push(pageText);
5056
- runsByPage.push(runs);
5057
- } catch {
5058
- textByPage.push("");
5059
- runsByPage.push([]);
5060
- }
5061
- }
5062
- return { textByPage, positions, runsByPage };
5063
- })();
5064
- textCache.set(doc, p);
5065
- }
5066
- return p;
5067
- }
5068
- var opCache = new WeakMap;
5069
- function multiply(m1, m2) {
5070
- return [
5071
- m1[0] * m2[0] + m1[2] * m2[1],
5072
- m1[1] * m2[0] + m1[3] * m2[1],
5073
- m1[0] * m2[2] + m1[2] * m2[3],
5074
- m1[1] * m2[2] + m1[3] * m2[3],
5075
- m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
5076
- m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
5077
- ];
5078
- }
5079
- function fontIsEmbedded(f) {
5080
- if (f.isType3Font)
5081
- return true;
5082
- if (f.missingFile === true)
5083
- return false;
5084
- if (f.isStandardFont === true)
5085
- return false;
5086
- return true;
5087
- }
5088
- function getOpPass(doc) {
5089
- let p = opCache.get(doc);
5090
- if (!p) {
5091
- p = (async () => {
5092
- const imagesByPage = new Map;
5093
- const fonts = [];
5094
- const seenFonts = new Set;
5095
- for (let i = 1;i <= doc.numPages; i++) {
5096
- let page;
5097
- try {
5098
- page = await doc.getPage(i);
5099
- } catch {
5100
- continue;
5101
- }
5102
- let ops;
5103
- try {
5104
- ops = await page.getOperatorList();
5105
- } catch {
5106
- continue;
5107
- }
5108
- let ctm = [1, 0, 0, 1, 0, 0];
5109
- const stack = [];
5110
- const pageImages = [];
5111
- for (let j = 0;j < ops.fnArray.length; j++) {
5112
- const fn = ops.fnArray[j];
5113
- const args = ops.argsArray[j];
5114
- if (fn === OPS.save) {
5115
- stack.push(ctm.slice());
5116
- } else if (fn === OPS.restore) {
5117
- ctm = stack.pop() ?? ctm;
5118
- } else if (fn === OPS.transform) {
5119
- ctm = multiply(ctm, args);
5120
- } else if (fn === OPS.paintImageXObject || fn === OPS.paintImageXObjectRepeat || fn === OPS.paintInlineImageXObject) {
5121
- const name = typeof args[0] === "string" ? args[0] : "(inline)";
5122
- pageImages.push({
5123
- name,
5124
- placedW: Math.hypot(ctm[0], ctm[1]),
5125
- placedH: Math.hypot(ctm[2], ctm[3]),
5126
- x: ctm[4],
5127
- y: ctm[5],
5128
- page: i
5129
- });
5130
- } else if (fn === OPS.setFont) {
5131
- const ref = args[0];
5132
- if (typeof ref === "string" && !seenFonts.has(ref)) {
5133
- seenFonts.add(ref);
5134
- try {
5135
- if (page.commonObjs.has(ref)) {
5136
- const f = page.commonObjs.get(ref);
5137
- fonts.push({
5138
- name: f?.name ?? ref,
5139
- embedded: fontIsEmbedded(f ?? {})
5140
- });
5141
- }
5142
- } catch {}
5143
- }
5144
- }
5145
- }
5146
- if (pageImages.length > 0)
5147
- imagesByPage.set(i, pageImages);
5148
- }
5149
- return { imagesByPage, fonts };
5150
- })();
5151
- opCache.set(doc, p);
5152
- }
5153
- return p;
5154
- }
5155
- function getImagePixelSize(page, name, timeoutMs = 2000) {
5156
- return new Promise((resolve4) => {
5157
- let done = false;
5158
- const finish = (v) => {
5159
- if (!done) {
5160
- done = true;
5161
- resolve4(v);
5162
- }
5163
- };
5164
- const t = setTimeout(() => finish(null), timeoutMs);
5165
- try {
5166
- page.objs.get(name, (obj) => {
5167
- clearTimeout(t);
5168
- finish(obj && obj.width && obj.height ? { width: obj.width, height: obj.height } : null);
5169
- });
5170
- } catch {
5171
- clearTimeout(t);
5172
- finish(null);
5173
- }
5174
- });
5175
- }
5176
- var PAINT_OPS = new Set([
5177
- "stroke",
5178
- "closeStroke",
5179
- "fill",
5180
- "eoFill",
5181
- "fillStroke",
5182
- "eoFillStroke",
5183
- "closeFillStroke",
5184
- "closeEOFillStroke",
5185
- "shadingFill",
5186
- "paintImageXObject",
5187
- "paintInlineImageXObject",
5188
- "paintImageXObjectRepeat",
5189
- "paintImageMaskXObject",
5190
- "paintImageMaskXObjectGroup",
5191
- "paintImageMaskXObjectRepeat",
5192
- "paintSolidColorImageMask",
5193
- "showText",
5194
- "showSpacedText",
5195
- "nextLineShowText",
5196
- "nextLineSetSpacingShowText"
5197
- ].map((name) => OPS[name]).filter((op) => op !== undefined));
5198
- async function pageHasMarks(doc, pageNum) {
5199
- const ops = await (await doc.getPage(pageNum)).getOperatorList();
5200
- return ops.fnArray.some((fn, i) => fn === OPS.constructPath ? PAINT_OPS.has(ops.argsArray[i][0]) : PAINT_OPS.has(fn));
5201
- }
5202
- async function getImageResolutions(doc) {
5203
- const { imagesByPage } = await getOpPass(doc);
5204
- const out = [];
5205
- for (const [pageNum, imgs] of imagesByPage) {
5206
- let page;
5207
- try {
5208
- page = await doc.getPage(pageNum);
5209
- } catch {
5210
- continue;
5211
- }
5212
- for (const img of imgs) {
5213
- if (img.name === "(inline)")
5214
- continue;
5215
- const px = await getImagePixelSize(page, img.name);
5216
- if (!px)
5217
- continue;
5218
- const placedWin = img.placedW / 72;
5219
- const placedHin = img.placedH / 72;
5220
- if (placedWin <= 0 || placedHin <= 0)
5221
- continue;
5222
- out.push({
5223
- page: pageNum,
5224
- xDpi: px.width / placedWin,
5225
- yDpi: px.height / placedHin
5226
- });
5227
- }
5228
- }
5229
- return out;
5230
- }
5231
- async function isLoadable(doc) {
5232
- try {
5233
- for (let i = 1;i <= doc.numPages; i++) {
5234
- await doc.getPage(i);
5235
- }
5236
- return true;
5237
- } catch {
5238
- return false;
5239
- }
5240
- }
5241
-
5242
4980
  // src/checks/pdf/qpdf-structure.ts
5243
4981
  var check = {
5244
4982
  id: "pdf.structure.qpdf",
@@ -5513,9 +5251,9 @@ var check4 = {
5513
5251
  registerCheck(check4);
5514
5252
 
5515
5253
  // src/lib/pdf-parse.ts
5516
- import { readFile as readFile8 } from "node:fs/promises";
5254
+ import { readFile as readFile7 } from "node:fs/promises";
5517
5255
  async function readPdfBytes(pdfPath) {
5518
- return readFile8(pdfPath, "latin1");
5256
+ return readFile7(pdfPath, "latin1");
5519
5257
  }
5520
5258
  function parseInkCov(out) {
5521
5259
  const lines = out.split(/\r?\n/).filter((l) => l.includes("CMYK"));
@@ -5776,7 +5514,7 @@ var check8 = {
5776
5514
  registerCheck(check8);
5777
5515
 
5778
5516
  // src/checks/pdf/black-text.ts
5779
- import { readFile as readFile9 } from "node:fs/promises";
5517
+ import { readFile as readFile8 } from "node:fs/promises";
5780
5518
  var check9 = {
5781
5519
  id: "pdf.print.black-text",
5782
5520
  name: "Black Text",
@@ -5789,7 +5527,7 @@ var check9 = {
5789
5527
  return [];
5790
5528
  try {
5791
5529
  const { findRichBlackText, LARGE_TEXT_PT } = await import("./black-text-gray-7dz61456.js");
5792
- const found = await findRichBlackText(await readFile9(ctx.pdfPath));
5530
+ const found = await findRichBlackText(await readFile8(ctx.pdfPath));
5793
5531
  if (found.runs === 0)
5794
5532
  return [];
5795
5533
  return [
@@ -6178,7 +5916,7 @@ var check18 = {
6178
5916
  registerCheck(check18);
6179
5917
 
6180
5918
  // src/checks/source/markdownlint.ts
6181
- import { readFile as readFile10 } from "node:fs/promises";
5919
+ import { readFile as readFile9 } from "node:fs/promises";
6182
5920
  import { lint } from "markdownlint/sync";
6183
5921
  import { parse as parseYaml2 } from "yaml";
6184
5922
 
@@ -6211,7 +5949,7 @@ function stripJsonComments(s) {
6211
5949
  return s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
6212
5950
  }
6213
5951
  async function loadConfig(configPath) {
6214
- const raw = await readFile10(configPath, "utf8");
5952
+ const raw = await readFile9(configPath, "utf8");
6215
5953
  let parsed;
6216
5954
  if (configPath.endsWith(".json")) {
6217
5955
  parsed = JSON.parse(raw);
@@ -6272,7 +6010,7 @@ registerCheck(check19);
6272
6010
 
6273
6011
  // src/checks/source/htmlhint.ts
6274
6012
  import { existsSync as existsSync7 } from "node:fs";
6275
- import { readFile as readFile11 } from "node:fs/promises";
6013
+ import { readFile as readFile10 } from "node:fs/promises";
6276
6014
  import htmlhint from "htmlhint";
6277
6015
  var { HTMLHint } = htmlhint;
6278
6016
  var CONFIG_NAMES2 = [".htmlhintrc"];
@@ -6292,8 +6030,8 @@ var check20 = {
6292
6030
  const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
6293
6031
  if (!resolvedConfig && !configPath)
6294
6032
  return [];
6295
- const ruleset = resolvedConfig ? JSON.parse(await readFile11(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
6296
- const html = await readFile11(ctx.htmlPath, "utf8");
6033
+ const ruleset = resolvedConfig ? JSON.parse(await readFile10(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
6034
+ const html = await readFile10(ctx.htmlPath, "utf8");
6297
6035
  const messages = HTMLHint.verify(html, ruleset);
6298
6036
  return messages.map((m) => ({
6299
6037
  checkId: check20.id,
@@ -6308,7 +6046,7 @@ var check20 = {
6308
6046
  registerCheck(check20);
6309
6047
 
6310
6048
  // src/checks/source/stylelint.ts
6311
- import { readFile as readFile12 } from "node:fs/promises";
6049
+ import { readFile as readFile11 } from "node:fs/promises";
6312
6050
 
6313
6051
  // src/lib/printsafe.ts
6314
6052
  import postcss from "postcss";
@@ -6650,7 +6388,7 @@ function checkCssRaw(css, from) {
6650
6388
  warnings.push({
6651
6389
  rule: ruleRiskyProps,
6652
6390
  severity: "warning",
6653
- message: `"${decl.prop}: ${decl.value}" prints blank in PDF/X exports (--format pdfx): Chromium emits repeating-*-gradient() and conic-gradient() in a form Ghostscript cannot convert. Plain PDF output is unaffected. Use linear-gradient() or radial-gradient() instead; for 0deg/90deg stripes write "linear-gradient(90deg, #b33 0 6px, #c8c5bf 6px 10px)" with "background-size: 10px 100%", and for anything else a small tiled image.`,
6391
+ message: `"${decl.prop}: ${decl.value}" is at risk in PDF/X exports (--format pdfx): Chromium emits repeating-*-gradient() and conic-gradient() in a form Ghostscript has been seen to paint as nothing or as irregular stripes, so it can print blank or distorted, unless the page also uses transparency and Ghostscript flattens the whole page to an image, which keeps the gradient but loses that page's live text. Plain PDF output is unaffected. Use linear-gradient() or radial-gradient() instead; for 0deg/90deg stripes write "linear-gradient(90deg, #b33 0 6px, #c8c5bf 6px 10px)" with "background-size: 10px 100%", and for anything else a small tiled image.`,
6654
6392
  ...nodeLoc(decl)
6655
6393
  });
6656
6394
  } else if (isUnpairedFitContentWidth(decl)) {
@@ -6715,7 +6453,7 @@ var check21 = {
6715
6453
  for (const file of files) {
6716
6454
  let css;
6717
6455
  try {
6718
- css = await readFile12(file, "utf8");
6456
+ css = await readFile11(file, "utf8");
6719
6457
  } catch (err) {
6720
6458
  results.push({
6721
6459
  checkId: check21.id,
@@ -6742,7 +6480,7 @@ var check21 = {
6742
6480
  registerCheck(check21);
6743
6481
 
6744
6482
  // src/checks/source/css-ownership.ts
6745
- import { readFile as readFile13 } from "node:fs/promises";
6483
+ import { readFile as readFile12 } from "node:fs/promises";
6746
6484
 
6747
6485
  // src/lib/style-resolver.ts
6748
6486
  import { existsSync as existsSync8 } from "node:fs";
@@ -6810,7 +6548,7 @@ import postcss2 from "postcss";
6810
6548
  import { parse as parseYaml3 } from "yaml";
6811
6549
  var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
6812
6550
  async function loadContract(configPath) {
6813
- const raw = await readFile13(configPath, "utf8");
6551
+ const raw = await readFile12(configPath, "utf8");
6814
6552
  const parsed = parseYaml3(raw);
6815
6553
  if (parsed && typeof parsed === "object")
6816
6554
  return parsed;
@@ -6961,7 +6699,7 @@ var check22 = {
6961
6699
  continue;
6962
6700
  let css;
6963
6701
  try {
6964
- css = await readFile13(absFile, "utf8");
6702
+ css = await readFile12(absFile, "utf8");
6965
6703
  } catch {
6966
6704
  continue;
6967
6705
  }
@@ -7008,7 +6746,7 @@ import { dirname as dirname3, resolve as resolve5 } from "node:path";
7008
6746
 
7009
6747
  // src/lib/asset-inline.ts
7010
6748
  import { createHash as createHash2 } from "node:crypto";
7011
- import { readFile as readFile14 } from "node:fs/promises";
6749
+ import { readFile as readFile13 } from "node:fs/promises";
7012
6750
  import path7 from "node:path";
7013
6751
  import postcss3 from "postcss";
7014
6752
  var FONT_EXTS = new Set([".woff2", ".woff", ".ttf", ".otf"]);
@@ -7073,7 +6811,7 @@ function mimeFor(ext) {
7073
6811
  }
7074
6812
  async function readOrThrow(absPath, kind, referencedFrom) {
7075
6813
  try {
7076
- return await readFile14(absPath);
6814
+ return await readFile13(absPath);
7077
6815
  } catch {
7078
6816
  throw new BuildError(`Missing ${kind}: ${absPath}
7079
6817
  referenced from ${referencedFrom}
@@ -7216,7 +6954,7 @@ async function collectStyleDependencies(projectDir, stylePaths) {
7216
6954
  found.add(abs);
7217
6955
  let source;
7218
6956
  try {
7219
- source = await readFile14(abs, "utf-8");
6957
+ source = await readFile13(abs, "utf-8");
7220
6958
  } catch {
7221
6959
  return;
7222
6960
  }
@@ -7293,7 +7031,7 @@ async function inlineShapeUrls(html, baseDir) {
7293
7031
  continue;
7294
7032
  let bytes;
7295
7033
  try {
7296
- bytes = await readFile14(abs);
7034
+ bytes = await readFile13(abs);
7297
7035
  } catch {
7298
7036
  continue;
7299
7037
  }
@@ -7311,7 +7049,7 @@ async function inlineShapeUrls(html, baseDir) {
7311
7049
  }
7312
7050
 
7313
7051
  // src/checks/source/local-ref-parser.ts
7314
- import { readFile as readFile15 } from "node:fs/promises";
7052
+ import { readFile as readFile14 } from "node:fs/promises";
7315
7053
  function newlineOffsets(content) {
7316
7054
  const offsets = [];
7317
7055
  for (let i = 0;i < content.length; i++)
@@ -7380,7 +7118,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
7380
7118
  for (const file of files) {
7381
7119
  let refs;
7382
7120
  try {
7383
- refs = collectRenderedLocalRefs(await readFile15(file, "utf8"));
7121
+ refs = collectRenderedLocalRefs(await readFile14(file, "utf8"));
7384
7122
  } catch {
7385
7123
  results.push(inspectionFailed(checkId, `Could not read source file: ${file}`, { file }));
7386
7124
  continue;
@@ -7731,7 +7469,7 @@ var check24 = {
7731
7469
  registerCheck(check24);
7732
7470
 
7733
7471
  // src/checks/source/accessibility-alt-text.ts
7734
- import { readFile as readFile16 } from "node:fs/promises";
7472
+ import { readFile as readFile15 } from "node:fs/promises";
7735
7473
  var check25 = {
7736
7474
  id: "source.accessibility.alt-text",
7737
7475
  name: "Image Alt Text",
@@ -7745,7 +7483,7 @@ var check25 = {
7745
7483
  const results = [];
7746
7484
  for (const file of files) {
7747
7485
  try {
7748
- const content = await readFile16(file, "utf8");
7486
+ const content = await readFile15(file, "utf8");
7749
7487
  const lines = content.split(`
7750
7488
  `);
7751
7489
  let inFence = false;
@@ -7789,7 +7527,7 @@ function extractImageAlts(line) {
7789
7527
  registerCheck(check25);
7790
7528
 
7791
7529
  // src/checks/source/accessibility-heading-order.ts
7792
- import { readFile as readFile17 } from "node:fs/promises";
7530
+ import { readFile as readFile16 } from "node:fs/promises";
7793
7531
  var check26 = {
7794
7532
  id: "source.accessibility.heading-order",
7795
7533
  name: "Heading Order",
@@ -7803,7 +7541,7 @@ var check26 = {
7803
7541
  const results = [];
7804
7542
  for (const file of files) {
7805
7543
  try {
7806
- const content = await readFile17(file, "utf8");
7544
+ const content = await readFile16(file, "utf8");
7807
7545
  const lines = content.split(`
7808
7546
  `);
7809
7547
  let inFence = false;
@@ -7845,7 +7583,7 @@ var check26 = {
7845
7583
  registerCheck(check26);
7846
7584
 
7847
7585
  // src/checks/source/layout-markers.ts
7848
- import { readFile as readFile18 } from "node:fs/promises";
7586
+ import { readFile as readFile17 } from "node:fs/promises";
7849
7587
 
7850
7588
  // src/lib/unknown-classes.ts
7851
7589
  import postcss4 from "postcss";
@@ -7948,7 +7686,7 @@ async function collectKnownClasses(ctx, plugins) {
7948
7686
  const sheets = (await collectStyleDependencies(ctx.inputDir, roots)).filter((f) => /\.css$/i.test(f));
7949
7687
  for (const sheet of sheets) {
7950
7688
  try {
7951
- addCssClasses(await readFile18(sheet, "utf8"), names);
7689
+ addCssClasses(await readFile17(sheet, "utf8"), names);
7952
7690
  } catch {}
7953
7691
  }
7954
7692
  return names;
@@ -7972,7 +7710,7 @@ var check27 = {
7972
7710
  const knownClasses = () => known ??= collectKnownClasses(ctx, plugins);
7973
7711
  for (const file of files) {
7974
7712
  try {
7975
- const content = await readFile18(file, "utf8");
7713
+ const content = await readFile17(file, "utf8");
7976
7714
  const env = {};
7977
7715
  const tokens = md.parse(content, env);
7978
7716
  md.renderer.render(tokens, md.options, env);
@@ -8012,7 +7750,7 @@ var check27 = {
8012
7750
  registerCheck(check27);
8013
7751
 
8014
7752
  // src/checks/source/merge-markers.ts
8015
- import { readFile as readFile19 } from "node:fs/promises";
7753
+ import { readFile as readFile18 } from "node:fs/promises";
8016
7754
  import path9 from "node:path";
8017
7755
 
8018
7756
  // src/checks/asset/extensions.ts
@@ -8054,7 +7792,7 @@ var check28 = {
8054
7792
  for (const file of textFiles) {
8055
7793
  let content;
8056
7794
  try {
8057
- content = await readFile19(file, "utf8");
7795
+ content = await readFile18(file, "utf8");
8058
7796
  } catch {
8059
7797
  results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
8060
7798
  continue;
@@ -8114,7 +7852,7 @@ var check28 = {
8114
7852
  registerCheck(check28);
8115
7853
 
8116
7854
  // src/checks/source/characters.ts
8117
- import { readFile as readFile20 } from "node:fs/promises";
7855
+ import { readFile as readFile19 } from "node:fs/promises";
8118
7856
 
8119
7857
  // src/checks/source/character-table.ts
8120
7858
  var CHARACTER_PATTERN = new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u00A0\\u00AD\\u00B4\\u02BC" + "\\u061C\\u2000-\\u2015\\u2018\\u2019\\u201B-\\u201D\\u201F\\u2022\\u2026" + "\\u2028-\\u202F\\u2032\\u2033\\u205F\\u2060\\u2066-\\u2069\\u2190\\u2192" + "\\u2194\\u21D2\\u2212\\u25AA\\u25E6\\u2605\\u2713\\u2714\\u2717\\u3000" + "\\uE000-\\uF8FF\\uFEFF\\uFF02\\uFF07\\uFFFD]" + "|\\p{Emoji_Presentation}|\\p{Extended_Pictographic}(?=\\uFE0F)", "gu");
@@ -8237,7 +7975,7 @@ var check29 = {
8237
7975
  for (const file of (ctx.markdownFiles ?? []).slice().sort()) {
8238
7976
  let content;
8239
7977
  try {
8240
- content = await readFile20(file, "utf8");
7978
+ content = await readFile19(file, "utf8");
8241
7979
  } catch {
8242
7980
  results.push(inspectionFailed(check29.id, `Could not read source file: ${file}`, { file }));
8243
7981
  continue;
@@ -8277,16 +8015,16 @@ var check29 = {
8277
8015
  registerCheck(check29);
8278
8016
 
8279
8017
  // src/checks/asset/image-file-size.ts
8280
- import { stat as stat4 } from "node:fs/promises";
8018
+ import { stat as stat3 } from "node:fs/promises";
8281
8019
 
8282
8020
  // src/lib/image-inspect.ts
8283
- import { stat as stat3, readFile as readFile21 } from "node:fs/promises";
8021
+ import { stat as stat2, readFile as readFile20 } from "node:fs/promises";
8284
8022
  var DEFAULT_DPI = 72;
8285
8023
  var cache = new Map;
8286
8024
  async function inspectImage(path10) {
8287
8025
  let st;
8288
8026
  try {
8289
- st = await stat3(path10);
8027
+ st = await stat2(path10);
8290
8028
  } catch {
8291
8029
  return null;
8292
8030
  }
@@ -8295,7 +8033,7 @@ async function inspectImage(path10) {
8295
8033
  return hit.info;
8296
8034
  let info3 = null;
8297
8035
  try {
8298
- const buf = await readFile21(path10);
8036
+ const buf = await readFile20(path10);
8299
8037
  info3 = parseImage(buf);
8300
8038
  } catch {
8301
8039
  info3 = null;
@@ -8517,7 +8255,7 @@ var check30 = {
8517
8255
  const results = [];
8518
8256
  for (const file of files) {
8519
8257
  try {
8520
- const info3 = await stat4(file);
8258
+ const info3 = await stat3(file);
8521
8259
  if (info3.size > maxSize) {
8522
8260
  const sizeMb = (info3.size / 1e6).toFixed(1);
8523
8261
  const maxMb = (maxSize / 1e6).toFixed(1);
@@ -8855,7 +8593,7 @@ var check37 = {
8855
8593
  registerCheck(check37);
8856
8594
 
8857
8595
  // src/checks/heuristic/section-density.ts
8858
- import { readFile as readFile22 } from "node:fs/promises";
8596
+ import { readFile as readFile21 } from "node:fs/promises";
8859
8597
  var check38 = {
8860
8598
  id: "heuristic.chunking.section-density",
8861
8599
  name: "Section Density",
@@ -8872,7 +8610,7 @@ var check38 = {
8872
8610
  const results = [];
8873
8611
  for (const file of files) {
8874
8612
  try {
8875
- const content = await readFile22(file, "utf8");
8613
+ const content = await readFile21(file, "utf8");
8876
8614
  const lines = content.split(`
8877
8615
  `);
8878
8616
  let currentSectionStart = 0;
@@ -9176,7 +8914,7 @@ async function restoreForCommand(dir, options) {
9176
8914
  }
9177
8915
 
9178
8916
  // src/lib/markdown/index.ts
9179
- import { readdir as readdir3, readFile as readFile23, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
8917
+ import { readdir as readdir3, readFile as readFile22, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
9180
8918
 
9181
8919
  // src/lib/remote-auth/sync-messages.ts
9182
8920
  function isOnlineSibling(filepath) {
@@ -9198,6 +8936,20 @@ function canonicalChapterId(p) {
9198
8936
  return s;
9199
8937
  }
9200
8938
 
8939
+ // src/engine/shared/print-area.ts
8940
+ function printAreaMeta(page) {
8941
+ const area = JSON.stringify({
8942
+ width: page.width,
8943
+ height: page.height,
8944
+ bleed: page.bleed ?? 0,
8945
+ safe: page.safe ?? 0,
8946
+ artSafe: page.artSafe,
8947
+ preset: page.preset,
8948
+ spec: page.spec
8949
+ });
8950
+ return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
8951
+ }
8952
+
9201
8953
  // src/lib/markdown/assemble.ts
9202
8954
  async function assembleBookHtml(opts) {
9203
8955
  const title = opts.title ?? "Document";
@@ -9263,7 +9015,8 @@ ${projectCss.trim()}` : null
9263
9015
  <html lang="en"${printAttrs}>
9264
9016
  <head>
9265
9017
  <meta charset="UTF-8">
9266
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
9018
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">${opts.printArea ? `
9019
+ ${printAreaMeta(opts.printArea)}` : ""}
9267
9020
  <title>${title}</title>${preloadTags}
9268
9021
  <style data-project-css>
9269
9022
  ${inlineCss}
@@ -9318,11 +9071,12 @@ ${css.trim()}
9318
9071
  }
9319
9072
  return assembleBookHtml({
9320
9073
  files,
9321
- readText: (relPath) => readFile23(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
9074
+ readText: (relPath) => readFile22(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
9322
9075
  projectCss: inlined.css,
9323
9076
  preloadImages: cssAssetCopies.map((c) => c.to),
9324
9077
  title: opts.title,
9325
9078
  print: opts.print,
9079
+ printArea: opts.printArea,
9326
9080
  plugins: opts.plugins,
9327
9081
  pluginCss,
9328
9082
  annotateSourceChapters: opts.annotateSourceChapters,
@@ -9335,6 +9089,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
9335
9089
  const html = await renderChapters(inputDir, {
9336
9090
  title: opts.title,
9337
9091
  print: opts.print,
9092
+ printArea: opts.printArea,
9338
9093
  styles: opts.styles,
9339
9094
  files: opts.files,
9340
9095
  plugins: opts.plugins,
@@ -9350,7 +9105,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
9350
9105
  }
9351
9106
 
9352
9107
  // src/lib/markdownlint-fix.ts
9353
- import { readFile as readFile24, writeFile as writeFile3 } from "node:fs/promises";
9108
+ import { readFile as readFile23, writeFile as writeFile3 } from "node:fs/promises";
9354
9109
  import { applyFixes } from "markdownlint";
9355
9110
  import { lint as lint2 } from "markdownlint/sync";
9356
9111
  async function uncommittedAmong(inputDir, files) {
@@ -9390,7 +9145,7 @@ async function applyMarkdownlintFixes(ctx) {
9390
9145
  for (const [file, violations] of Object.entries(results)) {
9391
9146
  if (!Array.isArray(violations) || violations.length === 0)
9392
9147
  continue;
9393
- const before = await readFile24(file, "utf8");
9148
+ const before = await readFile23(file, "utf8");
9394
9149
  const after = applyFixes(before, violations);
9395
9150
  if (after !== before)
9396
9151
  pending.push({ file, content: after });
@@ -9903,7 +9658,7 @@ import { randomBytes } from "node:crypto";
9903
9658
 
9904
9659
  // src/lib/build-fingerprint.ts
9905
9660
  import * as fs from "node:fs";
9906
- import { mkdir as mkdir5, readFile as readFile25, writeFile as writeFile4 } from "node:fs/promises";
9661
+ import { mkdir as mkdir5, readFile as readFile24, writeFile as writeFile4 } from "node:fs/promises";
9907
9662
  import { createHash as createHash4 } from "node:crypto";
9908
9663
  import path12 from "node:path";
9909
9664
  import git from "isomorphic-git";
@@ -9990,8 +9745,8 @@ async function getEngineBundleHash() {
9990
9745
  getAssetPath("engine/gutterpress-agent.js")
9991
9746
  ]);
9992
9747
  const [viewerBytes, agentBytes] = await Promise.all([
9993
- readFile25(viewerPath),
9994
- readFile25(agentPath)
9748
+ readFile24(viewerPath),
9749
+ readFile24(agentPath)
9995
9750
  ]);
9996
9751
  const hash = createHash4("sha256");
9997
9752
  hash.update(viewerBytes);
@@ -10054,7 +9809,10 @@ async function writeBuildFingerprint(input) {
10054
9809
  resolvedFlavor: input.pdfx.resolvedFlavor,
10055
9810
  iccPath: input.pdfx.iccPath,
10056
9811
  stripAnnotations: input.pdfx.stripAnnotations,
10057
- blackText: input.pdfx.blackText ?? null
9812
+ blackText: input.pdfx.blackText ?? null,
9813
+ transparency: input.pdfx.transparency ?? null,
9814
+ flattenPages: input.pdfx.flattenPages ?? null,
9815
+ rasterDpi: input.pdfx.rasterDpi ?? null
10058
9816
  },
10059
9817
  outputDir: input.recordedOutputDir ? path12.resolve(input.recordedOutputDir) : outputDir
10060
9818
  },
@@ -10209,6 +9967,7 @@ async function renderBook(ctx) {
10209
9967
  const htmlFile = await renderChaptersToFile(renderDir, workDir, {
10210
9968
  title: config.title,
10211
9969
  print: config.print,
9970
+ printArea: { ...config.page, preset: config.preset, spec: config.spec },
10212
9971
  styles: config.styles,
10213
9972
  files: config.source.files,
10214
9973
  plugins,
@@ -10361,7 +10120,7 @@ class PdfOutput {
10361
10120
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10362
10121
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10363
10122
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10364
- const { buildNativePdf } = await import("./engine-9xb1ky8d.js");
10123
+ const { buildNativePdf } = await import("./engine-67f68bxs.js");
10365
10124
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10366
10125
  title: config.title,
10367
10126
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10383,12 +10142,40 @@ class PdfOutput {
10383
10142
  if (pdfxMode) {
10384
10143
  const icc = opts.iccPath ?? config.pdfx.icc;
10385
10144
  effectiveIccPath = await resolveIccProfile(icc, manifestDir, opts.iccPath);
10386
- if (await hasLiveTransparency(await fsp2.readFile(rawPdf))) {
10387
- log.warn("This book uses transparency — an image with an alpha channel, or a CSS " + `rule like \`opacity\` or \`mix-blend-mode\`. PDF/${pdfxMode === "x1a" ? "X-1a" : "X-3"} has no way to represent that (both are based on PDF 1.3, which predates PDF transparency), so Ghostscript will flatten every page that uses it into a single raster ` + "image — that page loses its embedded fonts and searchable text in the " + "PDF/X output. To keep vector text, remove the `opacity`/blend rule and flatten any alpha-channel image against its intended background before including it (export it with no alpha channel), or build --format pdf instead of pdfx if this book doesn't need print-ready CMYK. `pdfx.rasterDpi` sets the resolution of those page images (default 720).");
10145
+ const rawBytes = await fsp2.readFile(rawPdf);
10146
+ const transparent = await transparentPages(rawBytes);
10147
+ const blankGradientPages = await findPostScriptShadingPages(rawBytes);
10148
+ const flattenedGradientPages = blankGradientPages.filter((n) => transparent.includes(n));
10149
+ const vectorGradientPages = blankGradientPages.filter((n) => !transparent.includes(n));
10150
+ const GRADIENT_FIX = "Replace it with a plain linear-gradient() or radial-gradient(), or a small tiled image. For 0deg/90deg stripes, `linear-gradient(90deg, #b33 0 6px, #c8c5bf 6px 10px)` with `background-size: 10px 100%` converts correctly.";
10151
+ const pageList = (pages) => `Page${pages.length === 1 ? "" : "s"} ${pages.join(", ")}`;
10152
+ if (vectorGradientPages.length > 0) {
10153
+ log.warn(`${pageList(vectorGradientPages)}: a repeating-*-gradient() or conic-gradient() ` + "can print blank or distorted — " + `Ghostscript has been seen to paint nothing, or irregular stripes, for it when converting to PDF/X; check the page visually. ${GRADIENT_FIX}`);
10154
+ }
10155
+ if (flattenedGradientPages.length > 0 && config.pdfx.transparency !== "preserve-text") {
10156
+ log.warn(`${pageList(flattenedGradientPages)}: a repeating-*-gradient() or conic-gradient() is painted only as part of the one raster image Ghostscript makes of the whole page, so the page loses its embedded fonts and searchable text. Check the page visually. ${GRADIENT_FIX}`);
10157
+ }
10158
+ const flattenPages = config.pdfx.flattenPages;
10159
+ if (transparent.length > 0 && config.pdfx.transparency === "preserve-text") {
10160
+ const { unsplittableTextReasons } = await import("./pdf-inspect-zdmy7qca.js");
10161
+ const reasons = await unsplittableTextReasons(rawBytes, transparent).catch((err) => {
10162
+ 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.`);
10163
+ return null;
10164
+ });
10165
+ if (reasons) {
10166
+ const plan = planTransparentPages(transparent, reasons, flattenPages, blankGradientPages);
10167
+ await flattenTransparentPages(rawPdf, plan.split, stage, config.pdfx.rasterDpi);
10168
+ reportTransparentPlan(plan, pdfxMode, config.pdfx.rasterDpi ?? 300, pageList);
10169
+ }
10388
10170
  }
10389
- const blankGradientPages = await findPostScriptShadingPages(await fsp2.readFile(rawPdf));
10390
- if (blankGradientPages.length > 0) {
10391
- log.warn(`Page${blankGradientPages.length === 1 ? "" : "s"} ${blankGradientPages.join(", ")}: ` + "a repeating-*-gradient() or conic-gradient() will print blank — " + "Ghostscript cannot convert it to PDF/X. Replace it with a plain linear-gradient() or radial-gradient(), or a small tiled image. For 0deg/90deg stripes, `linear-gradient(90deg, #b33 0 6px, #c8c5bf 6px 10px)` with `background-size: 10px 100%` converts correctly.");
10171
+ const unused = flattenPages.filter((n) => !transparent.includes(n));
10172
+ if (flattenPages.length > 0 && config.pdfx.transparency !== "preserve-text") {
10173
+ log.warn("pdfx.flattenPages has no effect without pdfx.transparency: preserve-text.");
10174
+ } else if (unused.length > 0) {
10175
+ log.warn(`pdfx.flattenPages lists ${pageList(unused).toLowerCase()}, which ${unused.length === 1 ? "has" : "have"} no transparency (or do not exist): nothing to flatten there.`);
10176
+ }
10177
+ if (transparent.length > 0 && config.pdfx.transparency !== "preserve-text") {
10178
+ log.warn("This book uses transparency — an image with an alpha channel, or a CSS " + `rule like \`opacity\` or \`mix-blend-mode\`. PDF/${pdfxMode === "x1a" ? "X-1a" : "X-3"} has no way to represent that (both are based on PDF 1.3, which predates PDF transparency), so Ghostscript will flatten every page that uses it into a single raster ` + "image — that page loses its embedded fonts and searchable text in the " + "PDF/X output. To keep vector text, remove the `opacity`/blend rule and flatten any alpha-channel image against its intended background before including it (export it with no alpha channel), or build --format pdf instead of pdfx if this book doesn't need print-ready CMYK. Or set `pdfx.transparency: preserve-text` to keep the text live on the pages where that is possible. `pdfx.rasterDpi` sets the resolution of those page images (default 720).");
10392
10179
  }
10393
10180
  const shouldStrip = opts.stripAnnotations ?? config.pdfx.stripAnnotations;
10394
10181
  shouldStripAnnotations = shouldStrip;
@@ -10431,7 +10218,10 @@ class PdfOutput {
10431
10218
  resolvedFlavor: config.pdfx.flavor,
10432
10219
  iccPath: effectiveIccPath,
10433
10220
  stripAnnotations: shouldStripAnnotations,
10434
- blackText: pdfxMode ? config.pdfx.blackText : null
10221
+ blackText: pdfxMode ? config.pdfx.blackText : null,
10222
+ transparency: pdfxMode ? config.pdfx.transparency : null,
10223
+ flattenPages: pdfxMode ? config.pdfx.flattenPages : null,
10224
+ rasterDpi: pdfxMode ? config.pdfx.rasterDpi ?? null : null
10435
10225
  }
10436
10226
  }, `Wrote: ${reportedPdf}`, { htmlPath: htmlFile, pdfPath: reportedPdf }, pdfName, engineDiagnostics);
10437
10227
  } finally {
@@ -10468,6 +10258,35 @@ async function runBuild(opts) {
10468
10258
  await fsp2.rm(ctx.workDir, { recursive: true, force: true });
10469
10259
  }
10470
10260
  }
10261
+ var FALLBACK_CAUSE = {
10262
+ listed: "listed in pdfx.flattenPages",
10263
+ "text-transparency": "text drawn with transparency or blending (opacity, mix-blend-mode, a soft mask, a translucent text-shadow); keep that text opaque",
10264
+ "type3-font": "text in a Type 3 font: for a synthetic bold (a weight the font has no face for), use a face with that weight or set the font's own weight; for -webkit-text-stroke, remove the stroke or accept this fallback",
10265
+ "text-pattern-or-clip": 'text filled with a pattern or used as a clip (SVG fill="url(#…)", gradient text, background-clip: text)',
10266
+ gradient: "a repeating or conic gradient; use a linear or radial gradient, or a small tiled image"
10267
+ };
10268
+ function reportTransparentPlan({ split, artOnly, kept }, pdfxMode, dpi, pageList) {
10269
+ const one = (pages, singular, plural) => pages.length === 1 ? singular : plural;
10270
+ if (split.length > 0) {
10271
+ log.warn(`${pageList(split)} ${one(split, "uses", "use")} transparency, which PDF/${pdfxMode === "x1a" ? "X-1a" : "X-3"} (PDF 1.3) cannot represent. Each was flattened to a ${dpi} DPI picture with its text kept live on top (pdfx.transparency: preserve-text), so fonts and searchable text survive. On those pages, rules, backgrounds and shapes are raster, and text always sits above the artwork: text another element covers shows through. List a page in pdfx.flattenPages to flatten it whole instead.`);
10272
+ }
10273
+ if (artOnly.length > 0) {
10274
+ log.info(`${pageList(artOnly)} ${one(artOnly, "uses", "use")} transparency but ${one(artOnly, "shows", "show")} no text, so there is no text to keep: Ghostscript flattens ${one(artOnly, "it", "each")} whole (resolution: pdfx.rasterDpi).`);
10275
+ }
10276
+ if (kept.size > 0) {
10277
+ const pages = [...kept.keys()];
10278
+ const byCause = new Map;
10279
+ for (const [n, cause] of kept)
10280
+ byCause.set(cause, [...byCause.get(cause) ?? [], n]);
10281
+ const lines = [...byCause].map(([cause, ns]) => ` - ${one(ns, "page", "pages")} ${ns.join(", ")}: ${FALLBACK_CAUSE[cause]}`);
10282
+ log.warn(`${pageList(pages)} could not keep ${one(pages, "its", "their")} text live; Ghostscript will flatten ${one(pages, "that page", "each of those pages")} into one raster image, losing its embedded fonts and searchable text. Each page lists the first cause found; fixing it can reveal another:
10283
+ ` + lines.join(`
10284
+ `));
10285
+ }
10286
+ if (split.length === 0 && [...kept.values()].some((cause) => cause !== "listed")) {
10287
+ log.warn("pdfx.transparency: preserve-text kept no page's text live in this book: every page with transparency and text falls back (see above).");
10288
+ }
10289
+ }
10471
10290
 
10472
10291
  // src/lib/open-path.ts
10473
10292
  import { spawn } from "node:child_process";
@@ -10501,7 +10320,7 @@ function openPath(filePath) {
10501
10320
  import path16 from "path";
10502
10321
  import { tmpdir } from "os";
10503
10322
  import { randomBytes as randomBytes2 } from "crypto";
10504
- import { readdir as readdir4, readFile as readFile26, writeFile as writeFile5 } from "node:fs/promises";
10323
+ import { readdir as readdir4, readFile as readFile25, writeFile as writeFile5 } from "node:fs/promises";
10505
10324
 
10506
10325
  // src/utils/file-utils.ts
10507
10326
  import { promises as fs3 } from "fs";
@@ -10604,6 +10423,7 @@ async function renderPreviewBook(inputPath, config, opts) {
10604
10423
  return renderChapters(inputPath, {
10605
10424
  title: config.title ?? "Document",
10606
10425
  print: config.print,
10426
+ printArea: config.printArea,
10607
10427
  styles: config.styles,
10608
10428
  files: opts.files,
10609
10429
  plugins,
@@ -10618,19 +10438,8 @@ async function renderPreviewBook(inputPath, config, opts) {
10618
10438
  }
10619
10439
  });
10620
10440
  }
10621
- function injectPreviewScripts(html, page) {
10622
- const area = page ? JSON.stringify({
10623
- width: page.width,
10624
- height: page.height,
10625
- bleed: page.bleed ?? 0,
10626
- safe: page.safe ?? 0,
10627
- artSafe: page.artSafe,
10628
- preset: page.preset,
10629
- spec: page.spec
10630
- }) : "";
10631
- const meta = area ? ` <meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>
10632
- ` : "";
10633
- const scripts = meta + ` <script src="/engine/gutterpress-viewer.js"></script>
10441
+ function injectPreviewScripts(html) {
10442
+ const scripts = ` <script src="/engine/gutterpress-viewer.js"></script>
10634
10443
  ` + `<script src="/preview/scripts/preview-interface.js"></script>
10635
10444
  ` + `<script src="/preview/scripts/preview-bridge.js"></script>
10636
10445
  `;
@@ -10645,7 +10454,10 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
10645
10454
  }
10646
10455
  const nextAssets = new Map;
10647
10456
  const nextImageRefs = new Set;
10648
- const html = await renderPreviewBook(inputPath, config, {
10457
+ const html = await renderPreviewBook(inputPath, {
10458
+ ...config,
10459
+ printArea: config.page && { ...config.page, preset: config.preset, spec: config.spec }
10460
+ }, {
10649
10461
  files: config.source?.files ?? null,
10650
10462
  onCssAssets: (copies) => {
10651
10463
  for (const copy of copies)
@@ -10664,7 +10476,7 @@ async function generateAndWriteHtml(inputPath, tempDir, config, cssAssets, image
10664
10476
  for (const ref of nextImageRefs)
10665
10477
  imageRefs.add(ref);
10666
10478
  }
10667
- await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html, config.page && { ...config.page, preset: config.preset, spec: config.spec }), "utf-8");
10479
+ await fsp3.writeFile(path15.join(tempDir, BOOK_HTML_FILENAME), injectPreviewScripts(html), "utf-8");
10668
10480
  }
10669
10481
  function isDotPathUnderRoot(candidatePath, root) {
10670
10482
  const normalizedRoot = root.replace(/\\/g, "/").replace(/\/+$/, "");
@@ -11062,7 +10874,7 @@ async function cleanupOrphanTempDirs() {
11062
10874
  const dir = path16.join(TEMP_DIR_BASE, entry);
11063
10875
  const pidFile = path16.join(dir, PID_FILE_NAME);
11064
10876
  try {
11065
- const raw = await readFile26(pidFile, "utf8");
10877
+ const raw = await readFile25(pidFile, "utf8");
11066
10878
  const pid = parseInt(raw.trim(), 10);
11067
10879
  if (!isProcessAlive(pid)) {
11068
10880
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -11169,7 +10981,7 @@ function createServerState(inputPath, tempDir, config, options) {
11169
10981
  import http from "node:http";
11170
10982
  import net from "node:net";
11171
10983
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
11172
- import { readFile as readFile27, stat as stat5 } from "node:fs/promises";
10984
+ import { readFile as readFile26, stat as stat4 } from "node:fs/promises";
11173
10985
  import path17 from "path";
11174
10986
  import { WebSocket, WebSocketServer } from "ws";
11175
10987
  var HMR_PATH = "/__gutterpress-hmr";
@@ -11228,7 +11040,7 @@ async function findAvailablePort(startPort, host = "127.0.0.1") {
11228
11040
  async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders = {}) {
11229
11041
  let filePath = absPath;
11230
11042
  try {
11231
- const s = await stat5(filePath);
11043
+ const s = await stat4(filePath);
11232
11044
  if (s.isDirectory())
11233
11045
  filePath = path17.join(filePath, "index.html");
11234
11046
  } catch {
@@ -11238,7 +11050,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
11238
11050
  }
11239
11051
  let data;
11240
11052
  try {
11241
- data = await readFile27(filePath);
11053
+ data = await readFile26(filePath);
11242
11054
  } catch {
11243
11055
  res.writeHead(404);
11244
11056
  res.end("Not Found");
@@ -11267,7 +11079,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11267
11079
  }
11268
11080
  let fileStat;
11269
11081
  try {
11270
- fileStat = await stat5(absPath, { bigint: true });
11082
+ fileStat = await stat4(absPath, { bigint: true });
11271
11083
  } catch {
11272
11084
  await serveStatic(absPath, res);
11273
11085
  return;
@@ -11287,7 +11099,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11287
11099
  let data;
11288
11100
  if (!validator || validator.stamp !== stamp) {
11289
11101
  try {
11290
- data = await readFile27(absPath);
11102
+ data = await readFile26(absPath);
11291
11103
  } catch {
11292
11104
  await serveStatic(absPath, res);
11293
11105
  return;
@@ -11315,7 +11127,7 @@ async function isMissingReferencedImage(pathname, absPath, refs) {
11315
11127
  if (!refs.has(imageRefKey(pathname)))
11316
11128
  return false;
11317
11129
  try {
11318
- await stat5(absPath);
11130
+ await stat4(absPath);
11319
11131
  return false;
11320
11132
  } catch (err) {
11321
11133
  return err?.code === "ENOENT";
@@ -11597,7 +11409,7 @@ async function startPreviewServer(options) {
11597
11409
  }
11598
11410
 
11599
11411
  // src/lib/project-scaffold.ts
11600
- import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile28, stat as stat6, writeFile as writeFile6 } from "node:fs/promises";
11412
+ import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile27, stat as stat5, writeFile as writeFile6 } from "node:fs/promises";
11601
11413
  import { constants as FS, existsSync as existsSync12 } from "node:fs";
11602
11414
  import path18 from "node:path";
11603
11415
 
@@ -11796,7 +11608,7 @@ async function scaffoldProject(options) {
11796
11608
  async function firstSourceFile(manifestPath) {
11797
11609
  let text;
11798
11610
  try {
11799
- text = await readFile28(manifestPath, "utf8");
11611
+ text = await readFile27(manifestPath, "utf8");
11800
11612
  } catch {
11801
11613
  return;
11802
11614
  }
@@ -11808,7 +11620,7 @@ async function firstSourceFile(manifestPath) {
11808
11620
  return m ? m[1].trim() : undefined;
11809
11621
  }
11810
11622
  async function fillTemplateFile(filePath, substitutions) {
11811
- let text = await readFile28(filePath, "utf8");
11623
+ let text = await readFile27(filePath, "utf8");
11812
11624
  for (const [token, value] of Object.entries(substitutions)) {
11813
11625
  text = text.split(token).join(value);
11814
11626
  }
@@ -11816,7 +11628,7 @@ async function fillTemplateFile(filePath, substitutions) {
11816
11628
  }
11817
11629
 
11818
11630
  // src/lib/extension-scaffold.ts
11819
- import { access as access2, mkdir as mkdir8, readFile as readFile29, writeFile as writeFile7 } from "node:fs/promises";
11631
+ import { access as access2, mkdir as mkdir8, readFile as readFile28, writeFile as writeFile7 } from "node:fs/promises";
11820
11632
  import { constants as FS2 } from "node:fs";
11821
11633
  import path19 from "node:path";
11822
11634
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -11933,7 +11745,7 @@ async function scaffoldExtension(options) {
11933
11745
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
11934
11746
  const dest = path19.join(extensionDir, relDest);
11935
11747
  await mkdir8(path19.dirname(dest), { recursive: true });
11936
- const text = await readFile29(src, "utf8");
11748
+ const text = await readFile28(src, "utf8");
11937
11749
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
11938
11750
  await writeFile7(dest, substitute(text, table), "utf8");
11939
11751
  written.push(relDest);
@@ -11978,7 +11790,7 @@ var SKIP_ENTRIES = new Set([
11978
11790
 
11979
11791
  // src/lib/extension-import.ts
11980
11792
  import { existsSync as existsSync13 } from "node:fs";
11981
- import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile30, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11793
+ import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile29, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11982
11794
  import { tmpdir as tmpdir2 } from "node:os";
11983
11795
  import path20 from "node:path";
11984
11796
  import { unzipSync } from "fflate";
@@ -12051,7 +11863,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
12051
11863
  if (!existsSync13(cssPath)) {
12052
11864
  throw new Error("A theme must contain a theme.css file.");
12053
11865
  }
12054
- const css = await readFile30(cssPath, "utf8");
11866
+ const css = await readFile29(cssPath, "utf8");
12055
11867
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
12056
11868
  if (reject) {
12057
11869
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -12079,7 +11891,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
12079
11891
  for (let i = 0;i < extraSheets.length; i++) {
12080
11892
  const rel = extraSheets[i];
12081
11893
  const sheetPath = extraSheetPaths[i];
12082
- const sheetCss = await readFile30(sheetPath, "utf8");
11894
+ const sheetCss = await readFile29(sheetPath, "utf8");
12083
11895
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
12084
11896
  if (sheetReject) {
12085
11897
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -12112,7 +11924,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
12112
11924
  let pkg = {};
12113
11925
  if (metaExists) {
12114
11926
  try {
12115
- const parsed = JSON.parse(await readFile30(file, "utf8"));
11927
+ const parsed = JSON.parse(await readFile29(file, "utf8"));
12116
11928
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
12117
11929
  pkg = parsed;
12118
11930
  }
@@ -12214,11 +12026,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
12214
12026
  async function importExtensionFromFile(projectDir, filePath) {
12215
12027
  const ext = path20.extname(filePath).toLowerCase();
12216
12028
  if (ext === ".zip") {
12217
- const buf = await readFile30(filePath);
12029
+ const buf = await readFile29(filePath);
12218
12030
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
12219
12031
  }
12220
12032
  if (ext === ".css") {
12221
- const css = await readFile30(filePath, "utf8");
12033
+ const css = await readFile29(filePath, "utf8");
12222
12034
  return importExtensionFromCssText(projectDir, css, prettify(path20.basename(filePath, ".css")));
12223
12035
  }
12224
12036
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -13002,7 +12814,7 @@ var drivethrurpgProvider = {
13002
12814
  };
13003
12815
 
13004
12816
  // src/lib/publish/providers/gdrive.ts
13005
- import { mkdtemp as mkdtemp3, readFile as readFile31, readdir as readdir6, rm as rm5, stat as stat7, writeFile as writeFile10 } from "node:fs/promises";
12817
+ import { mkdtemp as mkdtemp3, readFile as readFile30, readdir as readdir6, rm as rm5, stat as stat6, writeFile as writeFile10 } from "node:fs/promises";
13006
12818
  import { tmpdir as tmpdir3 } from "node:os";
13007
12819
  import path22 from "node:path";
13008
12820
  import { Zip, ZipDeflate } from "fflate";
@@ -13394,12 +13206,12 @@ async function collectZipEntries(root, dir, out, onWarn) {
13394
13206
  await collectZipEntries(root, full, out, onWarn);
13395
13207
  } else if (entry.isFile()) {
13396
13208
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13397
- out[rel] = await readFile31(full);
13209
+ out[rel] = await readFile30(full);
13398
13210
  } else if (entry.isSymbolicLink()) {
13399
- const resolved = await stat7(full).catch(() => null);
13211
+ const resolved = await stat6(full).catch(() => null);
13400
13212
  if (resolved?.isFile()) {
13401
13213
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13402
- out[rel] = await readFile31(full);
13214
+ out[rel] = await readFile30(full);
13403
13215
  } else if (resolved?.isDirectory()) {
13404
13216
  await collectZipEntries(root, full, out, onWarn);
13405
13217
  } else {
@@ -13469,7 +13281,7 @@ var gdriveProvider = {
13469
13281
  cleanup: async () => {}
13470
13282
  };
13471
13283
  try {
13472
- const artifactStat = await stat7(source.filePath);
13284
+ const artifactStat = await stat6(source.filePath);
13473
13285
  const about = await driveAbout(fetchImpl, accessToken);
13474
13286
  if (about.quota.limitBytes != null && about.quota.freeBytes != null) {
13475
13287
  if (artifactStat.size > about.quota.freeBytes) {
@@ -13514,7 +13326,7 @@ var gdriveProvider = {
13514
13326
  };
13515
13327
 
13516
13328
  // src/lib/publish/butler.ts
13517
- import { chmod, mkdir as mkdir11, stat as stat8, writeFile as writeFile11 } from "node:fs/promises";
13329
+ import { chmod, mkdir as mkdir11, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
13518
13330
  import path23 from "node:path";
13519
13331
  import { unzipSync as unzipSync2 } from "fflate";
13520
13332
  var BUTLER_DOWNLOAD_TIMEOUT_MS = 300000;
@@ -13535,7 +13347,7 @@ function butlerBinaryName(platform = process.platform) {
13535
13347
  }
13536
13348
  async function fileExists2(p) {
13537
13349
  try {
13538
- return (await stat8(p)).isFile();
13350
+ return (await stat7(p)).isFile();
13539
13351
  } catch {
13540
13352
  return false;
13541
13353
  }
@@ -13778,7 +13590,7 @@ var kdpProvider = {
13778
13590
  };
13779
13591
 
13780
13592
  // src/lib/publish/providers/local-folder.ts
13781
- import { cp as cp4, mkdir as mkdir12, stat as stat9 } from "node:fs/promises";
13593
+ import { cp as cp4, mkdir as mkdir12, stat as stat8 } from "node:fs/promises";
13782
13594
  import path24 from "node:path";
13783
13595
  var LOCAL_PUBLISH_DEFAULT_DIR = "dist";
13784
13596
  var info8 = {
@@ -13805,7 +13617,7 @@ var localFolderProvider = {
13805
13617
  async preflight(req) {
13806
13618
  const dir = localPublishDir(req);
13807
13619
  try {
13808
- const s = await stat9(dir);
13620
+ const s = await stat8(dir);
13809
13621
  if (!s.isDirectory()) {
13810
13622
  return [
13811
13623
  { severity: "error", id: "local/not-a-folder", message: `${dir} exists but is not a folder.` }
@@ -14060,7 +13872,7 @@ function publishProviderFor(id) {
14060
13872
  }
14061
13873
 
14062
13874
  // src/lib/publish/run-publish.ts
14063
- import { readdir as readdir7, stat as stat10 } from "node:fs/promises";
13875
+ import { readdir as readdir7, stat as stat9 } from "node:fs/promises";
14064
13876
  import path25 from "node:path";
14065
13877
  function resolvePublishFormat(info10, providerConfig) {
14066
13878
  if (info10.formats && info10.formats.length > 0) {
@@ -14102,7 +13914,7 @@ var PDF_HINT = "Build the PDF first (gutterpress build, or export it from the ap
14102
13914
  var HTML_HINT = "Build the website export first (gutterpress build --format html).";
14103
13915
  async function artifactIssues(artifact) {
14104
13916
  try {
14105
- const s = await stat10(artifact.path);
13917
+ const s = await stat9(artifact.path);
14106
13918
  if (artifact.format === "pdf" && !s.isFile()) {
14107
13919
  return [
14108
13920
  {
@@ -14148,7 +13960,7 @@ async function artifactIssues(artifact) {
14148
13960
  var BUILD_FINGERPRINT_FILENAME = "build-fingerprint.json";
14149
13961
  async function htmlDirIssues(dir) {
14150
13962
  const issues = [];
14151
- const isThere = async (rel) => stat10(path25.join(dir, rel)).then(() => true, () => false);
13963
+ const isThere = async (rel) => stat9(path25.join(dir, rel)).then(() => true, () => false);
14152
13964
  if (!await isThere(BOOK_HTML)) {
14153
13965
  issues.push({
14154
13966
  severity: "error",
@@ -14386,7 +14198,7 @@ import git8 from "isomorphic-git";
14386
14198
  // src/lib/remote-auth/scorched-earth.ts
14387
14199
  import git9 from "isomorphic-git";
14388
14200
  // src/lib/publish/selections.ts
14389
- import { chmod as chmod2, mkdir as mkdir13, readFile as readFile32, writeFile as writeFile12 } from "node:fs/promises";
14201
+ import { chmod as chmod2, mkdir as mkdir13, readFile as readFile31, writeFile as writeFile12 } from "node:fs/promises";
14390
14202
  import path26 from "node:path";
14391
14203
  class PublishSelectionsStore {
14392
14204
  filePath;
@@ -14396,7 +14208,7 @@ class PublishSelectionsStore {
14396
14208
  }
14397
14209
  async read() {
14398
14210
  try {
14399
- const raw = await readFile32(this.filePath, "utf8");
14211
+ const raw = await readFile31(this.filePath, "utf8");
14400
14212
  const parsed = JSON.parse(raw);
14401
14213
  if (parsed && typeof parsed === "object") {
14402
14214
  return {