gutterpress 0.11.20-alpha.2 → 0.11.20-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 (31) hide show
  1. package/dist/api/index.js +3 -2
  2. package/dist/{build-qmjg9r2d.js → build-a8ycsg80.js} +5 -4
  3. package/dist/{cli-zacevwcy.js → cli-9k2tb493.js} +181 -431
  4. package/dist/{cli-7z29z4wz.js → cli-hn9pk0hp.js} +1 -1
  5. package/dist/cli-sth219v1.js +418 -0
  6. package/dist/{cli-j802s31j.js → cli-x4p99w8d.js} +2 -2
  7. package/dist/{cli-gh0ym80r.js → cli-y9bcya7r.js} +121 -20
  8. package/dist/cli.js +12 -12
  9. package/dist/{doctor-fe7tcs4v.js → doctor-wbjkrcpc.js} +2 -2
  10. package/dist/{engine-yax79k9m.js → engine-fdtv01a2.js} +2 -2
  11. package/dist/{engine-9xb1ky8d.js → engine-yxnnp3hx.js} +1 -1
  12. package/dist/{ext-ahwxt6gv.js → ext-pesyrs68.js} +5 -4
  13. package/dist/{gutterpress-viewer-retj6ym2.js → gutterpress-viewer-1yse8cx3.js} +12 -4
  14. package/dist/{index-6xwtb7cw.js → index-r87y7wzw.js} +1 -1
  15. package/dist/index-sth219v1.js +418 -0
  16. package/dist/{index-razpty47.js → index-tjvrchxj.js} +297 -448
  17. package/dist/{index-3nr5rzzp.js → index-zagagnwq.js} +2 -2
  18. package/dist/index.js +4 -3
  19. package/dist/lib/build-fingerprint.d.ts +8 -0
  20. package/dist/lib/ghostscript.d.ts +70 -16
  21. package/dist/lib/pdf-inspect.d.ts +45 -0
  22. package/dist/{manifest.schema-74s1ykyv.json → manifest.schema-c9x8912a.json} +13 -1
  23. package/dist/{new-ftd4fec4.js → new-8yah8z91.js} +5 -4
  24. package/dist/pdf-inspect-zdmy7qca.js +35 -0
  25. package/dist/{preflight-8sqarcsr.js → preflight-8ccx4kdc.js} +5 -4
  26. package/dist/{preview-q76xn95h.js → preview-w7v9w36q.js} +5 -4
  27. package/dist/{publish-7gdy6gbf.js → publish-g5nh5myh.js} +5 -4
  28. package/dist/schema/manifest.types.d.ts +20 -2
  29. package/dist/test-helpers/testkit.d.ts +2 -2
  30. package/dist/{validate-e3ryegce.js → validate-1yh59x0x.js} +5 -4
  31. package/package.json +1 -1
@@ -9,7 +9,20 @@ import {
9
9
  getAssetsDir,
10
10
  lastPageMode,
11
11
  parseSize
12
- } from "./index-3nr5rzzp.js";
12
+ } from "./index-zagagnwq.js";
13
+ import {
14
+ countLinkAnnotations,
15
+ getImageResolutions,
16
+ getOpPass,
17
+ getOutlineCount,
18
+ getPageLabels,
19
+ getPageSize,
20
+ getTextPass,
21
+ isLoadable,
22
+ loadPdf,
23
+ pageHasMarks,
24
+ retainPdfCache
25
+ } from "./index-sth219v1.js";
13
26
  import {
14
27
  INSTALL_HINTS,
15
28
  enhancedPath,
@@ -157,6 +170,8 @@ var DTRPG_PRESET = {
157
170
  icc: "profiles/CGATS21_CRPC1.icc",
158
171
  stripAnnotations: true,
159
172
  blackText: "k-only",
173
+ transparency: "flatten-page",
174
+ flattenPages: [],
160
175
  rasterDpi: undefined
161
176
  },
162
177
  page: {
@@ -242,6 +257,8 @@ var BOOK_PRESET = {
242
257
  icc: "profiles/CGATS21_CRPC1.icc",
243
258
  stripAnnotations: true,
244
259
  blackText: "icc",
260
+ transparency: "flatten-page",
261
+ flattenPages: [],
245
262
  rasterDpi: undefined
246
263
  },
247
264
  page: {
@@ -647,6 +664,14 @@ function resolveWithPreset(cliOverrides, manifest, preset) {
647
664
  if (lastPage !== undefined && lastPage !== "blank" && lastPage !== "short-by-one") {
648
665
  throw new UsageError(`Manifest field \`print.lastPage\` must be "blank" or "short-by-one", got ${JSON.stringify(lastPage)}.`);
649
666
  }
667
+ const transparency = c.pdfx?.transparency ?? m.pdfx?.transparency;
668
+ if (transparency !== undefined && transparency !== "flatten-page" && transparency !== "preserve-text") {
669
+ throw new UsageError(`Manifest field \`pdfx.transparency\` must be "flatten-page" or "preserve-text", got ${JSON.stringify(transparency)}.`);
670
+ }
671
+ const flattenPages = c.pdfx?.flattenPages ?? m.pdfx?.flattenPages;
672
+ if (flattenPages !== undefined && !(Array.isArray(flattenPages) && flattenPages.every((n) => Number.isInteger(n) && n >= 1))) {
673
+ throw new UsageError(`Manifest field \`pdfx.flattenPages\` must be a list of page numbers (1, 2, …), got ${JSON.stringify(flattenPages)}.`);
674
+ }
650
675
  const rasterDpi = c.pdfx?.rasterDpi ?? m.pdfx?.rasterDpi;
651
676
  if (rasterDpi !== undefined && !(Number.isInteger(rasterDpi) && rasterDpi >= 72 && rasterDpi <= 1200)) {
652
677
  throw new UsageError(`Manifest field \`pdfx.rasterDpi\` must be a whole number from 72 to 1200, got ${JSON.stringify(rasterDpi)}.`);
@@ -5767,8 +5792,8 @@ async function resolveGhostscript(targetPlatform = process.platform, env = proce
5767
5792
  }
5768
5793
  return targetPlatform === "win32" ? findWindowsGhostscriptInstall(env) : undefined;
5769
5794
  }
5770
- async function hasLiveTransparency(pdfBytes) {
5771
- const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } = await import("pdf-lib");
5795
+ async function transparentPages(pdfBytes) {
5796
+ const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFArray, PDFRef } = await import("pdf-lib");
5772
5797
  let doc;
5773
5798
  try {
5774
5799
  doc = await PDFDocument.load(pdfBytes, {
@@ -5776,36 +5801,64 @@ async function hasLiveTransparency(pdfBytes) {
5776
5801
  throwOnInvalidObject: false
5777
5802
  });
5778
5803
  } catch {
5779
- return false;
5804
+ return [];
5780
5805
  }
5781
5806
  const ctx = doc.context;
5782
- const NONE = "/None";
5783
- for (const [, obj] of ctx.enumerateIndirectObjects()) {
5784
- const dict = obj instanceof PDFRawStream ? obj.dict : obj instanceof PDFDict ? obj : undefined;
5785
- if (!dict)
5786
- continue;
5807
+ const SKIP = new Set(["Parent", "Font", "Annots"]);
5808
+ const isSet = (v) => v !== undefined && String(v) !== "/None";
5809
+ const dictHasTransparency = (dict) => {
5787
5810
  const smask = dict.get(PDFName.of("SMask"));
5788
5811
  const subtype = String(dict.get(PDFName.of("Subtype")) ?? "");
5789
5812
  const type = String(dict.get(PDFName.of("Type")) ?? "");
5790
- if (subtype === "/Image" && smask && String(smask) !== NONE)
5813
+ if (subtype === "/Image" && isSet(smask))
5791
5814
  return true;
5792
- if (type === "/ExtGState") {
5793
- if (smask && String(smask) !== NONE)
5815
+ if (type === "/ExtGState" || dict.has(PDFName.of("ca")) || dict.has(PDFName.of("BM"))) {
5816
+ if (isSet(smask))
5794
5817
  return true;
5795
5818
  for (const key of ["ca", "CA"]) {
5796
- const alpha = dict.get(PDFName.of(key));
5819
+ const alpha = ctx.lookup(dict.get(PDFName.of(key)));
5797
5820
  if (alpha instanceof PDFNumber && alpha.asNumber() < 1)
5798
5821
  return true;
5799
5822
  }
5823
+ const bm = ctx.lookup(dict.get(PDFName.of("BM")));
5824
+ const mode = bm instanceof PDFArray ? bm.get(0) : bm;
5825
+ if (mode && !["/Normal", "/Compatible"].includes(String(mode)))
5826
+ return true;
5800
5827
  }
5801
- const group = dict.get(PDFName.of("Group"));
5802
- if (group) {
5803
- const resolved = group instanceof PDFRef ? ctx.lookup(group) : group;
5804
- if (resolved instanceof PDFDict && String(resolved.get(PDFName.of("S"))) === "/Transparency")
5828
+ const group = ctx.lookup(dict.get(PDFName.of("Group")));
5829
+ return group instanceof PDFDict && String(group.get(PDFName.of("S"))) === "/Transparency";
5830
+ };
5831
+ const reaches = (root) => {
5832
+ const seen = new Set;
5833
+ const stack = [root];
5834
+ while (stack.length) {
5835
+ const node = stack.pop();
5836
+ const obj = node instanceof PDFRef ? ctx.lookup(node) : node;
5837
+ if (!obj || seen.has(obj))
5838
+ continue;
5839
+ seen.add(obj);
5840
+ if (obj instanceof PDFArray) {
5841
+ stack.push(...obj.asArray());
5842
+ continue;
5843
+ }
5844
+ const dict = obj instanceof PDFRawStream ? obj.dict : obj instanceof PDFDict ? obj : undefined;
5845
+ if (!dict)
5846
+ continue;
5847
+ if (dictHasTransparency(dict))
5805
5848
  return true;
5849
+ for (const [key, value] of dict.entries()) {
5850
+ if (!SKIP.has(key.asString().slice(1)))
5851
+ stack.push(value);
5852
+ }
5806
5853
  }
5807
- }
5808
- return false;
5854
+ return false;
5855
+ };
5856
+ const pages = [];
5857
+ doc.getPages().forEach((page, i) => {
5858
+ if (reaches(page.node))
5859
+ pages.push(i + 1);
5860
+ });
5861
+ return pages;
5809
5862
  }
5810
5863
  async function findPostScriptShadingPages(pdfBytes) {
5811
5864
  const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } = await import("pdf-lib");
@@ -5876,6 +5929,75 @@ async function stripAnnotations(pdfPath, stagingDir) {
5876
5929
  const out = await doc.save();
5877
5930
  await writeFile3(pdfPath, out);
5878
5931
  }
5932
+ function planTransparentPages(transparent, reasons, flattenPages, gradientPages) {
5933
+ const split = [];
5934
+ const artOnly = [];
5935
+ const kept = new Map;
5936
+ for (const n of transparent) {
5937
+ const reason = reasons.get(n);
5938
+ if (reason === "no-text")
5939
+ artOnly.push(n);
5940
+ else if (flattenPages.includes(n))
5941
+ kept.set(n, "listed");
5942
+ else if (reason)
5943
+ kept.set(n, reason);
5944
+ else if (gradientPages.includes(n))
5945
+ kept.set(n, "gradient");
5946
+ else
5947
+ split.push(n);
5948
+ }
5949
+ return { split, artOnly, kept };
5950
+ }
5951
+ async function flattenTransparentPages(pdfPath, pageNumbers, stagingDir, dpi = 300) {
5952
+ const pages = [...new Set(pageNumbers)].sort((a, b) => a - b);
5953
+ if (pages.length === 0)
5954
+ return;
5955
+ const ghostscript = await resolveGhostscript();
5956
+ if (!ghostscript) {
5957
+ throw new Error("Ghostscript executable not found. Install Ghostscript or set GHOSTSCRIPT_PATH to its command-line executable.");
5958
+ }
5959
+ const art = join3(stagingDir, "flatten-art.pdf");
5960
+ const text = join3(stagingDir, "flatten-text.pdf");
5961
+ const pageList = `-sPageList=${pages.join(",")}`;
5962
+ const gs = (...args) => run(ghostscript, [
5963
+ "-q",
5964
+ "-dBATCH",
5965
+ "-dNOPAUSE",
5966
+ "-dSAFER",
5967
+ "-dPreserveAnnots=false",
5968
+ "-dNO_PDFMARK_OUTLINES",
5969
+ ...args
5970
+ ]);
5971
+ try {
5972
+ await gs(pageList, "-sDEVICE=pdfimage24", "-dFILTERTEXT", `-r${dpi}`, `-sOutputFile=${art}`, pdfPath);
5973
+ await gs(pageList, "-sDEVICE=pdfwrite", "-dNOTRANSPARENCY", "-dFILTERIMAGE", "-dFILTERVECTOR", "-dCompatibilityLevel=1.3", `-sOutputFile=${text}`, pdfPath);
5974
+ const { PDFDocument, PDFName } = await import("pdf-lib");
5975
+ const doc = await PDFDocument.load(await readFile9(pdfPath), { updateMetadata: false });
5976
+ const artDoc = await PDFDocument.load(await readFile9(art));
5977
+ const textDoc = await PDFDocument.load(await readFile9(text));
5978
+ if (artDoc.getPageCount() !== pages.length || textDoc.getPageCount() !== pages.length) {
5979
+ throw new Error(`Ghostscript returned ${artDoc.getPageCount()} artwork and ${textDoc.getPageCount()} text pages for ${pages.length} transparent pages.`);
5980
+ }
5981
+ const artPages = await doc.embedPages(artDoc.getPages());
5982
+ const withText = textDoc.getPages().filter((p) => p.node.Contents());
5983
+ const embeddedText = await doc.embedPages(withText);
5984
+ const textPages = textDoc.getPages().map((p) => embeddedText[withText.indexOf(p)]);
5985
+ pages.forEach((pageNumber, i) => {
5986
+ const page = doc.getPage(pageNumber - 1);
5987
+ const { x, y, width, height } = page.getMediaBox();
5988
+ page.node.set(PDFName.of("Contents"), doc.context.obj([]));
5989
+ page.node.set(PDFName.of("Resources"), doc.context.obj({}));
5990
+ page.node.delete(PDFName.of("Group"));
5991
+ page.drawPage(artPages[i], { x, y, width, height });
5992
+ const textPage = textPages[i];
5993
+ if (textPage)
5994
+ page.drawPage(textPage, { x, y, width, height });
5995
+ });
5996
+ await writeFile3(pdfPath, await doc.save());
5997
+ } finally {
5998
+ await Promise.all([art, text].map((f) => unlink2(f).catch(() => {})));
5999
+ }
6000
+ }
5879
6001
  function makePdfxDefPs(params) {
5880
6002
  const pdfxVersion = params.pdfx === "x3" ? "PDF/X-3:2002" : "PDF/X-1:2001";
5881
6003
  const pdfxConformance = params.pdfx === "x1a" ? "PDF/X-1a:2001" : null;
@@ -5967,6 +6089,10 @@ async function convertToPdfxCmyk(inputPdf, outPdf, config) {
5967
6089
  "-dDownsampleColorImages=false",
5968
6090
  "-dDownsampleGrayImages=false",
5969
6091
  "-dDownsampleMonoImages=false",
6092
+ "-dAutoFilterColorImages=false",
6093
+ "-dColorImageFilter=/FlateEncode",
6094
+ "-dAutoFilterGrayImages=false",
6095
+ "-dGrayImageFilter=/FlateEncode",
5970
6096
  ...config.rasterDpi ? [`-r${config.rasterDpi}`] : [],
5971
6097
  "-sProcessColorModel=DeviceCMYK",
5972
6098
  "-sColorConversionStrategy=CMYK",
@@ -6017,7 +6143,7 @@ import git from "isomorphic-git";
6017
6143
  // package.json
6018
6144
  var package_default = {
6019
6145
  name: "gutterpress",
6020
- version: "0.11.20-alpha.2",
6146
+ version: "0.11.20-alpha.3",
6021
6147
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
6022
6148
  author: "itlackey",
6023
6149
  license: "MPL-2.0",
@@ -6272,7 +6398,10 @@ async function writeBuildFingerprint(input) {
6272
6398
  resolvedFlavor: input.pdfx.resolvedFlavor,
6273
6399
  iccPath: input.pdfx.iccPath,
6274
6400
  stripAnnotations: input.pdfx.stripAnnotations,
6275
- blackText: input.pdfx.blackText ?? null
6401
+ blackText: input.pdfx.blackText ?? null,
6402
+ transparency: input.pdfx.transparency ?? null,
6403
+ flattenPages: input.pdfx.flattenPages ?? null,
6404
+ rasterDpi: input.pdfx.rasterDpi ?? null
6276
6405
  },
6277
6406
  outputDir: input.recordedOutputDir ? path10.resolve(input.recordedOutputDir) : outputDir
6278
6407
  },
@@ -6614,346 +6743,6 @@ function inspectionFailed(checkId, message, extra = {}) {
6614
6743
  };
6615
6744
  }
6616
6745
 
6617
- // src/lib/pdf-inspect.ts
6618
- import { stat as stat2, readFile as readFile13 } from "node:fs/promises";
6619
- import { getDocumentProxy } from "unpdf";
6620
- import * as pdfjs from "unpdf/pdfjs";
6621
- function getOps(mod) {
6622
- if ("OPS" in mod && mod.OPS && typeof mod.OPS === "object") {
6623
- return mod.OPS;
6624
- }
6625
- throw new Error("unpdf/pdfjs missing OPS export");
6626
- }
6627
- var OPS = getOps(pdfjs);
6628
- var docCache = new Map;
6629
- var DOC_CACHE_MAX = 8;
6630
- function destroyEntry(entry) {
6631
- entry.doc.then((d) => d.destroy()).catch(() => {});
6632
- }
6633
- var EVICT_DESTROY_GRACE_MS = 60000;
6634
- var gracePending = new Map;
6635
- function destroyEntryAfterGrace(entry) {
6636
- const t = setTimeout(() => {
6637
- gracePending.delete(t);
6638
- destroyEntry(entry);
6639
- }, EVICT_DESTROY_GRACE_MS);
6640
- t.unref?.();
6641
- gracePending.set(t, entry);
6642
- }
6643
- function evictLru() {
6644
- while (docCache.size > DOC_CACHE_MAX) {
6645
- const oldestKey = docCache.keys().next().value;
6646
- if (oldestKey === undefined)
6647
- break;
6648
- const evicted = docCache.get(oldestKey);
6649
- docCache.delete(oldestKey);
6650
- if (evicted)
6651
- destroyEntryAfterGrace(evicted);
6652
- }
6653
- }
6654
- async function loadPdf(path11) {
6655
- let st;
6656
- try {
6657
- st = await stat2(path11);
6658
- } catch {
6659
- return null;
6660
- }
6661
- const hit = docCache.get(path11);
6662
- if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
6663
- docCache.delete(path11);
6664
- docCache.set(path11, hit);
6665
- try {
6666
- return await hit.doc;
6667
- } catch {
6668
- return null;
6669
- }
6670
- }
6671
- if (hit)
6672
- destroyEntry(hit);
6673
- const docPromise = readFile13(path11).then((buf) => getDocumentProxy(new Uint8Array(buf)));
6674
- docCache.delete(path11);
6675
- docCache.set(path11, { mtimeMs: st.mtimeMs, size: st.size, doc: docPromise });
6676
- evictLru();
6677
- try {
6678
- return await docPromise;
6679
- } catch {
6680
- if (docCache.get(path11)?.doc === docPromise)
6681
- docCache.delete(path11);
6682
- return null;
6683
- }
6684
- }
6685
- var activeRetains = 0;
6686
- function retainPdfCache() {
6687
- activeRetains++;
6688
- let released = false;
6689
- return () => {
6690
- if (released)
6691
- return;
6692
- released = true;
6693
- activeRetains--;
6694
- if (activeRetains === 0)
6695
- clearPdfCache();
6696
- };
6697
- }
6698
- function clearPdfCache() {
6699
- for (const [t, entry] of gracePending) {
6700
- clearTimeout(t);
6701
- destroyEntry(entry);
6702
- }
6703
- gracePending.clear();
6704
- for (const entry of docCache.values())
6705
- destroyEntry(entry);
6706
- docCache.clear();
6707
- }
6708
- function getPageSize(page) {
6709
- const vp = page.getViewport({ scale: 1 });
6710
- return { w: vp.width, h: vp.height };
6711
- }
6712
- async function getOutlineCount(doc) {
6713
- try {
6714
- const outline = await doc.getOutline();
6715
- return outline ? outline.length : 0;
6716
- } catch {
6717
- return 0;
6718
- }
6719
- }
6720
- async function getPageLabels(doc) {
6721
- try {
6722
- return await doc.getPageLabels();
6723
- } catch {
6724
- return null;
6725
- }
6726
- }
6727
- async function countLinkAnnotations(doc, stopAtFirst = false) {
6728
- let count = 0;
6729
- for (let i = 1;i <= doc.numPages; i++) {
6730
- try {
6731
- const page = await doc.getPage(i);
6732
- const annots = await page.getAnnotations();
6733
- for (const a of annots) {
6734
- if (a.subtype === "Link") {
6735
- count++;
6736
- if (stopAtFirst)
6737
- return count;
6738
- }
6739
- }
6740
- } catch {}
6741
- }
6742
- return count;
6743
- }
6744
- var textCache = new WeakMap;
6745
- function getTextPass(doc) {
6746
- let p = textCache.get(doc);
6747
- if (!p) {
6748
- p = (async () => {
6749
- const textByPage = [];
6750
- const positions = [];
6751
- const runsByPage = [];
6752
- for (let i = 1;i <= doc.numPages; i++) {
6753
- try {
6754
- const page = await doc.getPage(i);
6755
- const tc = await page.getTextContent();
6756
- let pageText = "";
6757
- const runs = [];
6758
- for (const item of tc.items) {
6759
- const it = item;
6760
- if (typeof it.str !== "string")
6761
- continue;
6762
- pageText += it.str;
6763
- if (it.transform && it.str.trim().length > 0) {
6764
- const x = it.transform[4];
6765
- const y = it.transform[5];
6766
- positions.push({ x, y });
6767
- runs.push({ s: it.str, x, y, w: it.width ?? 0, h: it.height ?? 0 });
6768
- }
6769
- }
6770
- textByPage.push(pageText);
6771
- runsByPage.push(runs);
6772
- } catch {
6773
- textByPage.push("");
6774
- runsByPage.push([]);
6775
- }
6776
- }
6777
- return { textByPage, positions, runsByPage };
6778
- })();
6779
- textCache.set(doc, p);
6780
- }
6781
- return p;
6782
- }
6783
- var opCache = new WeakMap;
6784
- function multiply(m1, m2) {
6785
- return [
6786
- m1[0] * m2[0] + m1[2] * m2[1],
6787
- m1[1] * m2[0] + m1[3] * m2[1],
6788
- m1[0] * m2[2] + m1[2] * m2[3],
6789
- m1[1] * m2[2] + m1[3] * m2[3],
6790
- m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
6791
- m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
6792
- ];
6793
- }
6794
- function fontIsEmbedded(f) {
6795
- if (f.isType3Font)
6796
- return true;
6797
- if (f.missingFile === true)
6798
- return false;
6799
- if (f.isStandardFont === true)
6800
- return false;
6801
- return true;
6802
- }
6803
- function getOpPass(doc) {
6804
- let p = opCache.get(doc);
6805
- if (!p) {
6806
- p = (async () => {
6807
- const imagesByPage = new Map;
6808
- const fonts = [];
6809
- const seenFonts = new Set;
6810
- for (let i = 1;i <= doc.numPages; i++) {
6811
- let page;
6812
- try {
6813
- page = await doc.getPage(i);
6814
- } catch {
6815
- continue;
6816
- }
6817
- let ops;
6818
- try {
6819
- ops = await page.getOperatorList();
6820
- } catch {
6821
- continue;
6822
- }
6823
- let ctm = [1, 0, 0, 1, 0, 0];
6824
- const stack = [];
6825
- const pageImages = [];
6826
- for (let j = 0;j < ops.fnArray.length; j++) {
6827
- const fn = ops.fnArray[j];
6828
- const args = ops.argsArray[j];
6829
- if (fn === OPS.save) {
6830
- stack.push(ctm.slice());
6831
- } else if (fn === OPS.restore) {
6832
- ctm = stack.pop() ?? ctm;
6833
- } else if (fn === OPS.transform) {
6834
- ctm = multiply(ctm, args);
6835
- } else if (fn === OPS.paintImageXObject || fn === OPS.paintImageXObjectRepeat || fn === OPS.paintInlineImageXObject) {
6836
- const name = typeof args[0] === "string" ? args[0] : "(inline)";
6837
- pageImages.push({
6838
- name,
6839
- placedW: Math.hypot(ctm[0], ctm[1]),
6840
- placedH: Math.hypot(ctm[2], ctm[3]),
6841
- x: ctm[4],
6842
- y: ctm[5],
6843
- page: i
6844
- });
6845
- } else if (fn === OPS.setFont) {
6846
- const ref = args[0];
6847
- if (typeof ref === "string" && !seenFonts.has(ref)) {
6848
- seenFonts.add(ref);
6849
- try {
6850
- if (page.commonObjs.has(ref)) {
6851
- const f = page.commonObjs.get(ref);
6852
- fonts.push({
6853
- name: f?.name ?? ref,
6854
- embedded: fontIsEmbedded(f ?? {})
6855
- });
6856
- }
6857
- } catch {}
6858
- }
6859
- }
6860
- }
6861
- if (pageImages.length > 0)
6862
- imagesByPage.set(i, pageImages);
6863
- }
6864
- return { imagesByPage, fonts };
6865
- })();
6866
- opCache.set(doc, p);
6867
- }
6868
- return p;
6869
- }
6870
- function getImagePixelSize(page, name, timeoutMs = 2000) {
6871
- return new Promise((resolve6) => {
6872
- let done = false;
6873
- const finish = (v) => {
6874
- if (!done) {
6875
- done = true;
6876
- resolve6(v);
6877
- }
6878
- };
6879
- const t = setTimeout(() => finish(null), timeoutMs);
6880
- try {
6881
- page.objs.get(name, (obj) => {
6882
- clearTimeout(t);
6883
- finish(obj && obj.width && obj.height ? { width: obj.width, height: obj.height } : null);
6884
- });
6885
- } catch {
6886
- clearTimeout(t);
6887
- finish(null);
6888
- }
6889
- });
6890
- }
6891
- var PAINT_OPS = new Set([
6892
- "stroke",
6893
- "closeStroke",
6894
- "fill",
6895
- "eoFill",
6896
- "fillStroke",
6897
- "eoFillStroke",
6898
- "closeFillStroke",
6899
- "closeEOFillStroke",
6900
- "shadingFill",
6901
- "paintImageXObject",
6902
- "paintInlineImageXObject",
6903
- "paintImageXObjectRepeat",
6904
- "paintImageMaskXObject",
6905
- "paintImageMaskXObjectGroup",
6906
- "paintImageMaskXObjectRepeat",
6907
- "paintSolidColorImageMask",
6908
- "showText",
6909
- "showSpacedText",
6910
- "nextLineShowText",
6911
- "nextLineSetSpacingShowText"
6912
- ].map((name) => OPS[name]).filter((op) => op !== undefined));
6913
- async function pageHasMarks(doc, pageNum) {
6914
- const ops = await (await doc.getPage(pageNum)).getOperatorList();
6915
- return ops.fnArray.some((fn, i) => fn === OPS.constructPath ? PAINT_OPS.has(ops.argsArray[i][0]) : PAINT_OPS.has(fn));
6916
- }
6917
- async function getImageResolutions(doc) {
6918
- const { imagesByPage } = await getOpPass(doc);
6919
- const out = [];
6920
- for (const [pageNum, imgs] of imagesByPage) {
6921
- let page;
6922
- try {
6923
- page = await doc.getPage(pageNum);
6924
- } catch {
6925
- continue;
6926
- }
6927
- for (const img of imgs) {
6928
- if (img.name === "(inline)")
6929
- continue;
6930
- const px = await getImagePixelSize(page, img.name);
6931
- if (!px)
6932
- continue;
6933
- const placedWin = img.placedW / 72;
6934
- const placedHin = img.placedH / 72;
6935
- if (placedWin <= 0 || placedHin <= 0)
6936
- continue;
6937
- out.push({
6938
- page: pageNum,
6939
- xDpi: px.width / placedWin,
6940
- yDpi: px.height / placedHin
6941
- });
6942
- }
6943
- }
6944
- return out;
6945
- }
6946
- async function isLoadable(doc) {
6947
- try {
6948
- for (let i = 1;i <= doc.numPages; i++) {
6949
- await doc.getPage(i);
6950
- }
6951
- return true;
6952
- } catch {
6953
- return false;
6954
- }
6955
- }
6956
-
6957
6746
  // src/checks/pdf/qpdf-structure.ts
6958
6747
  var check2 = {
6959
6748
  id: "pdf.structure.qpdf",
@@ -7228,9 +7017,9 @@ var check5 = {
7228
7017
  registerCheck(check5);
7229
7018
 
7230
7019
  // src/lib/pdf-parse.ts
7231
- import { readFile as readFile14 } from "node:fs/promises";
7020
+ import { readFile as readFile13 } from "node:fs/promises";
7232
7021
  async function readPdfBytes(pdfPath) {
7233
- return readFile14(pdfPath, "latin1");
7022
+ return readFile13(pdfPath, "latin1");
7234
7023
  }
7235
7024
  function parseInkCov(out) {
7236
7025
  const lines = out.split(/\r?\n/).filter((l) => l.includes("CMYK"));
@@ -7491,7 +7280,7 @@ var check9 = {
7491
7280
  registerCheck(check9);
7492
7281
 
7493
7282
  // src/checks/pdf/black-text.ts
7494
- import { readFile as readFile15 } from "node:fs/promises";
7283
+ import { readFile as readFile14 } from "node:fs/promises";
7495
7284
  var check10 = {
7496
7285
  id: "pdf.print.black-text",
7497
7286
  name: "Black Text",
@@ -7504,7 +7293,7 @@ var check10 = {
7504
7293
  return [];
7505
7294
  try {
7506
7295
  const { findRichBlackText, LARGE_TEXT_PT } = await import("./black-text-gray-7dz61456.js");
7507
- const found = await findRichBlackText(await readFile15(ctx.pdfPath));
7296
+ const found = await findRichBlackText(await readFile14(ctx.pdfPath));
7508
7297
  if (found.runs === 0)
7509
7298
  return [];
7510
7299
  return [
@@ -7894,7 +7683,7 @@ registerCheck(check19);
7894
7683
 
7895
7684
  // src/checks/source/htmlhint.ts
7896
7685
  import { existsSync as existsSync9 } from "node:fs";
7897
- import { readFile as readFile16 } from "node:fs/promises";
7686
+ import { readFile as readFile15 } from "node:fs/promises";
7898
7687
  import htmlhint from "htmlhint";
7899
7688
  var { HTMLHint } = htmlhint;
7900
7689
  var CONFIG_NAMES2 = [".htmlhintrc"];
@@ -7914,8 +7703,8 @@ var check20 = {
7914
7703
  const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
7915
7704
  if (!resolvedConfig && !configPath)
7916
7705
  return [];
7917
- const ruleset = resolvedConfig ? JSON.parse(await readFile16(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
7918
- const html = await readFile16(ctx.htmlPath, "utf8");
7706
+ const ruleset = resolvedConfig ? JSON.parse(await readFile15(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
7707
+ const html = await readFile15(ctx.htmlPath, "utf8");
7919
7708
  const messages = HTMLHint.verify(html, ruleset);
7920
7709
  return messages.map((m) => ({
7921
7710
  checkId: check20.id,
@@ -7930,7 +7719,7 @@ var check20 = {
7930
7719
  registerCheck(check20);
7931
7720
 
7932
7721
  // src/checks/source/stylelint.ts
7933
- import { readFile as readFile17 } from "node:fs/promises";
7722
+ import { readFile as readFile16 } from "node:fs/promises";
7934
7723
 
7935
7724
  // src/lib/printsafe.ts
7936
7725
  import postcss2 from "postcss";
@@ -8272,7 +8061,7 @@ function checkCssRaw(css, from) {
8272
8061
  warnings.push({
8273
8062
  rule: ruleRiskyProps,
8274
8063
  severity: "warning",
8275
- 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.`,
8064
+ 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.`,
8276
8065
  ...nodeLoc(decl)
8277
8066
  });
8278
8067
  } else if (isUnpairedFitContentWidth(decl)) {
@@ -8337,7 +8126,7 @@ var check21 = {
8337
8126
  for (const file of files) {
8338
8127
  let css;
8339
8128
  try {
8340
- css = await readFile17(file, "utf8");
8129
+ css = await readFile16(file, "utf8");
8341
8130
  } catch (err) {
8342
8131
  results.push({
8343
8132
  checkId: check21.id,
@@ -8364,12 +8153,12 @@ var check21 = {
8364
8153
  registerCheck(check21);
8365
8154
 
8366
8155
  // src/checks/source/css-ownership.ts
8367
- import { readFile as readFile18 } from "node:fs/promises";
8156
+ import { readFile as readFile17 } from "node:fs/promises";
8368
8157
  import postcss3 from "postcss";
8369
8158
  import { parse as parseYaml3 } from "yaml";
8370
8159
  var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
8371
8160
  async function loadContract(configPath) {
8372
- const raw = await readFile18(configPath, "utf8");
8161
+ const raw = await readFile17(configPath, "utf8");
8373
8162
  const parsed = parseYaml3(raw);
8374
8163
  if (parsed && typeof parsed === "object")
8375
8164
  return parsed;
@@ -8520,7 +8309,7 @@ var check22 = {
8520
8309
  continue;
8521
8310
  let css;
8522
8311
  try {
8523
- css = await readFile18(absFile, "utf8");
8312
+ css = await readFile17(absFile, "utf8");
8524
8313
  } catch {
8525
8314
  continue;
8526
8315
  }
@@ -8566,7 +8355,7 @@ import { existsSync as existsSync10 } from "node:fs";
8566
8355
  import { dirname as dirname3, resolve as resolve6 } from "node:path";
8567
8356
 
8568
8357
  // src/checks/source/local-ref-parser.ts
8569
- import { readFile as readFile19 } from "node:fs/promises";
8358
+ import { readFile as readFile18 } from "node:fs/promises";
8570
8359
  function newlineOffsets(content) {
8571
8360
  const offsets = [];
8572
8361
  for (let i = 0;i < content.length; i++)
@@ -8635,7 +8424,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
8635
8424
  for (const file of files) {
8636
8425
  let refs;
8637
8426
  try {
8638
- refs = collectRenderedLocalRefs(await readFile19(file, "utf8"));
8427
+ refs = collectRenderedLocalRefs(await readFile18(file, "utf8"));
8639
8428
  } catch {
8640
8429
  results.push(inspectionFailed(checkId, `Could not read source file: ${file}`, { file }));
8641
8430
  continue;
@@ -8986,7 +8775,7 @@ var check24 = {
8986
8775
  registerCheck(check24);
8987
8776
 
8988
8777
  // src/checks/source/accessibility-alt-text.ts
8989
- import { readFile as readFile20 } from "node:fs/promises";
8778
+ import { readFile as readFile19 } from "node:fs/promises";
8990
8779
  var check25 = {
8991
8780
  id: "source.accessibility.alt-text",
8992
8781
  name: "Image Alt Text",
@@ -9000,7 +8789,7 @@ var check25 = {
9000
8789
  const results = [];
9001
8790
  for (const file of files) {
9002
8791
  try {
9003
- const content = await readFile20(file, "utf8");
8792
+ const content = await readFile19(file, "utf8");
9004
8793
  const lines = content.split(`
9005
8794
  `);
9006
8795
  let inFence = false;
@@ -9044,7 +8833,7 @@ function extractImageAlts(line) {
9044
8833
  registerCheck(check25);
9045
8834
 
9046
8835
  // src/checks/source/accessibility-heading-order.ts
9047
- import { readFile as readFile21 } from "node:fs/promises";
8836
+ import { readFile as readFile20 } from "node:fs/promises";
9048
8837
  var check26 = {
9049
8838
  id: "source.accessibility.heading-order",
9050
8839
  name: "Heading Order",
@@ -9058,7 +8847,7 @@ var check26 = {
9058
8847
  const results = [];
9059
8848
  for (const file of files) {
9060
8849
  try {
9061
- const content = await readFile21(file, "utf8");
8850
+ const content = await readFile20(file, "utf8");
9062
8851
  const lines = content.split(`
9063
8852
  `);
9064
8853
  let inFence = false;
@@ -9100,7 +8889,7 @@ var check26 = {
9100
8889
  registerCheck(check26);
9101
8890
 
9102
8891
  // src/checks/source/layout-markers.ts
9103
- import { readFile as readFile22 } from "node:fs/promises";
8892
+ import { readFile as readFile21 } from "node:fs/promises";
9104
8893
 
9105
8894
  // src/lib/unknown-classes.ts
9106
8895
  import postcss4 from "postcss";
@@ -9203,7 +8992,7 @@ async function collectKnownClasses(ctx, plugins) {
9203
8992
  const sheets = (await collectStyleDependencies(ctx.inputDir, roots)).filter((f) => /\.css$/i.test(f));
9204
8993
  for (const sheet of sheets) {
9205
8994
  try {
9206
- addCssClasses(await readFile22(sheet, "utf8"), names);
8995
+ addCssClasses(await readFile21(sheet, "utf8"), names);
9207
8996
  } catch {}
9208
8997
  }
9209
8998
  return names;
@@ -9227,7 +9016,7 @@ var check27 = {
9227
9016
  const knownClasses = () => known ??= collectKnownClasses(ctx, plugins);
9228
9017
  for (const file of files) {
9229
9018
  try {
9230
- const content = await readFile22(file, "utf8");
9019
+ const content = await readFile21(file, "utf8");
9231
9020
  const env = {};
9232
9021
  const tokens = md.parse(content, env);
9233
9022
  md.renderer.render(tokens, md.options, env);
@@ -9267,7 +9056,7 @@ var check27 = {
9267
9056
  registerCheck(check27);
9268
9057
 
9269
9058
  // src/checks/source/merge-markers.ts
9270
- import { readFile as readFile23 } from "node:fs/promises";
9059
+ import { readFile as readFile22 } from "node:fs/promises";
9271
9060
  import path12 from "node:path";
9272
9061
 
9273
9062
  // src/checks/asset/extensions.ts
@@ -9309,7 +9098,7 @@ var check28 = {
9309
9098
  for (const file of textFiles) {
9310
9099
  let content;
9311
9100
  try {
9312
- content = await readFile23(file, "utf8");
9101
+ content = await readFile22(file, "utf8");
9313
9102
  } catch {
9314
9103
  results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
9315
9104
  continue;
@@ -9369,7 +9158,7 @@ var check28 = {
9369
9158
  registerCheck(check28);
9370
9159
 
9371
9160
  // src/checks/source/characters.ts
9372
- import { readFile as readFile24 } from "node:fs/promises";
9161
+ import { readFile as readFile23 } from "node:fs/promises";
9373
9162
 
9374
9163
  // src/checks/source/character-table.ts
9375
9164
  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");
@@ -9492,7 +9281,7 @@ var check29 = {
9492
9281
  for (const file of (ctx.markdownFiles ?? []).slice().sort()) {
9493
9282
  let content;
9494
9283
  try {
9495
- content = await readFile24(file, "utf8");
9284
+ content = await readFile23(file, "utf8");
9496
9285
  } catch {
9497
9286
  results.push(inspectionFailed(check29.id, `Could not read source file: ${file}`, { file }));
9498
9287
  continue;
@@ -9532,16 +9321,16 @@ var check29 = {
9532
9321
  registerCheck(check29);
9533
9322
 
9534
9323
  // src/checks/asset/image-file-size.ts
9535
- import { stat as stat4 } from "node:fs/promises";
9324
+ import { stat as stat3 } from "node:fs/promises";
9536
9325
 
9537
9326
  // src/lib/image-inspect.ts
9538
- import { stat as stat3, readFile as readFile25 } from "node:fs/promises";
9327
+ import { stat as stat2, readFile as readFile24 } from "node:fs/promises";
9539
9328
  var DEFAULT_DPI = 72;
9540
9329
  var cache = new Map;
9541
9330
  async function inspectImage(path13) {
9542
9331
  let st;
9543
9332
  try {
9544
- st = await stat3(path13);
9333
+ st = await stat2(path13);
9545
9334
  } catch {
9546
9335
  return null;
9547
9336
  }
@@ -9550,7 +9339,7 @@ async function inspectImage(path13) {
9550
9339
  return hit.info;
9551
9340
  let info3 = null;
9552
9341
  try {
9553
- const buf = await readFile25(path13);
9342
+ const buf = await readFile24(path13);
9554
9343
  info3 = parseImage(buf);
9555
9344
  } catch {
9556
9345
  info3 = null;
@@ -9772,7 +9561,7 @@ var check30 = {
9772
9561
  const results = [];
9773
9562
  for (const file of files) {
9774
9563
  try {
9775
- const info3 = await stat4(file);
9564
+ const info3 = await stat3(file);
9776
9565
  if (info3.size > maxSize) {
9777
9566
  const sizeMb = (info3.size / 1e6).toFixed(1);
9778
9567
  const maxMb = (maxSize / 1e6).toFixed(1);
@@ -10110,7 +9899,7 @@ var check37 = {
10110
9899
  registerCheck(check37);
10111
9900
 
10112
9901
  // src/checks/heuristic/section-density.ts
10113
- import { readFile as readFile26 } from "node:fs/promises";
9902
+ import { readFile as readFile25 } from "node:fs/promises";
10114
9903
  var check38 = {
10115
9904
  id: "heuristic.chunking.section-density",
10116
9905
  name: "Section Density",
@@ -10127,7 +9916,7 @@ var check38 = {
10127
9916
  const results = [];
10128
9917
  for (const file of files) {
10129
9918
  try {
10130
- const content = await readFile26(file, "utf8");
9919
+ const content = await readFile25(file, "utf8");
10131
9920
  const lines = content.split(`
10132
9921
  `);
10133
9922
  let currentSectionStart = 0;
@@ -11009,7 +10798,7 @@ class PdfOutput {
11009
10798
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
11010
10799
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
11011
10800
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
11012
- const { buildNativePdf } = await import("./engine-yax79k9m.js");
10801
+ const { buildNativePdf } = await import("./engine-fdtv01a2.js");
11013
10802
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
11014
10803
  title: config.title,
11015
10804
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -11031,12 +10820,40 @@ class PdfOutput {
11031
10820
  if (pdfxMode) {
11032
10821
  const icc = opts.iccPath ?? config.pdfx.icc;
11033
10822
  effectiveIccPath = await resolveIccProfile(icc, manifestDir, opts.iccPath);
11034
- if (await hasLiveTransparency(await fsp2.readFile(rawPdf))) {
11035
- 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).");
10823
+ const rawBytes = await fsp2.readFile(rawPdf);
10824
+ const transparent = await transparentPages(rawBytes);
10825
+ const blankGradientPages = await findPostScriptShadingPages(rawBytes);
10826
+ const flattenedGradientPages = blankGradientPages.filter((n) => transparent.includes(n));
10827
+ const vectorGradientPages = blankGradientPages.filter((n) => !transparent.includes(n));
10828
+ 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.";
10829
+ const pageList = (pages) => `Page${pages.length === 1 ? "" : "s"} ${pages.join(", ")}`;
10830
+ if (vectorGradientPages.length > 0) {
10831
+ 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}`);
11036
10832
  }
11037
- const blankGradientPages = await findPostScriptShadingPages(await fsp2.readFile(rawPdf));
11038
- if (blankGradientPages.length > 0) {
11039
- 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.");
10833
+ if (flattenedGradientPages.length > 0 && config.pdfx.transparency !== "preserve-text") {
10834
+ 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}`);
10835
+ }
10836
+ const flattenPages = config.pdfx.flattenPages;
10837
+ if (transparent.length > 0 && config.pdfx.transparency === "preserve-text") {
10838
+ const { unsplittableTextReasons } = await import("./pdf-inspect-zdmy7qca.js");
10839
+ const reasons = await unsplittableTextReasons(rawBytes, transparent).catch((err) => {
10840
+ 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.`);
10841
+ return null;
10842
+ });
10843
+ if (reasons) {
10844
+ const plan = planTransparentPages(transparent, reasons, flattenPages, blankGradientPages);
10845
+ await flattenTransparentPages(rawPdf, plan.split, stage, config.pdfx.rasterDpi);
10846
+ reportTransparentPlan(plan, pdfxMode, config.pdfx.rasterDpi ?? 300, pageList);
10847
+ }
10848
+ }
10849
+ const unused = flattenPages.filter((n) => !transparent.includes(n));
10850
+ if (flattenPages.length > 0 && config.pdfx.transparency !== "preserve-text") {
10851
+ log.warn("pdfx.flattenPages has no effect without pdfx.transparency: preserve-text.");
10852
+ } else if (unused.length > 0) {
10853
+ log.warn(`pdfx.flattenPages lists ${pageList(unused).toLowerCase()}, which ${unused.length === 1 ? "has" : "have"} no transparency (or do not exist): nothing to flatten there.`);
10854
+ }
10855
+ if (transparent.length > 0 && config.pdfx.transparency !== "preserve-text") {
10856
+ 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).");
11040
10857
  }
11041
10858
  const shouldStrip = opts.stripAnnotations ?? config.pdfx.stripAnnotations;
11042
10859
  shouldStripAnnotations = shouldStrip;
@@ -11079,7 +10896,10 @@ class PdfOutput {
11079
10896
  resolvedFlavor: config.pdfx.flavor,
11080
10897
  iccPath: effectiveIccPath,
11081
10898
  stripAnnotations: shouldStripAnnotations,
11082
- blackText: pdfxMode ? config.pdfx.blackText : null
10899
+ blackText: pdfxMode ? config.pdfx.blackText : null,
10900
+ transparency: pdfxMode ? config.pdfx.transparency : null,
10901
+ flattenPages: pdfxMode ? config.pdfx.flattenPages : null,
10902
+ rasterDpi: pdfxMode ? config.pdfx.rasterDpi ?? null : null
11083
10903
  }
11084
10904
  }, `Wrote: ${reportedPdf}`, { htmlPath: htmlFile, pdfPath: reportedPdf }, pdfName, engineDiagnostics);
11085
10905
  } finally {
@@ -11116,11 +10936,40 @@ async function runBuild(opts) {
11116
10936
  await fsp2.rm(ctx.workDir, { recursive: true, force: true });
11117
10937
  }
11118
10938
  }
10939
+ var FALLBACK_CAUSE = {
10940
+ listed: "listed in pdfx.flattenPages",
10941
+ "text-transparency": "text drawn with transparency or blending (opacity, mix-blend-mode, a soft mask, a translucent text-shadow); keep that text opaque",
10942
+ "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",
10943
+ "text-pattern-or-clip": 'text filled with a pattern or used as a clip (SVG fill="url(#…)", gradient text, background-clip: text)',
10944
+ gradient: "a repeating or conic gradient; use a linear or radial gradient, or a small tiled image"
10945
+ };
10946
+ function reportTransparentPlan({ split, artOnly, kept }, pdfxMode, dpi, pageList) {
10947
+ const one = (pages, singular, plural) => pages.length === 1 ? singular : plural;
10948
+ if (split.length > 0) {
10949
+ 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.`);
10950
+ }
10951
+ if (artOnly.length > 0) {
10952
+ 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).`);
10953
+ }
10954
+ if (kept.size > 0) {
10955
+ const pages = [...kept.keys()];
10956
+ const byCause = new Map;
10957
+ for (const [n, cause] of kept)
10958
+ byCause.set(cause, [...byCause.get(cause) ?? [], n]);
10959
+ const lines = [...byCause].map(([cause, ns]) => ` - ${one(ns, "page", "pages")} ${ns.join(", ")}: ${FALLBACK_CAUSE[cause]}`);
10960
+ 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:
10961
+ ` + lines.join(`
10962
+ `));
10963
+ }
10964
+ if (split.length === 0 && [...kept.values()].some((cause) => cause !== "listed")) {
10965
+ 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).");
10966
+ }
10967
+ }
11119
10968
  // src/preview/lifecycle.ts
11120
10969
  import path16 from "path";
11121
10970
  import { tmpdir } from "os";
11122
10971
  import { randomBytes as randomBytes2 } from "crypto";
11123
- import { readdir as readdir5, readFile as readFile27, writeFile as writeFile6 } from "node:fs/promises";
10972
+ import { readdir as readdir5, readFile as readFile26, writeFile as writeFile6 } from "node:fs/promises";
11124
10973
 
11125
10974
  // src/utils/file-utils.ts
11126
10975
  import { promises as fs3 } from "fs";
@@ -11681,7 +11530,7 @@ async function cleanupOrphanTempDirs() {
11681
11530
  const dir = path16.join(TEMP_DIR_BASE, entry);
11682
11531
  const pidFile = path16.join(dir, PID_FILE_NAME);
11683
11532
  try {
11684
- const raw = await readFile27(pidFile, "utf8");
11533
+ const raw = await readFile26(pidFile, "utf8");
11685
11534
  const pid = parseInt(raw.trim(), 10);
11686
11535
  if (!isProcessAlive(pid)) {
11687
11536
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -11788,7 +11637,7 @@ function createServerState(inputPath, tempDir, config, options) {
11788
11637
  import http from "node:http";
11789
11638
  import net from "node:net";
11790
11639
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
11791
- import { readFile as readFile28, stat as stat5 } from "node:fs/promises";
11640
+ import { readFile as readFile27, stat as stat4 } from "node:fs/promises";
11792
11641
  import path17 from "path";
11793
11642
  import { WebSocket, WebSocketServer } from "ws";
11794
11643
 
@@ -11877,7 +11726,7 @@ async function findAvailablePort(startPort, host = "127.0.0.1") {
11877
11726
  async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders = {}) {
11878
11727
  let filePath = absPath;
11879
11728
  try {
11880
- const s = await stat5(filePath);
11729
+ const s = await stat4(filePath);
11881
11730
  if (s.isDirectory())
11882
11731
  filePath = path17.join(filePath, "index.html");
11883
11732
  } catch {
@@ -11887,7 +11736,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
11887
11736
  }
11888
11737
  let data;
11889
11738
  try {
11890
- data = await readFile28(filePath);
11739
+ data = await readFile27(filePath);
11891
11740
  } catch {
11892
11741
  res.writeHead(404);
11893
11742
  res.end("Not Found");
@@ -11916,7 +11765,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11916
11765
  }
11917
11766
  let fileStat;
11918
11767
  try {
11919
- fileStat = await stat5(absPath, { bigint: true });
11768
+ fileStat = await stat4(absPath, { bigint: true });
11920
11769
  } catch {
11921
11770
  await serveStatic(absPath, res);
11922
11771
  return;
@@ -11936,7 +11785,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11936
11785
  let data;
11937
11786
  if (!validator || validator.stamp !== stamp) {
11938
11787
  try {
11939
- data = await readFile28(absPath);
11788
+ data = await readFile27(absPath);
11940
11789
  } catch {
11941
11790
  await serveStatic(absPath, res);
11942
11791
  return;
@@ -11964,7 +11813,7 @@ async function isMissingReferencedImage(pathname, absPath, refs) {
11964
11813
  if (!refs.has(imageRefKey(pathname)))
11965
11814
  return false;
11966
11815
  try {
11967
- await stat5(absPath);
11816
+ await stat4(absPath);
11968
11817
  return false;
11969
11818
  } catch (err) {
11970
11819
  return err?.code === "ENOENT";
@@ -12248,7 +12097,7 @@ async function startPreviewServer(options) {
12248
12097
  import { platform, arch, release } from "node:os";
12249
12098
 
12250
12099
  // src/lib/remote-auth/token-store.ts
12251
- import { chmod, mkdir as mkdir7, readFile as readFile29, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
12100
+ import { chmod, mkdir as mkdir7, readFile as readFile28, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
12252
12101
  import os3 from "node:os";
12253
12102
  import path18 from "node:path";
12254
12103
  function credentialHostKey(hostOrUrl) {
@@ -12292,7 +12141,7 @@ class FileTokenStore {
12292
12141
  async read() {
12293
12142
  let raw;
12294
12143
  try {
12295
- raw = await readFile29(this.filePath, "utf8");
12144
+ raw = await readFile28(this.filePath, "utf8");
12296
12145
  } catch (e) {
12297
12146
  if (e?.code === "ENOENT") {
12298
12147
  return { version: 1, credentials: {} };
@@ -12492,13 +12341,13 @@ async function getSystemDiagnostics() {
12492
12341
  };
12493
12342
  }
12494
12343
  // src/lib/project-scaffold.ts
12495
- import { access, copyFile, cp as cp2, mkdir as mkdir9, readdir as readdir6, readFile as readFile31, stat as stat6, writeFile as writeFile9 } from "node:fs/promises";
12344
+ import { access, copyFile, cp as cp2, mkdir as mkdir9, readdir as readdir6, readFile as readFile30, stat as stat5, writeFile as writeFile9 } from "node:fs/promises";
12496
12345
  import { constants as FS, existsSync as existsSync14 } from "node:fs";
12497
12346
  import path20 from "node:path";
12498
12347
 
12499
12348
  // src/lib/page-size.ts
12500
12349
  import { existsSync as existsSync13 } from "node:fs";
12501
- import { mkdir as mkdir8, readFile as readFile30, writeFile as writeFile8 } from "node:fs/promises";
12350
+ import { mkdir as mkdir8, readFile as readFile29, writeFile as writeFile8 } from "node:fs/promises";
12502
12351
  import path19 from "node:path";
12503
12352
  import postcss5 from "postcss";
12504
12353
  import { Scalar as Scalar2 } from "yaml";
@@ -12603,7 +12452,7 @@ var relDisplay2 = (projectDir, abs) => path19.relative(projectDir, abs).split(pa
12603
12452
  async function findCssSize(projectDir, sheets) {
12604
12453
  let found = null;
12605
12454
  for (const file of sheets) {
12606
- const size = readPageSizeFromCss(await readFile30(file, "utf8"));
12455
+ const size = readPageSizeFromCss(await readFile29(file, "utf8"));
12607
12456
  if (size)
12608
12457
  found = { file, size };
12609
12458
  }
@@ -12630,7 +12479,7 @@ async function setPageSetup(projectDir, choice) {
12630
12479
  const found = await findCssSize(projectDir, sheets);
12631
12480
  const newSheet = path19.join(projectDir, "styles", "book.css");
12632
12481
  const target = found?.file ?? sheets[sheets.length - 1] ?? newSheet;
12633
- const before = existsSync13(target) ? await readFile30(target, "utf8") : "";
12482
+ const before = existsSync13(target) ? await readFile29(target, "utf8") : "";
12634
12483
  let after;
12635
12484
  try {
12636
12485
  after = setPageSizeInCss(before, size);
@@ -12839,7 +12688,7 @@ async function adoptFolder(options) {
12839
12688
  }
12840
12689
  let st;
12841
12690
  try {
12842
- st = await stat6(dir);
12691
+ st = await stat5(dir);
12843
12692
  } catch {
12844
12693
  throw new CreateProjectErrorImpl("scaffold-io", `Folder not found: ${dir}`);
12845
12694
  }
@@ -12920,7 +12769,7 @@ styles:
12920
12769
  async function firstSourceFile(manifestPath) {
12921
12770
  let text;
12922
12771
  try {
12923
- text = await readFile31(manifestPath, "utf8");
12772
+ text = await readFile30(manifestPath, "utf8");
12924
12773
  } catch {
12925
12774
  return;
12926
12775
  }
@@ -12932,14 +12781,14 @@ async function firstSourceFile(manifestPath) {
12932
12781
  return m ? m[1].trim() : undefined;
12933
12782
  }
12934
12783
  async function fillTemplateFile(filePath, substitutions) {
12935
- let text = await readFile31(filePath, "utf8");
12784
+ let text = await readFile30(filePath, "utf8");
12936
12785
  for (const [token, value] of Object.entries(substitutions)) {
12937
12786
  text = text.split(token).join(value);
12938
12787
  }
12939
12788
  await writeFile9(filePath, text, "utf8");
12940
12789
  }
12941
12790
  // src/lib/extension-scaffold.ts
12942
- import { access as access2, mkdir as mkdir10, readFile as readFile32, writeFile as writeFile10 } from "node:fs/promises";
12791
+ import { access as access2, mkdir as mkdir10, readFile as readFile31, writeFile as writeFile10 } from "node:fs/promises";
12943
12792
  import { constants as FS2 } from "node:fs";
12944
12793
  import path21 from "node:path";
12945
12794
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -13056,7 +12905,7 @@ async function scaffoldExtension(options) {
13056
12905
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
13057
12906
  const dest = path21.join(extensionDir, relDest);
13058
12907
  await mkdir10(path21.dirname(dest), { recursive: true });
13059
- const text = await readFile32(src, "utf8");
12908
+ const text = await readFile31(src, "utf8");
13060
12909
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
13061
12910
  await writeFile10(dest, substitute(text, table), "utf8");
13062
12911
  written.push(relDest);
@@ -13082,7 +12931,7 @@ function substitute(text, table) {
13082
12931
  return out;
13083
12932
  }
13084
12933
  // src/lib/project-templates.ts
13085
- import { cp as cp3, mkdir as mkdir11, readFile as readFile33, readdir as readdir7, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
12934
+ import { cp as cp3, mkdir as mkdir11, readFile as readFile32, readdir as readdir7, stat as stat6, writeFile as writeFile11 } from "node:fs/promises";
13086
12935
  import { constants as FS3, existsSync as existsSync15 } from "node:fs";
13087
12936
  import { access as access3 } from "node:fs/promises";
13088
12937
  import path22 from "node:path";
@@ -13094,7 +12943,7 @@ var BUILT_IN_TEMPLATE_IDS = [
13094
12943
  ];
13095
12944
  async function readTemplateChoices(manifestPath) {
13096
12945
  try {
13097
- const text = await readFile33(manifestPath, "utf8");
12946
+ const text = await readFile32(manifestPath, "utf8");
13098
12947
  const doc = parseDocument2(text);
13099
12948
  const out = {};
13100
12949
  const preset = doc.get("preset");
@@ -13276,7 +13125,7 @@ async function saveProjectAsTemplate(options) {
13276
13125
  async function retokeniseManifest(manifestPath) {
13277
13126
  let text;
13278
13127
  try {
13279
- text = await readFile33(manifestPath, "utf8");
13128
+ text = await readFile32(manifestPath, "utf8");
13280
13129
  } catch {
13281
13130
  return;
13282
13131
  }
@@ -13297,7 +13146,7 @@ async function listCustomTemplates(templatesRoot) {
13297
13146
  const dir = path22.join(templatesRoot, entry.name);
13298
13147
  let label = prettify(entry.name);
13299
13148
  try {
13300
- const meta = JSON.parse(await readFile33(path22.join(dir, TEMPLATE_META_FILE), "utf8"));
13149
+ const meta = JSON.parse(await readFile32(path22.join(dir, TEMPLATE_META_FILE), "utf8"));
13301
13150
  if (typeof meta.label === "string" && meta.label)
13302
13151
  label = meta.label;
13303
13152
  } catch {}
@@ -13315,7 +13164,7 @@ async function listCustomTemplates(templatesRoot) {
13315
13164
  }
13316
13165
  async function importTemplateFromFolder(options) {
13317
13166
  const { sourceDir, templatesRoot } = options;
13318
- const srcStat = await stat7(sourceDir);
13167
+ const srcStat = await stat6(sourceDir);
13319
13168
  if (!srcStat.isDirectory()) {
13320
13169
  throw new Error("The chosen path is not a folder.");
13321
13170
  }
@@ -13323,7 +13172,7 @@ async function importTemplateFromFolder(options) {
13323
13172
  return saveProjectAsTemplate({ projectDir: sourceDir, name, templatesRoot });
13324
13173
  }
13325
13174
  // src/lib/snippets.ts
13326
- import { readdir as readdir8, readFile as readFile34, writeFile as writeFile12, mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
13175
+ import { readdir as readdir8, readFile as readFile33, writeFile as writeFile12, mkdir as mkdir12, rm as rm4 } from "node:fs/promises";
13327
13176
  import path23 from "node:path";
13328
13177
  var SNIPPETS_DIR = "snippets";
13329
13178
  var LEVEL_RANK = { project: 0, extension: 1, core: 2 };
@@ -13364,7 +13213,7 @@ async function scanSnippetFiles(dir) {
13364
13213
  continue;
13365
13214
  let body = "";
13366
13215
  try {
13367
- body = await readFile34(path23.join(dir, fileName), "utf8");
13216
+ body = await readFile33(path23.join(dir, fileName), "utf8");
13368
13217
  } catch {
13369
13218
  continue;
13370
13219
  }
@@ -13453,7 +13302,7 @@ async function collectLibrary(projectDir) {
13453
13302
  listed.component = name;
13454
13303
  continue;
13455
13304
  }
13456
- const body = await readFile34(path23.join(entry.dir, rel), "utf8").catch(() => null);
13305
+ const body = await readFile33(path23.join(entry.dir, rel), "utf8").catch(() => null);
13457
13306
  if (body === null)
13458
13307
  continue;
13459
13308
  snippets2.push({
@@ -13522,7 +13371,7 @@ async function listLocationVariants(projectDir) {
13522
13371
  }
13523
13372
  // src/lib/extension-import.ts
13524
13373
  import { existsSync as existsSync16 } from "node:fs";
13525
- import { cp as cp4, mkdir as mkdir13, mkdtemp as mkdtemp2, readFile as readFile35, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
13374
+ import { cp as cp4, mkdir as mkdir13, mkdtemp as mkdtemp2, readFile as readFile34, rm as rm5, writeFile as writeFile13 } from "node:fs/promises";
13526
13375
  import { tmpdir as tmpdir2 } from "node:os";
13527
13376
  import path24 from "node:path";
13528
13377
  import { unzipSync } from "fflate";
@@ -13595,7 +13444,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
13595
13444
  if (!existsSync16(cssPath)) {
13596
13445
  throw new Error("A theme must contain a theme.css file.");
13597
13446
  }
13598
- const css = await readFile35(cssPath, "utf8");
13447
+ const css = await readFile34(cssPath, "utf8");
13599
13448
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
13600
13449
  if (reject) {
13601
13450
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -13623,7 +13472,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
13623
13472
  for (let i = 0;i < extraSheets.length; i++) {
13624
13473
  const rel = extraSheets[i];
13625
13474
  const sheetPath = extraSheetPaths[i];
13626
- const sheetCss = await readFile35(sheetPath, "utf8");
13475
+ const sheetCss = await readFile34(sheetPath, "utf8");
13627
13476
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
13628
13477
  if (sheetReject) {
13629
13478
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -13656,7 +13505,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
13656
13505
  let pkg = {};
13657
13506
  if (metaExists) {
13658
13507
  try {
13659
- const parsed = JSON.parse(await readFile35(file, "utf8"));
13508
+ const parsed = JSON.parse(await readFile34(file, "utf8"));
13660
13509
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
13661
13510
  pkg = parsed;
13662
13511
  }
@@ -13758,11 +13607,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
13758
13607
  async function importExtensionFromFile(projectDir, filePath) {
13759
13608
  const ext = path24.extname(filePath).toLowerCase();
13760
13609
  if (ext === ".zip") {
13761
- const buf = await readFile35(filePath);
13610
+ const buf = await readFile34(filePath);
13762
13611
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
13763
13612
  }
13764
13613
  if (ext === ".css") {
13765
- const css = await readFile35(filePath, "utf8");
13614
+ const css = await readFile34(filePath, "utf8");
13766
13615
  return importExtensionFromCssText(projectDir, css, prettify(path24.basename(filePath, ".css")));
13767
13616
  }
13768
13617
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -16143,7 +15992,7 @@ async function scorchedEarth(options) {
16143
15992
  }
16144
15993
  }
16145
15994
  // src/lib/publish/run-publish.ts
16146
- import { readdir as readdir12, stat as stat12 } from "node:fs/promises";
15995
+ import { readdir as readdir12, stat as stat11 } from "node:fs/promises";
16147
15996
  import path33 from "node:path";
16148
15997
 
16149
15998
  // src/lib/publish/command-runner.ts
@@ -16374,7 +16223,7 @@ var drivethrurpgProvider = {
16374
16223
  };
16375
16224
 
16376
16225
  // src/lib/publish/providers/gdrive.ts
16377
- import { mkdtemp as mkdtemp3, readFile as readFile36, readdir as readdir11, rm as rm9, stat as stat9, writeFile as writeFile16 } from "node:fs/promises";
16226
+ import { mkdtemp as mkdtemp3, readFile as readFile35, readdir as readdir11, rm as rm9, stat as stat8, writeFile as writeFile16 } from "node:fs/promises";
16378
16227
  import { tmpdir as tmpdir3 } from "node:os";
16379
16228
  import path30 from "node:path";
16380
16229
  import { Zip, ZipDeflate } from "fflate";
@@ -16766,12 +16615,12 @@ async function collectZipEntries(root, dir, out, onWarn) {
16766
16615
  await collectZipEntries(root, full, out, onWarn);
16767
16616
  } else if (entry.isFile()) {
16768
16617
  const rel = path30.relative(root, full).split(path30.sep).join("/");
16769
- out[rel] = await readFile36(full);
16618
+ out[rel] = await readFile35(full);
16770
16619
  } else if (entry.isSymbolicLink()) {
16771
- const resolved = await stat9(full).catch(() => null);
16620
+ const resolved = await stat8(full).catch(() => null);
16772
16621
  if (resolved?.isFile()) {
16773
16622
  const rel = path30.relative(root, full).split(path30.sep).join("/");
16774
- out[rel] = await readFile36(full);
16623
+ out[rel] = await readFile35(full);
16775
16624
  } else if (resolved?.isDirectory()) {
16776
16625
  await collectZipEntries(root, full, out, onWarn);
16777
16626
  } else {
@@ -16841,7 +16690,7 @@ var gdriveProvider = {
16841
16690
  cleanup: async () => {}
16842
16691
  };
16843
16692
  try {
16844
- const artifactStat = await stat9(source.filePath);
16693
+ const artifactStat = await stat8(source.filePath);
16845
16694
  const about = await driveAbout(fetchImpl, accessToken);
16846
16695
  if (about.quota.limitBytes != null && about.quota.freeBytes != null) {
16847
16696
  if (artifactStat.size > about.quota.freeBytes) {
@@ -16886,7 +16735,7 @@ var gdriveProvider = {
16886
16735
  };
16887
16736
 
16888
16737
  // src/lib/publish/butler.ts
16889
- import { chmod as chmod2, mkdir as mkdir19, stat as stat10, writeFile as writeFile17 } from "node:fs/promises";
16738
+ import { chmod as chmod2, mkdir as mkdir19, stat as stat9, writeFile as writeFile17 } from "node:fs/promises";
16890
16739
  import path31 from "node:path";
16891
16740
  import { unzipSync as unzipSync2 } from "fflate";
16892
16741
  var BUTLER_DOWNLOAD_TIMEOUT_MS = 300000;
@@ -16907,7 +16756,7 @@ function butlerBinaryName(platform2 = process.platform) {
16907
16756
  }
16908
16757
  async function fileExists2(p) {
16909
16758
  try {
16910
- return (await stat10(p)).isFile();
16759
+ return (await stat9(p)).isFile();
16911
16760
  } catch {
16912
16761
  return false;
16913
16762
  }
@@ -17150,7 +16999,7 @@ var kdpProvider = {
17150
16999
  };
17151
17000
 
17152
17001
  // src/lib/publish/providers/local-folder.ts
17153
- import { cp as cp7, mkdir as mkdir20, stat as stat11 } from "node:fs/promises";
17002
+ import { cp as cp7, mkdir as mkdir20, stat as stat10 } from "node:fs/promises";
17154
17003
  import path32 from "node:path";
17155
17004
  var LOCAL_PUBLISH_DEFAULT_DIR = "dist";
17156
17005
  var info8 = {
@@ -17177,7 +17026,7 @@ var localFolderProvider = {
17177
17026
  async preflight(req) {
17178
17027
  const dir = localPublishDir(req);
17179
17028
  try {
17180
- const s = await stat11(dir);
17029
+ const s = await stat10(dir);
17181
17030
  if (!s.isDirectory()) {
17182
17031
  return [
17183
17032
  { severity: "error", id: "local/not-a-folder", message: `${dir} exists but is not a folder.` }
@@ -17472,7 +17321,7 @@ var PDF_HINT = "Build the PDF first (gutterpress build, or export it from the ap
17472
17321
  var HTML_HINT = "Build the website export first (gutterpress build --format html).";
17473
17322
  async function artifactIssues(artifact) {
17474
17323
  try {
17475
- const s = await stat12(artifact.path);
17324
+ const s = await stat11(artifact.path);
17476
17325
  if (artifact.format === "pdf" && !s.isFile()) {
17477
17326
  return [
17478
17327
  {
@@ -17518,7 +17367,7 @@ async function artifactIssues(artifact) {
17518
17367
  var BUILD_FINGERPRINT_FILENAME = "build-fingerprint.json";
17519
17368
  async function htmlDirIssues(dir) {
17520
17369
  const issues = [];
17521
- const isThere = async (rel) => stat12(path33.join(dir, rel)).then(() => true, () => false);
17370
+ const isThere = async (rel) => stat11(path33.join(dir, rel)).then(() => true, () => false);
17522
17371
  if (!await isThere(BOOK_HTML)) {
17523
17372
  issues.push({
17524
17373
  severity: "error",
@@ -17637,7 +17486,7 @@ async function disconnectPublishCredential(key, deps, options = {}) {
17637
17486
  }
17638
17487
  }
17639
17488
  // src/lib/publish/selections.ts
17640
- import { chmod as chmod3, mkdir as mkdir21, readFile as readFile37, writeFile as writeFile18 } from "node:fs/promises";
17489
+ import { chmod as chmod3, mkdir as mkdir21, readFile as readFile36, writeFile as writeFile18 } from "node:fs/promises";
17641
17490
  import path34 from "node:path";
17642
17491
  class PublishSelectionsStore {
17643
17492
  filePath;
@@ -17647,7 +17496,7 @@ class PublishSelectionsStore {
17647
17496
  }
17648
17497
  async read() {
17649
17498
  try {
17650
- const raw = await readFile37(this.filePath, "utf8");
17499
+ const raw = await readFile36(this.filePath, "utf8");
17651
17500
  const parsed = JSON.parse(raw);
17652
17501
  if (parsed && typeof parsed === "object") {
17653
17502
  return {