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
@@ -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-y9bcya7r.js";
18
20
  import {
19
21
  DEBOUNCE,
20
22
  UsageError,
21
23
  resolvePort
22
- } from "./cli-7z29z4wz.js";
24
+ } from "./cli-hn9pk0hp.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-x4p99w8d.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)}.`);
@@ -4899,346 +4926,6 @@ function inspectionFailed(checkId, message, extra = {}) {
4899
4926
  };
4900
4927
  }
4901
4928
 
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
4929
  // src/checks/pdf/qpdf-structure.ts
5243
4930
  var check = {
5244
4931
  id: "pdf.structure.qpdf",
@@ -5513,9 +5200,9 @@ var check4 = {
5513
5200
  registerCheck(check4);
5514
5201
 
5515
5202
  // src/lib/pdf-parse.ts
5516
- import { readFile as readFile8 } from "node:fs/promises";
5203
+ import { readFile as readFile7 } from "node:fs/promises";
5517
5204
  async function readPdfBytes(pdfPath) {
5518
- return readFile8(pdfPath, "latin1");
5205
+ return readFile7(pdfPath, "latin1");
5519
5206
  }
5520
5207
  function parseInkCov(out) {
5521
5208
  const lines = out.split(/\r?\n/).filter((l) => l.includes("CMYK"));
@@ -5776,7 +5463,7 @@ var check8 = {
5776
5463
  registerCheck(check8);
5777
5464
 
5778
5465
  // src/checks/pdf/black-text.ts
5779
- import { readFile as readFile9 } from "node:fs/promises";
5466
+ import { readFile as readFile8 } from "node:fs/promises";
5780
5467
  var check9 = {
5781
5468
  id: "pdf.print.black-text",
5782
5469
  name: "Black Text",
@@ -5789,7 +5476,7 @@ var check9 = {
5789
5476
  return [];
5790
5477
  try {
5791
5478
  const { findRichBlackText, LARGE_TEXT_PT } = await import("./black-text-gray-7dz61456.js");
5792
- const found = await findRichBlackText(await readFile9(ctx.pdfPath));
5479
+ const found = await findRichBlackText(await readFile8(ctx.pdfPath));
5793
5480
  if (found.runs === 0)
5794
5481
  return [];
5795
5482
  return [
@@ -6178,7 +5865,7 @@ var check18 = {
6178
5865
  registerCheck(check18);
6179
5866
 
6180
5867
  // src/checks/source/markdownlint.ts
6181
- import { readFile as readFile10 } from "node:fs/promises";
5868
+ import { readFile as readFile9 } from "node:fs/promises";
6182
5869
  import { lint } from "markdownlint/sync";
6183
5870
  import { parse as parseYaml2 } from "yaml";
6184
5871
 
@@ -6211,7 +5898,7 @@ function stripJsonComments(s) {
6211
5898
  return s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
6212
5899
  }
6213
5900
  async function loadConfig(configPath) {
6214
- const raw = await readFile10(configPath, "utf8");
5901
+ const raw = await readFile9(configPath, "utf8");
6215
5902
  let parsed;
6216
5903
  if (configPath.endsWith(".json")) {
6217
5904
  parsed = JSON.parse(raw);
@@ -6272,7 +5959,7 @@ registerCheck(check19);
6272
5959
 
6273
5960
  // src/checks/source/htmlhint.ts
6274
5961
  import { existsSync as existsSync7 } from "node:fs";
6275
- import { readFile as readFile11 } from "node:fs/promises";
5962
+ import { readFile as readFile10 } from "node:fs/promises";
6276
5963
  import htmlhint from "htmlhint";
6277
5964
  var { HTMLHint } = htmlhint;
6278
5965
  var CONFIG_NAMES2 = [".htmlhintrc"];
@@ -6292,8 +5979,8 @@ var check20 = {
6292
5979
  const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
6293
5980
  if (!resolvedConfig && !configPath)
6294
5981
  return [];
6295
- const ruleset = resolvedConfig ? JSON.parse(await readFile11(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
6296
- const html = await readFile11(ctx.htmlPath, "utf8");
5982
+ const ruleset = resolvedConfig ? JSON.parse(await readFile10(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
5983
+ const html = await readFile10(ctx.htmlPath, "utf8");
6297
5984
  const messages = HTMLHint.verify(html, ruleset);
6298
5985
  return messages.map((m) => ({
6299
5986
  checkId: check20.id,
@@ -6308,7 +5995,7 @@ var check20 = {
6308
5995
  registerCheck(check20);
6309
5996
 
6310
5997
  // src/checks/source/stylelint.ts
6311
- import { readFile as readFile12 } from "node:fs/promises";
5998
+ import { readFile as readFile11 } from "node:fs/promises";
6312
5999
 
6313
6000
  // src/lib/printsafe.ts
6314
6001
  import postcss from "postcss";
@@ -6650,7 +6337,7 @@ function checkCssRaw(css, from) {
6650
6337
  warnings.push({
6651
6338
  rule: ruleRiskyProps,
6652
6339
  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.`,
6340
+ 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
6341
  ...nodeLoc(decl)
6655
6342
  });
6656
6343
  } else if (isUnpairedFitContentWidth(decl)) {
@@ -6715,7 +6402,7 @@ var check21 = {
6715
6402
  for (const file of files) {
6716
6403
  let css;
6717
6404
  try {
6718
- css = await readFile12(file, "utf8");
6405
+ css = await readFile11(file, "utf8");
6719
6406
  } catch (err) {
6720
6407
  results.push({
6721
6408
  checkId: check21.id,
@@ -6742,7 +6429,7 @@ var check21 = {
6742
6429
  registerCheck(check21);
6743
6430
 
6744
6431
  // src/checks/source/css-ownership.ts
6745
- import { readFile as readFile13 } from "node:fs/promises";
6432
+ import { readFile as readFile12 } from "node:fs/promises";
6746
6433
 
6747
6434
  // src/lib/style-resolver.ts
6748
6435
  import { existsSync as existsSync8 } from "node:fs";
@@ -6810,7 +6497,7 @@ import postcss2 from "postcss";
6810
6497
  import { parse as parseYaml3 } from "yaml";
6811
6498
  var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
6812
6499
  async function loadContract(configPath) {
6813
- const raw = await readFile13(configPath, "utf8");
6500
+ const raw = await readFile12(configPath, "utf8");
6814
6501
  const parsed = parseYaml3(raw);
6815
6502
  if (parsed && typeof parsed === "object")
6816
6503
  return parsed;
@@ -6961,7 +6648,7 @@ var check22 = {
6961
6648
  continue;
6962
6649
  let css;
6963
6650
  try {
6964
- css = await readFile13(absFile, "utf8");
6651
+ css = await readFile12(absFile, "utf8");
6965
6652
  } catch {
6966
6653
  continue;
6967
6654
  }
@@ -7008,7 +6695,7 @@ import { dirname as dirname3, resolve as resolve5 } from "node:path";
7008
6695
 
7009
6696
  // src/lib/asset-inline.ts
7010
6697
  import { createHash as createHash2 } from "node:crypto";
7011
- import { readFile as readFile14 } from "node:fs/promises";
6698
+ import { readFile as readFile13 } from "node:fs/promises";
7012
6699
  import path7 from "node:path";
7013
6700
  import postcss3 from "postcss";
7014
6701
  var FONT_EXTS = new Set([".woff2", ".woff", ".ttf", ".otf"]);
@@ -7073,7 +6760,7 @@ function mimeFor(ext) {
7073
6760
  }
7074
6761
  async function readOrThrow(absPath, kind, referencedFrom) {
7075
6762
  try {
7076
- return await readFile14(absPath);
6763
+ return await readFile13(absPath);
7077
6764
  } catch {
7078
6765
  throw new BuildError(`Missing ${kind}: ${absPath}
7079
6766
  referenced from ${referencedFrom}
@@ -7216,7 +6903,7 @@ async function collectStyleDependencies(projectDir, stylePaths) {
7216
6903
  found.add(abs);
7217
6904
  let source;
7218
6905
  try {
7219
- source = await readFile14(abs, "utf-8");
6906
+ source = await readFile13(abs, "utf-8");
7220
6907
  } catch {
7221
6908
  return;
7222
6909
  }
@@ -7293,7 +6980,7 @@ async function inlineShapeUrls(html, baseDir) {
7293
6980
  continue;
7294
6981
  let bytes;
7295
6982
  try {
7296
- bytes = await readFile14(abs);
6983
+ bytes = await readFile13(abs);
7297
6984
  } catch {
7298
6985
  continue;
7299
6986
  }
@@ -7311,7 +6998,7 @@ async function inlineShapeUrls(html, baseDir) {
7311
6998
  }
7312
6999
 
7313
7000
  // src/checks/source/local-ref-parser.ts
7314
- import { readFile as readFile15 } from "node:fs/promises";
7001
+ import { readFile as readFile14 } from "node:fs/promises";
7315
7002
  function newlineOffsets(content) {
7316
7003
  const offsets = [];
7317
7004
  for (let i = 0;i < content.length; i++)
@@ -7380,7 +7067,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
7380
7067
  for (const file of files) {
7381
7068
  let refs;
7382
7069
  try {
7383
- refs = collectRenderedLocalRefs(await readFile15(file, "utf8"));
7070
+ refs = collectRenderedLocalRefs(await readFile14(file, "utf8"));
7384
7071
  } catch {
7385
7072
  results.push(inspectionFailed(checkId, `Could not read source file: ${file}`, { file }));
7386
7073
  continue;
@@ -7731,7 +7418,7 @@ var check24 = {
7731
7418
  registerCheck(check24);
7732
7419
 
7733
7420
  // src/checks/source/accessibility-alt-text.ts
7734
- import { readFile as readFile16 } from "node:fs/promises";
7421
+ import { readFile as readFile15 } from "node:fs/promises";
7735
7422
  var check25 = {
7736
7423
  id: "source.accessibility.alt-text",
7737
7424
  name: "Image Alt Text",
@@ -7745,7 +7432,7 @@ var check25 = {
7745
7432
  const results = [];
7746
7433
  for (const file of files) {
7747
7434
  try {
7748
- const content = await readFile16(file, "utf8");
7435
+ const content = await readFile15(file, "utf8");
7749
7436
  const lines = content.split(`
7750
7437
  `);
7751
7438
  let inFence = false;
@@ -7789,7 +7476,7 @@ function extractImageAlts(line) {
7789
7476
  registerCheck(check25);
7790
7477
 
7791
7478
  // src/checks/source/accessibility-heading-order.ts
7792
- import { readFile as readFile17 } from "node:fs/promises";
7479
+ import { readFile as readFile16 } from "node:fs/promises";
7793
7480
  var check26 = {
7794
7481
  id: "source.accessibility.heading-order",
7795
7482
  name: "Heading Order",
@@ -7803,7 +7490,7 @@ var check26 = {
7803
7490
  const results = [];
7804
7491
  for (const file of files) {
7805
7492
  try {
7806
- const content = await readFile17(file, "utf8");
7493
+ const content = await readFile16(file, "utf8");
7807
7494
  const lines = content.split(`
7808
7495
  `);
7809
7496
  let inFence = false;
@@ -7845,7 +7532,7 @@ var check26 = {
7845
7532
  registerCheck(check26);
7846
7533
 
7847
7534
  // src/checks/source/layout-markers.ts
7848
- import { readFile as readFile18 } from "node:fs/promises";
7535
+ import { readFile as readFile17 } from "node:fs/promises";
7849
7536
 
7850
7537
  // src/lib/unknown-classes.ts
7851
7538
  import postcss4 from "postcss";
@@ -7948,7 +7635,7 @@ async function collectKnownClasses(ctx, plugins) {
7948
7635
  const sheets = (await collectStyleDependencies(ctx.inputDir, roots)).filter((f) => /\.css$/i.test(f));
7949
7636
  for (const sheet of sheets) {
7950
7637
  try {
7951
- addCssClasses(await readFile18(sheet, "utf8"), names);
7638
+ addCssClasses(await readFile17(sheet, "utf8"), names);
7952
7639
  } catch {}
7953
7640
  }
7954
7641
  return names;
@@ -7972,7 +7659,7 @@ var check27 = {
7972
7659
  const knownClasses = () => known ??= collectKnownClasses(ctx, plugins);
7973
7660
  for (const file of files) {
7974
7661
  try {
7975
- const content = await readFile18(file, "utf8");
7662
+ const content = await readFile17(file, "utf8");
7976
7663
  const env = {};
7977
7664
  const tokens = md.parse(content, env);
7978
7665
  md.renderer.render(tokens, md.options, env);
@@ -8012,7 +7699,7 @@ var check27 = {
8012
7699
  registerCheck(check27);
8013
7700
 
8014
7701
  // src/checks/source/merge-markers.ts
8015
- import { readFile as readFile19 } from "node:fs/promises";
7702
+ import { readFile as readFile18 } from "node:fs/promises";
8016
7703
  import path9 from "node:path";
8017
7704
 
8018
7705
  // src/checks/asset/extensions.ts
@@ -8054,7 +7741,7 @@ var check28 = {
8054
7741
  for (const file of textFiles) {
8055
7742
  let content;
8056
7743
  try {
8057
- content = await readFile19(file, "utf8");
7744
+ content = await readFile18(file, "utf8");
8058
7745
  } catch {
8059
7746
  results.push(inspectionFailed(check28.id, `Could not read source file: ${file}`, { file }));
8060
7747
  continue;
@@ -8114,7 +7801,7 @@ var check28 = {
8114
7801
  registerCheck(check28);
8115
7802
 
8116
7803
  // src/checks/source/characters.ts
8117
- import { readFile as readFile20 } from "node:fs/promises";
7804
+ import { readFile as readFile19 } from "node:fs/promises";
8118
7805
 
8119
7806
  // src/checks/source/character-table.ts
8120
7807
  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 +7924,7 @@ var check29 = {
8237
7924
  for (const file of (ctx.markdownFiles ?? []).slice().sort()) {
8238
7925
  let content;
8239
7926
  try {
8240
- content = await readFile20(file, "utf8");
7927
+ content = await readFile19(file, "utf8");
8241
7928
  } catch {
8242
7929
  results.push(inspectionFailed(check29.id, `Could not read source file: ${file}`, { file }));
8243
7930
  continue;
@@ -8277,16 +7964,16 @@ var check29 = {
8277
7964
  registerCheck(check29);
8278
7965
 
8279
7966
  // src/checks/asset/image-file-size.ts
8280
- import { stat as stat4 } from "node:fs/promises";
7967
+ import { stat as stat3 } from "node:fs/promises";
8281
7968
 
8282
7969
  // src/lib/image-inspect.ts
8283
- import { stat as stat3, readFile as readFile21 } from "node:fs/promises";
7970
+ import { stat as stat2, readFile as readFile20 } from "node:fs/promises";
8284
7971
  var DEFAULT_DPI = 72;
8285
7972
  var cache = new Map;
8286
7973
  async function inspectImage(path10) {
8287
7974
  let st;
8288
7975
  try {
8289
- st = await stat3(path10);
7976
+ st = await stat2(path10);
8290
7977
  } catch {
8291
7978
  return null;
8292
7979
  }
@@ -8295,7 +7982,7 @@ async function inspectImage(path10) {
8295
7982
  return hit.info;
8296
7983
  let info3 = null;
8297
7984
  try {
8298
- const buf = await readFile21(path10);
7985
+ const buf = await readFile20(path10);
8299
7986
  info3 = parseImage(buf);
8300
7987
  } catch {
8301
7988
  info3 = null;
@@ -8517,7 +8204,7 @@ var check30 = {
8517
8204
  const results = [];
8518
8205
  for (const file of files) {
8519
8206
  try {
8520
- const info3 = await stat4(file);
8207
+ const info3 = await stat3(file);
8521
8208
  if (info3.size > maxSize) {
8522
8209
  const sizeMb = (info3.size / 1e6).toFixed(1);
8523
8210
  const maxMb = (maxSize / 1e6).toFixed(1);
@@ -8855,7 +8542,7 @@ var check37 = {
8855
8542
  registerCheck(check37);
8856
8543
 
8857
8544
  // src/checks/heuristic/section-density.ts
8858
- import { readFile as readFile22 } from "node:fs/promises";
8545
+ import { readFile as readFile21 } from "node:fs/promises";
8859
8546
  var check38 = {
8860
8547
  id: "heuristic.chunking.section-density",
8861
8548
  name: "Section Density",
@@ -8872,7 +8559,7 @@ var check38 = {
8872
8559
  const results = [];
8873
8560
  for (const file of files) {
8874
8561
  try {
8875
- const content = await readFile22(file, "utf8");
8562
+ const content = await readFile21(file, "utf8");
8876
8563
  const lines = content.split(`
8877
8564
  `);
8878
8565
  let currentSectionStart = 0;
@@ -9176,7 +8863,7 @@ async function restoreForCommand(dir, options) {
9176
8863
  }
9177
8864
 
9178
8865
  // src/lib/markdown/index.ts
9179
- import { readdir as readdir3, readFile as readFile23, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
8866
+ import { readdir as readdir3, readFile as readFile22, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
9180
8867
 
9181
8868
  // src/lib/remote-auth/sync-messages.ts
9182
8869
  function isOnlineSibling(filepath) {
@@ -9318,7 +9005,7 @@ ${css.trim()}
9318
9005
  }
9319
9006
  return assembleBookHtml({
9320
9007
  files,
9321
- readText: (relPath) => readFile23(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
9008
+ readText: (relPath) => readFile22(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
9322
9009
  projectCss: inlined.css,
9323
9010
  preloadImages: cssAssetCopies.map((c) => c.to),
9324
9011
  title: opts.title,
@@ -9350,7 +9037,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
9350
9037
  }
9351
9038
 
9352
9039
  // src/lib/markdownlint-fix.ts
9353
- import { readFile as readFile24, writeFile as writeFile3 } from "node:fs/promises";
9040
+ import { readFile as readFile23, writeFile as writeFile3 } from "node:fs/promises";
9354
9041
  import { applyFixes } from "markdownlint";
9355
9042
  import { lint as lint2 } from "markdownlint/sync";
9356
9043
  async function uncommittedAmong(inputDir, files) {
@@ -9390,7 +9077,7 @@ async function applyMarkdownlintFixes(ctx) {
9390
9077
  for (const [file, violations] of Object.entries(results)) {
9391
9078
  if (!Array.isArray(violations) || violations.length === 0)
9392
9079
  continue;
9393
- const before = await readFile24(file, "utf8");
9080
+ const before = await readFile23(file, "utf8");
9394
9081
  const after = applyFixes(before, violations);
9395
9082
  if (after !== before)
9396
9083
  pending.push({ file, content: after });
@@ -9903,7 +9590,7 @@ import { randomBytes } from "node:crypto";
9903
9590
 
9904
9591
  // src/lib/build-fingerprint.ts
9905
9592
  import * as fs from "node:fs";
9906
- import { mkdir as mkdir5, readFile as readFile25, writeFile as writeFile4 } from "node:fs/promises";
9593
+ import { mkdir as mkdir5, readFile as readFile24, writeFile as writeFile4 } from "node:fs/promises";
9907
9594
  import { createHash as createHash4 } from "node:crypto";
9908
9595
  import path12 from "node:path";
9909
9596
  import git from "isomorphic-git";
@@ -9990,8 +9677,8 @@ async function getEngineBundleHash() {
9990
9677
  getAssetPath("engine/gutterpress-agent.js")
9991
9678
  ]);
9992
9679
  const [viewerBytes, agentBytes] = await Promise.all([
9993
- readFile25(viewerPath),
9994
- readFile25(agentPath)
9680
+ readFile24(viewerPath),
9681
+ readFile24(agentPath)
9995
9682
  ]);
9996
9683
  const hash = createHash4("sha256");
9997
9684
  hash.update(viewerBytes);
@@ -10054,7 +9741,10 @@ async function writeBuildFingerprint(input) {
10054
9741
  resolvedFlavor: input.pdfx.resolvedFlavor,
10055
9742
  iccPath: input.pdfx.iccPath,
10056
9743
  stripAnnotations: input.pdfx.stripAnnotations,
10057
- blackText: input.pdfx.blackText ?? null
9744
+ blackText: input.pdfx.blackText ?? null,
9745
+ transparency: input.pdfx.transparency ?? null,
9746
+ flattenPages: input.pdfx.flattenPages ?? null,
9747
+ rasterDpi: input.pdfx.rasterDpi ?? null
10058
9748
  },
10059
9749
  outputDir: input.recordedOutputDir ? path12.resolve(input.recordedOutputDir) : outputDir
10060
9750
  },
@@ -10361,7 +10051,7 @@ class PdfOutput {
10361
10051
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10362
10052
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10363
10053
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10364
- const { buildNativePdf } = await import("./engine-9xb1ky8d.js");
10054
+ const { buildNativePdf } = await import("./engine-yxnnp3hx.js");
10365
10055
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10366
10056
  title: config.title,
10367
10057
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10383,12 +10073,40 @@ class PdfOutput {
10383
10073
  if (pdfxMode) {
10384
10074
  const icc = opts.iccPath ?? config.pdfx.icc;
10385
10075
  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).");
10076
+ const rawBytes = await fsp2.readFile(rawPdf);
10077
+ const transparent = await transparentPages(rawBytes);
10078
+ const blankGradientPages = await findPostScriptShadingPages(rawBytes);
10079
+ const flattenedGradientPages = blankGradientPages.filter((n) => transparent.includes(n));
10080
+ const vectorGradientPages = blankGradientPages.filter((n) => !transparent.includes(n));
10081
+ 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.";
10082
+ const pageList = (pages) => `Page${pages.length === 1 ? "" : "s"} ${pages.join(", ")}`;
10083
+ if (vectorGradientPages.length > 0) {
10084
+ 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}`);
10085
+ }
10086
+ if (flattenedGradientPages.length > 0 && config.pdfx.transparency !== "preserve-text") {
10087
+ 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}`);
10088
+ }
10089
+ const flattenPages = config.pdfx.flattenPages;
10090
+ if (transparent.length > 0 && config.pdfx.transparency === "preserve-text") {
10091
+ const { unsplittableTextReasons } = await import("./pdf-inspect-zdmy7qca.js");
10092
+ const reasons = await unsplittableTextReasons(rawBytes, transparent).catch((err) => {
10093
+ 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.`);
10094
+ return null;
10095
+ });
10096
+ if (reasons) {
10097
+ const plan = planTransparentPages(transparent, reasons, flattenPages, blankGradientPages);
10098
+ await flattenTransparentPages(rawPdf, plan.split, stage, config.pdfx.rasterDpi);
10099
+ reportTransparentPlan(plan, pdfxMode, config.pdfx.rasterDpi ?? 300, pageList);
10100
+ }
10101
+ }
10102
+ const unused = flattenPages.filter((n) => !transparent.includes(n));
10103
+ if (flattenPages.length > 0 && config.pdfx.transparency !== "preserve-text") {
10104
+ log.warn("pdfx.flattenPages has no effect without pdfx.transparency: preserve-text.");
10105
+ } else if (unused.length > 0) {
10106
+ log.warn(`pdfx.flattenPages lists ${pageList(unused).toLowerCase()}, which ${unused.length === 1 ? "has" : "have"} no transparency (or do not exist): nothing to flatten there.`);
10388
10107
  }
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.");
10108
+ if (transparent.length > 0 && config.pdfx.transparency !== "preserve-text") {
10109
+ 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
10110
  }
10393
10111
  const shouldStrip = opts.stripAnnotations ?? config.pdfx.stripAnnotations;
10394
10112
  shouldStripAnnotations = shouldStrip;
@@ -10431,7 +10149,10 @@ class PdfOutput {
10431
10149
  resolvedFlavor: config.pdfx.flavor,
10432
10150
  iccPath: effectiveIccPath,
10433
10151
  stripAnnotations: shouldStripAnnotations,
10434
- blackText: pdfxMode ? config.pdfx.blackText : null
10152
+ blackText: pdfxMode ? config.pdfx.blackText : null,
10153
+ transparency: pdfxMode ? config.pdfx.transparency : null,
10154
+ flattenPages: pdfxMode ? config.pdfx.flattenPages : null,
10155
+ rasterDpi: pdfxMode ? config.pdfx.rasterDpi ?? null : null
10435
10156
  }
10436
10157
  }, `Wrote: ${reportedPdf}`, { htmlPath: htmlFile, pdfPath: reportedPdf }, pdfName, engineDiagnostics);
10437
10158
  } finally {
@@ -10468,6 +10189,35 @@ async function runBuild(opts) {
10468
10189
  await fsp2.rm(ctx.workDir, { recursive: true, force: true });
10469
10190
  }
10470
10191
  }
10192
+ var FALLBACK_CAUSE = {
10193
+ listed: "listed in pdfx.flattenPages",
10194
+ "text-transparency": "text drawn with transparency or blending (opacity, mix-blend-mode, a soft mask, a translucent text-shadow); keep that text opaque",
10195
+ "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",
10196
+ "text-pattern-or-clip": 'text filled with a pattern or used as a clip (SVG fill="url(#…)", gradient text, background-clip: text)',
10197
+ gradient: "a repeating or conic gradient; use a linear or radial gradient, or a small tiled image"
10198
+ };
10199
+ function reportTransparentPlan({ split, artOnly, kept }, pdfxMode, dpi, pageList) {
10200
+ const one = (pages, singular, plural) => pages.length === 1 ? singular : plural;
10201
+ if (split.length > 0) {
10202
+ 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.`);
10203
+ }
10204
+ if (artOnly.length > 0) {
10205
+ 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).`);
10206
+ }
10207
+ if (kept.size > 0) {
10208
+ const pages = [...kept.keys()];
10209
+ const byCause = new Map;
10210
+ for (const [n, cause] of kept)
10211
+ byCause.set(cause, [...byCause.get(cause) ?? [], n]);
10212
+ const lines = [...byCause].map(([cause, ns]) => ` - ${one(ns, "page", "pages")} ${ns.join(", ")}: ${FALLBACK_CAUSE[cause]}`);
10213
+ 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:
10214
+ ` + lines.join(`
10215
+ `));
10216
+ }
10217
+ if (split.length === 0 && [...kept.values()].some((cause) => cause !== "listed")) {
10218
+ 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).");
10219
+ }
10220
+ }
10471
10221
 
10472
10222
  // src/lib/open-path.ts
10473
10223
  import { spawn } from "node:child_process";
@@ -10501,7 +10251,7 @@ function openPath(filePath) {
10501
10251
  import path16 from "path";
10502
10252
  import { tmpdir } from "os";
10503
10253
  import { randomBytes as randomBytes2 } from "crypto";
10504
- import { readdir as readdir4, readFile as readFile26, writeFile as writeFile5 } from "node:fs/promises";
10254
+ import { readdir as readdir4, readFile as readFile25, writeFile as writeFile5 } from "node:fs/promises";
10505
10255
 
10506
10256
  // src/utils/file-utils.ts
10507
10257
  import { promises as fs3 } from "fs";
@@ -11062,7 +10812,7 @@ async function cleanupOrphanTempDirs() {
11062
10812
  const dir = path16.join(TEMP_DIR_BASE, entry);
11063
10813
  const pidFile = path16.join(dir, PID_FILE_NAME);
11064
10814
  try {
11065
- const raw = await readFile26(pidFile, "utf8");
10815
+ const raw = await readFile25(pidFile, "utf8");
11066
10816
  const pid = parseInt(raw.trim(), 10);
11067
10817
  if (!isProcessAlive(pid)) {
11068
10818
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -11169,7 +10919,7 @@ function createServerState(inputPath, tempDir, config, options) {
11169
10919
  import http from "node:http";
11170
10920
  import net from "node:net";
11171
10921
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
11172
- import { readFile as readFile27, stat as stat5 } from "node:fs/promises";
10922
+ import { readFile as readFile26, stat as stat4 } from "node:fs/promises";
11173
10923
  import path17 from "path";
11174
10924
  import { WebSocket, WebSocketServer } from "ws";
11175
10925
  var HMR_PATH = "/__gutterpress-hmr";
@@ -11228,7 +10978,7 @@ async function findAvailablePort(startPort, host = "127.0.0.1") {
11228
10978
  async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders = {}) {
11229
10979
  let filePath = absPath;
11230
10980
  try {
11231
- const s = await stat5(filePath);
10981
+ const s = await stat4(filePath);
11232
10982
  if (s.isDirectory())
11233
10983
  filePath = path17.join(filePath, "index.html");
11234
10984
  } catch {
@@ -11238,7 +10988,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
11238
10988
  }
11239
10989
  let data;
11240
10990
  try {
11241
- data = await readFile27(filePath);
10991
+ data = await readFile26(filePath);
11242
10992
  } catch {
11243
10993
  res.writeHead(404);
11244
10994
  res.end("Not Found");
@@ -11267,7 +11017,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11267
11017
  }
11268
11018
  let fileStat;
11269
11019
  try {
11270
- fileStat = await stat5(absPath, { bigint: true });
11020
+ fileStat = await stat4(absPath, { bigint: true });
11271
11021
  } catch {
11272
11022
  await serveStatic(absPath, res);
11273
11023
  return;
@@ -11287,7 +11037,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
11287
11037
  let data;
11288
11038
  if (!validator || validator.stamp !== stamp) {
11289
11039
  try {
11290
- data = await readFile27(absPath);
11040
+ data = await readFile26(absPath);
11291
11041
  } catch {
11292
11042
  await serveStatic(absPath, res);
11293
11043
  return;
@@ -11315,7 +11065,7 @@ async function isMissingReferencedImage(pathname, absPath, refs) {
11315
11065
  if (!refs.has(imageRefKey(pathname)))
11316
11066
  return false;
11317
11067
  try {
11318
- await stat5(absPath);
11068
+ await stat4(absPath);
11319
11069
  return false;
11320
11070
  } catch (err) {
11321
11071
  return err?.code === "ENOENT";
@@ -11597,7 +11347,7 @@ async function startPreviewServer(options) {
11597
11347
  }
11598
11348
 
11599
11349
  // 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";
11350
+ 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
11351
  import { constants as FS, existsSync as existsSync12 } from "node:fs";
11602
11352
  import path18 from "node:path";
11603
11353
 
@@ -11796,7 +11546,7 @@ async function scaffoldProject(options) {
11796
11546
  async function firstSourceFile(manifestPath) {
11797
11547
  let text;
11798
11548
  try {
11799
- text = await readFile28(manifestPath, "utf8");
11549
+ text = await readFile27(manifestPath, "utf8");
11800
11550
  } catch {
11801
11551
  return;
11802
11552
  }
@@ -11808,7 +11558,7 @@ async function firstSourceFile(manifestPath) {
11808
11558
  return m ? m[1].trim() : undefined;
11809
11559
  }
11810
11560
  async function fillTemplateFile(filePath, substitutions) {
11811
- let text = await readFile28(filePath, "utf8");
11561
+ let text = await readFile27(filePath, "utf8");
11812
11562
  for (const [token, value] of Object.entries(substitutions)) {
11813
11563
  text = text.split(token).join(value);
11814
11564
  }
@@ -11816,7 +11566,7 @@ async function fillTemplateFile(filePath, substitutions) {
11816
11566
  }
11817
11567
 
11818
11568
  // src/lib/extension-scaffold.ts
11819
- import { access as access2, mkdir as mkdir8, readFile as readFile29, writeFile as writeFile7 } from "node:fs/promises";
11569
+ import { access as access2, mkdir as mkdir8, readFile as readFile28, writeFile as writeFile7 } from "node:fs/promises";
11820
11570
  import { constants as FS2 } from "node:fs";
11821
11571
  import path19 from "node:path";
11822
11572
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -11933,7 +11683,7 @@ async function scaffoldExtension(options) {
11933
11683
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
11934
11684
  const dest = path19.join(extensionDir, relDest);
11935
11685
  await mkdir8(path19.dirname(dest), { recursive: true });
11936
- const text = await readFile29(src, "utf8");
11686
+ const text = await readFile28(src, "utf8");
11937
11687
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
11938
11688
  await writeFile7(dest, substitute(text, table), "utf8");
11939
11689
  written.push(relDest);
@@ -11978,7 +11728,7 @@ var SKIP_ENTRIES = new Set([
11978
11728
 
11979
11729
  // src/lib/extension-import.ts
11980
11730
  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";
11731
+ import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile29, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11982
11732
  import { tmpdir as tmpdir2 } from "node:os";
11983
11733
  import path20 from "node:path";
11984
11734
  import { unzipSync } from "fflate";
@@ -12051,7 +11801,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
12051
11801
  if (!existsSync13(cssPath)) {
12052
11802
  throw new Error("A theme must contain a theme.css file.");
12053
11803
  }
12054
- const css = await readFile30(cssPath, "utf8");
11804
+ const css = await readFile29(cssPath, "utf8");
12055
11805
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
12056
11806
  if (reject) {
12057
11807
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -12079,7 +11829,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
12079
11829
  for (let i = 0;i < extraSheets.length; i++) {
12080
11830
  const rel = extraSheets[i];
12081
11831
  const sheetPath = extraSheetPaths[i];
12082
- const sheetCss = await readFile30(sheetPath, "utf8");
11832
+ const sheetCss = await readFile29(sheetPath, "utf8");
12083
11833
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
12084
11834
  if (sheetReject) {
12085
11835
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -12112,7 +11862,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
12112
11862
  let pkg = {};
12113
11863
  if (metaExists) {
12114
11864
  try {
12115
- const parsed = JSON.parse(await readFile30(file, "utf8"));
11865
+ const parsed = JSON.parse(await readFile29(file, "utf8"));
12116
11866
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
12117
11867
  pkg = parsed;
12118
11868
  }
@@ -12214,11 +11964,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
12214
11964
  async function importExtensionFromFile(projectDir, filePath) {
12215
11965
  const ext = path20.extname(filePath).toLowerCase();
12216
11966
  if (ext === ".zip") {
12217
- const buf = await readFile30(filePath);
11967
+ const buf = await readFile29(filePath);
12218
11968
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
12219
11969
  }
12220
11970
  if (ext === ".css") {
12221
- const css = await readFile30(filePath, "utf8");
11971
+ const css = await readFile29(filePath, "utf8");
12222
11972
  return importExtensionFromCssText(projectDir, css, prettify(path20.basename(filePath, ".css")));
12223
11973
  }
12224
11974
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -13002,7 +12752,7 @@ var drivethrurpgProvider = {
13002
12752
  };
13003
12753
 
13004
12754
  // 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";
12755
+ import { mkdtemp as mkdtemp3, readFile as readFile30, readdir as readdir6, rm as rm5, stat as stat6, writeFile as writeFile10 } from "node:fs/promises";
13006
12756
  import { tmpdir as tmpdir3 } from "node:os";
13007
12757
  import path22 from "node:path";
13008
12758
  import { Zip, ZipDeflate } from "fflate";
@@ -13394,12 +13144,12 @@ async function collectZipEntries(root, dir, out, onWarn) {
13394
13144
  await collectZipEntries(root, full, out, onWarn);
13395
13145
  } else if (entry.isFile()) {
13396
13146
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13397
- out[rel] = await readFile31(full);
13147
+ out[rel] = await readFile30(full);
13398
13148
  } else if (entry.isSymbolicLink()) {
13399
- const resolved = await stat7(full).catch(() => null);
13149
+ const resolved = await stat6(full).catch(() => null);
13400
13150
  if (resolved?.isFile()) {
13401
13151
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13402
- out[rel] = await readFile31(full);
13152
+ out[rel] = await readFile30(full);
13403
13153
  } else if (resolved?.isDirectory()) {
13404
13154
  await collectZipEntries(root, full, out, onWarn);
13405
13155
  } else {
@@ -13469,7 +13219,7 @@ var gdriveProvider = {
13469
13219
  cleanup: async () => {}
13470
13220
  };
13471
13221
  try {
13472
- const artifactStat = await stat7(source.filePath);
13222
+ const artifactStat = await stat6(source.filePath);
13473
13223
  const about = await driveAbout(fetchImpl, accessToken);
13474
13224
  if (about.quota.limitBytes != null && about.quota.freeBytes != null) {
13475
13225
  if (artifactStat.size > about.quota.freeBytes) {
@@ -13514,7 +13264,7 @@ var gdriveProvider = {
13514
13264
  };
13515
13265
 
13516
13266
  // src/lib/publish/butler.ts
13517
- import { chmod, mkdir as mkdir11, stat as stat8, writeFile as writeFile11 } from "node:fs/promises";
13267
+ import { chmod, mkdir as mkdir11, stat as stat7, writeFile as writeFile11 } from "node:fs/promises";
13518
13268
  import path23 from "node:path";
13519
13269
  import { unzipSync as unzipSync2 } from "fflate";
13520
13270
  var BUTLER_DOWNLOAD_TIMEOUT_MS = 300000;
@@ -13535,7 +13285,7 @@ function butlerBinaryName(platform = process.platform) {
13535
13285
  }
13536
13286
  async function fileExists2(p) {
13537
13287
  try {
13538
- return (await stat8(p)).isFile();
13288
+ return (await stat7(p)).isFile();
13539
13289
  } catch {
13540
13290
  return false;
13541
13291
  }
@@ -13778,7 +13528,7 @@ var kdpProvider = {
13778
13528
  };
13779
13529
 
13780
13530
  // src/lib/publish/providers/local-folder.ts
13781
- import { cp as cp4, mkdir as mkdir12, stat as stat9 } from "node:fs/promises";
13531
+ import { cp as cp4, mkdir as mkdir12, stat as stat8 } from "node:fs/promises";
13782
13532
  import path24 from "node:path";
13783
13533
  var LOCAL_PUBLISH_DEFAULT_DIR = "dist";
13784
13534
  var info8 = {
@@ -13805,7 +13555,7 @@ var localFolderProvider = {
13805
13555
  async preflight(req) {
13806
13556
  const dir = localPublishDir(req);
13807
13557
  try {
13808
- const s = await stat9(dir);
13558
+ const s = await stat8(dir);
13809
13559
  if (!s.isDirectory()) {
13810
13560
  return [
13811
13561
  { severity: "error", id: "local/not-a-folder", message: `${dir} exists but is not a folder.` }
@@ -14060,7 +13810,7 @@ function publishProviderFor(id) {
14060
13810
  }
14061
13811
 
14062
13812
  // src/lib/publish/run-publish.ts
14063
- import { readdir as readdir7, stat as stat10 } from "node:fs/promises";
13813
+ import { readdir as readdir7, stat as stat9 } from "node:fs/promises";
14064
13814
  import path25 from "node:path";
14065
13815
  function resolvePublishFormat(info10, providerConfig) {
14066
13816
  if (info10.formats && info10.formats.length > 0) {
@@ -14102,7 +13852,7 @@ var PDF_HINT = "Build the PDF first (gutterpress build, or export it from the ap
14102
13852
  var HTML_HINT = "Build the website export first (gutterpress build --format html).";
14103
13853
  async function artifactIssues(artifact) {
14104
13854
  try {
14105
- const s = await stat10(artifact.path);
13855
+ const s = await stat9(artifact.path);
14106
13856
  if (artifact.format === "pdf" && !s.isFile()) {
14107
13857
  return [
14108
13858
  {
@@ -14148,7 +13898,7 @@ async function artifactIssues(artifact) {
14148
13898
  var BUILD_FINGERPRINT_FILENAME = "build-fingerprint.json";
14149
13899
  async function htmlDirIssues(dir) {
14150
13900
  const issues = [];
14151
- const isThere = async (rel) => stat10(path25.join(dir, rel)).then(() => true, () => false);
13901
+ const isThere = async (rel) => stat9(path25.join(dir, rel)).then(() => true, () => false);
14152
13902
  if (!await isThere(BOOK_HTML)) {
14153
13903
  issues.push({
14154
13904
  severity: "error",
@@ -14386,7 +14136,7 @@ import git8 from "isomorphic-git";
14386
14136
  // src/lib/remote-auth/scorched-earth.ts
14387
14137
  import git9 from "isomorphic-git";
14388
14138
  // src/lib/publish/selections.ts
14389
- import { chmod as chmod2, mkdir as mkdir13, readFile as readFile32, writeFile as writeFile12 } from "node:fs/promises";
14139
+ import { chmod as chmod2, mkdir as mkdir13, readFile as readFile31, writeFile as writeFile12 } from "node:fs/promises";
14390
14140
  import path26 from "node:path";
14391
14141
  class PublishSelectionsStore {
14392
14142
  filePath;
@@ -14396,7 +14146,7 @@ class PublishSelectionsStore {
14396
14146
  }
14397
14147
  async read() {
14398
14148
  try {
14399
- const raw = await readFile32(this.filePath, "utf8");
14149
+ const raw = await readFile31(this.filePath, "utf8");
14400
14150
  const parsed = JSON.parse(raw);
14401
14151
  if (parsed && typeof parsed === "object") {
14402
14152
  return {