gutterpress 0.11.18 → 0.11.19-alpha.1

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.
package/dist/api/index.js CHANGED
@@ -137,7 +137,7 @@ import {
137
137
  testRemoteAccess,
138
138
  updateExtensions,
139
139
  validateProjectExtensions
140
- } from "../index-e1hbmb5a.js";
140
+ } from "../index-n4ek1358.js";
141
141
  import"../index-ht1bcqdd.js";
142
142
  import {
143
143
  BuildError
@@ -146,7 +146,7 @@ import {
146
146
  DEFAULT_PRINT_OPTS,
147
147
  assertMilestone,
148
148
  readyProbeExpr
149
- } from "../index-rzm1phg0.js";
149
+ } from "../index-bt0qws5c.js";
150
150
  import {
151
151
  AUTO_SNAPSHOT_MESSAGE,
152
152
  HISTORY_PAGE_LIMIT,
@@ -1,24 +1,24 @@
1
1
  import {
2
2
  runBuild,
3
3
  splitOutPath
4
- } from "./cli-6q7trt7z.js";
4
+ } from "./cli-mkn97gxp.js";
5
5
  import {
6
6
  log
7
- } from "./cli-xe29z84b.js";
7
+ } from "./cli-5j0a5hmc.js";
8
8
  import {
9
9
  UsageError,
10
10
  parseFormat,
11
11
  parsePdfxFlavor,
12
12
  rejectExtraPositionals,
13
13
  rejectUnknownFlags
14
- } from "./cli-rg8jq04a.js";
14
+ } from "./cli-n3nd3sav.js";
15
15
  import"./cli-ht1bcqdd.js";
16
- import"./cli-ptez7zcc.js";
16
+ import"./cli-xx94a1k6.js";
17
17
  import {
18
18
  BuildError
19
19
  } from "./cli-46ycxe6r.js";
20
- import"./cli-kkzka31p.js";
21
- import"./cli-7fkhgtfn.js";
20
+ import"./cli-eyyggect.js";
21
+ import"./cli-asap4t1z.js";
22
22
  import"./cli-5qr3z0qa.js";
23
23
  import"./cli-v5mp7a6q.js";
24
24
  import"./cli-37x76zdn.js";
@@ -4,8 +4,8 @@ import {
4
4
  assertMilestone,
5
5
  launchChromium,
6
6
  readyProbeExpr
7
- } from "./cli-kkzka31p.js";
8
- import"./cli-7fkhgtfn.js";
7
+ } from "./cli-eyyggect.js";
8
+ import"./cli-asap4t1z.js";
9
9
  import"./cli-37x76zdn.js";
10
10
  export {
11
11
  readyProbeExpr,
@@ -4,7 +4,7 @@ import {
4
4
  assertMilestone,
5
5
  launchChromium,
6
6
  readyProbeExpr
7
- } from "./index-rzm1phg0.js";
7
+ } from "./index-bt0qws5c.js";
8
8
  import"./index-37x76zdn.js";
9
9
  export {
10
10
  readyProbeExpr,
@@ -1,14 +1,16 @@
1
1
  import {
2
2
  package_default
3
- } from "./cli-rg8jq04a.js";
3
+ } from "./cli-n3nd3sav.js";
4
4
  import {
5
+ enhancedPath,
5
6
  execCapture,
6
7
  findTool,
7
8
  fullInstallHint,
8
9
  isToolAvailable,
9
10
  resolveChromiumExecutable,
10
- run
11
- } from "./cli-7fkhgtfn.js";
11
+ run,
12
+ spawnCapture
13
+ } from "./cli-asap4t1z.js";
12
14
  import {
13
15
  __require
14
16
  } from "./cli-37x76zdn.js";
@@ -170,6 +172,58 @@ async function hasLiveTransparency(pdfBytes) {
170
172
  }
171
173
  return false;
172
174
  }
175
+ async function findPostScriptShadingPages(pdfBytes) {
176
+ const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } = await import("pdf-lib");
177
+ let doc;
178
+ try {
179
+ doc = await PDFDocument.load(pdfBytes, {
180
+ updateMetadata: false,
181
+ throwOnInvalidObject: false
182
+ });
183
+ } catch {
184
+ return [];
185
+ }
186
+ const ctx = doc.context;
187
+ const asDict = (obj) => {
188
+ const o = obj instanceof PDFRef ? ctx.lookup(obj) : obj;
189
+ return o instanceof PDFRawStream ? o.dict : o instanceof PDFDict ? o : undefined;
190
+ };
191
+ const num = (obj) => {
192
+ const o = obj instanceof PDFRef ? ctx.lookup(obj) : obj;
193
+ return o instanceof PDFNumber ? o.asNumber() : undefined;
194
+ };
195
+ const isPostScriptShading = (shading) => {
196
+ const dict = asDict(shading);
197
+ if (!dict || num(dict.get(PDFName.of("ShadingType"))) !== 1)
198
+ return false;
199
+ return num(asDict(dict.get(PDFName.of("Function")))?.get(PDFName.of("FunctionType"))) === 4;
200
+ };
201
+ const subDicts = (resources, key) => {
202
+ const group = asDict(resources.get(PDFName.of(key)));
203
+ return group ? group.entries().map(([, v]) => v) : [];
204
+ };
205
+ const seen = new Set;
206
+ const usesPostScriptShading = (resources) => {
207
+ if (!resources || seen.has(resources))
208
+ return false;
209
+ seen.add(resources);
210
+ if (subDicts(resources, "Shading").some(isPostScriptShading))
211
+ return true;
212
+ for (const pattern of subDicts(resources, "Pattern")) {
213
+ const dict = asDict(pattern);
214
+ if (dict && isPostScriptShading(dict.get(PDFName.of("Shading"))))
215
+ return true;
216
+ }
217
+ return subDicts(resources, "XObject").some((x) => usesPostScriptShading(asDict(asDict(x)?.get(PDFName.of("Resources")))));
218
+ };
219
+ const pages = [];
220
+ doc.getPages().forEach((page, i) => {
221
+ seen.clear();
222
+ if (usesPostScriptShading(asDict(page.node.Resources())))
223
+ pages.push(i + 1);
224
+ });
225
+ return pages;
226
+ }
173
227
  async function stripAnnotations(pdfPath, stagingDir) {
174
228
  const tmp = join(stagingDir, "annotations-stripped.pdf");
175
229
  try {
@@ -297,7 +351,22 @@ async function convertToPdfxCmyk(inputPdf, outPdf, config) {
297
351
  if (!ghostscript) {
298
352
  throw new Error("Ghostscript executable not found. Install Ghostscript or set GHOSTSCRIPT_PATH to its command-line executable.");
299
353
  }
300
- await run(ghostscript, args);
354
+ const { code, signal, stdout, stderr } = await spawnCapture(ghostscript, args, {
355
+ env: { ...process.env, PATH: enhancedPath }
356
+ });
357
+ if (signal) {
358
+ const version = await execCapture(ghostscript, ["--version"]).then((r) => r.stdout.trim()).catch(() => "unknown version");
359
+ throw new Error(`Ghostscript ${version} crashed (${signal}) while converting to PDF/X. This is a Ghostscript bug, not a problem with the PDF. Ghostscript 10.02.1 has been seen to crash in the PDF 1.3 transparency flattening PDF/X requires, on a page with an alpha-channel image. Flattening that image against its background avoided it there; a newer Ghostscript may too. A plain PDF (--format pdf) does not run this step.`);
360
+ }
361
+ if (code !== 0) {
362
+ throw new Error(`${ghostscript} ${args.join(" ")} exited ${code}
363
+ ${stdout}${stderr}`);
364
+ }
365
+ const at = stderr.indexOf("The following warnings");
366
+ if (at >= 0) {
367
+ log.warn(`Ghostscript reported problems converting to PDF/X:
368
+ ${stderr.slice(at).trim()}`);
369
+ }
301
370
  } finally {
302
371
  await unlink(tmpDef).catch(() => {});
303
372
  }
@@ -514,4 +583,4 @@ async function getSystemDiagnostics() {
514
583
  };
515
584
  }
516
585
 
517
- export { setLogLevel, debug, info, warn, error, log, resolveGhostscript, hasLiveTransparency, stripAnnotations, stampCreator, convertToPdfxCmyk, PACKAGE_META, PACKAGE_VERSION, defaultConfigDir, FileTokenStore, getSystemDiagnostics };
586
+ export { setLogLevel, debug, info, warn, error, log, resolveGhostscript, hasLiveTransparency, findPostScriptShadingPages, stripAnnotations, stampCreator, convertToPdfxCmyk, PACKAGE_META, PACKAGE_VERSION, defaultConfigDir, FileTokenStore, getSystemDiagnostics };
@@ -16,11 +16,12 @@ function run(cmd, args, opts = {}) {
16
16
  return new Promise((resolve, reject) => {
17
17
  const p = spawn(cmd, args, { stdio: "inherit", cwd: opts.cwd, env: enhancedEnv() });
18
18
  p.on("error", reject);
19
- p.on("exit", (code) => {
19
+ p.on("exit", (code, signal) => {
20
20
  if (code === 0)
21
21
  return resolve();
22
22
  const cwd = opts.cwd ?? process.cwd();
23
- reject(new Error(`${cmd} ${args.join(" ")} (cwd=${cwd}) exited ${code}`));
23
+ const what = signal ? `was killed by signal ${signal}` : `exited ${code}`;
24
+ reject(Object.assign(new Error(`${cmd} ${args.join(" ")} (cwd=${cwd}) ${what}`), { signal }));
24
25
  });
25
26
  });
26
27
  }
@@ -240,4 +241,4 @@ async function requireChromiumExecutable() {
240
241
  `));
241
242
  }
242
243
 
243
- export { run, spawnCapture, execCapture, isToolAvailable, findTool, INSTALL_HINTS, fullInstallHint, resolveChromiumExecutable, requireChromiumExecutable };
244
+ export { enhancedPath, run, spawnCapture, execCapture, isToolAvailable, findTool, INSTALL_HINTS, fullInstallHint, resolveChromiumExecutable, requireChromiumExecutable };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  requireChromiumExecutable
3
- } from "./cli-7fkhgtfn.js";
3
+ } from "./cli-asap4t1z.js";
4
4
 
5
5
  // src/engine/shared/cdp.ts
6
6
  import { spawn } from "node:child_process";
@@ -5,6 +5,7 @@ import {
5
5
  debug,
6
6
  defaultConfigDir,
7
7
  error,
8
+ findPostScriptShadingPages,
8
9
  hasLiveTransparency,
9
10
  info,
10
11
  log,
@@ -13,12 +14,12 @@ import {
13
14
  stampCreator,
14
15
  stripAnnotations,
15
16
  warn
16
- } from "./cli-xe29z84b.js";
17
+ } from "./cli-5j0a5hmc.js";
17
18
  import {
18
19
  DEBOUNCE,
19
20
  UsageError,
20
21
  resolvePort
21
- } from "./cli-rg8jq04a.js";
22
+ } from "./cli-n3nd3sav.js";
22
23
  import {
23
24
  blackTextToGray
24
25
  } from "./cli-ht1bcqdd.js";
@@ -27,7 +28,7 @@ import {
27
28
  getAssetPath,
28
29
  getAssetsDir,
29
30
  lastPageMode
30
- } from "./cli-ptez7zcc.js";
31
+ } from "./cli-xx94a1k6.js";
31
32
  import {
32
33
  BuildError,
33
34
  EXIT_CODES
@@ -39,7 +40,7 @@ import {
39
40
  requireChromiumExecutable,
40
41
  resolveChromiumExecutable,
41
42
  spawnCapture
42
- } from "./cli-7fkhgtfn.js";
43
+ } from "./cli-asap4t1z.js";
43
44
  import {
44
45
  PLUGINS_DIR,
45
46
  VENDORED_NPM_DIR,
@@ -6289,6 +6290,9 @@ function isDroppedPageGradient(decl) {
6289
6290
  return false;
6290
6291
  return isInPageBox(decl);
6291
6292
  }
6293
+ function isPostScriptShadingGradient(decl) {
6294
+ return /(repeating-(linear|radial|conic)|conic)-gradient\(/i.test(decl.value);
6295
+ }
6292
6296
  function isInPageMarginBox(decl) {
6293
6297
  const box = decl.parent;
6294
6298
  if (!box || box.type !== "atrule")
@@ -6537,6 +6541,13 @@ function checkCssRaw(css, from) {
6537
6541
  message: `"${decl.prop}: ${decl.value}" on @page paints nothing in print — Chromium drops a gradient in the @page box's own background, with no error and a valid-looking PDF. A solid colour there paints the whole sheet; the same gradient also works on "html" (it covers the content area, not the sheet) or in a margin box.`,
6538
6542
  ...nodeLoc(decl)
6539
6543
  });
6544
+ } else if (isPostScriptShadingGradient(decl)) {
6545
+ warnings.push({
6546
+ rule: ruleRiskyProps,
6547
+ severity: "warning",
6548
+ 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.`,
6549
+ ...nodeLoc(decl)
6550
+ });
6540
6551
  } else if (isUnpairedFitContentWidth(decl)) {
6541
6552
  warnings.push({
6542
6553
  rule: ruleRiskyProps,
@@ -10082,7 +10093,7 @@ class PdfOutput {
10082
10093
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10083
10094
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10084
10095
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10085
- const { buildNativePdf } = await import("./engine-wtnakejg.js");
10096
+ const { buildNativePdf } = await import("./engine-fe0n7v9p.js");
10086
10097
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10087
10098
  title: config.title,
10088
10099
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10107,6 +10118,10 @@ class PdfOutput {
10107
10118
  if (await hasLiveTransparency(await fsp2.readFile(rawPdf))) {
10108
10119
  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.");
10109
10120
  }
10121
+ const blankGradientPages = await findPostScriptShadingPages(await fsp2.readFile(rawPdf));
10122
+ if (blankGradientPages.length > 0) {
10123
+ 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.");
10124
+ }
10110
10125
  const shouldStrip = opts.stripAnnotations ?? config.pdfx.stripAnnotations;
10111
10126
  shouldStripAnnotations = shouldStrip;
10112
10127
  if (shouldStrip) {
@@ -10161,7 +10176,7 @@ async function runBuild(opts) {
10161
10176
  if (ctx.format !== "html") {
10162
10177
  await preflightBuildTools(ctx.format, opts, ctx.config);
10163
10178
  }
10164
- const launch = opts.engineBrowser ?? (async () => (await import("./cdp-smdx6mr3.js")).launchChromium());
10179
+ const launch = opts.engineBrowser ?? (async () => (await import("./cdp-1hn3xsk2.js")).launchChromium());
10165
10180
  const browserPromise = ctx.format === "html" ? null : launch();
10166
10181
  browserPromise?.catch(() => {});
10167
10182
  try {
@@ -4,7 +4,7 @@ import {
4
4
  // package.json
5
5
  var package_default = {
6
6
  name: "gutterpress",
7
- version: "0.11.18",
7
+ version: "0.11.19-alpha.1",
8
8
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
9
9
  author: "itlackey",
10
10
  license: "MPL-2.0",
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launchChromium
3
- } from "./cli-kkzka31p.js";
3
+ } from "./cli-eyyggect.js";
4
4
 
5
5
  // src/engine/compiler/build.ts
6
6
  import { readFile as readFile2 } from "node:fs/promises";
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  UsageError,
7
7
  package_default,
8
8
  rejectUnknownFlags
9
- } from "./cli-rg8jq04a.js";
9
+ } from "./cli-n3nd3sav.js";
10
10
  import {
11
11
  EXIT_CODES
12
12
  } from "./cli-46ycxe6r.js";
@@ -18,14 +18,14 @@ import {
18
18
  import { defineCommand, parseArgs, runMain } from "citty";
19
19
  import { statSync } from "node:fs";
20
20
  var SUBCOMMANDS = {
21
- new: () => import("./new-rnnavkc8.js").then((m) => m.default),
22
- preview: () => import("./preview-f9csmejj.js").then((m) => m.default),
23
- build: () => import("./build-67bj810g.js").then((m) => m.default),
24
- publish: () => import("./publish-40yvnb62.js").then((m) => m.default),
25
- validate: () => import("./validate-pt921n11.js").then((m) => m.default),
26
- preflight: () => import("./preflight-4769yg8n.js").then((m) => m.default),
27
- doctor: () => import("./doctor-gcqb6xtj.js").then((m) => m.default),
28
- ext: () => import("./ext-pkaghr3f.js").then((m) => m.default)
21
+ new: () => import("./new-0wyn55dn.js").then((m) => m.default),
22
+ preview: () => import("./preview-2x113vyc.js").then((m) => m.default),
23
+ build: () => import("./build-vgz3pr8y.js").then((m) => m.default),
24
+ publish: () => import("./publish-r379k1hr.js").then((m) => m.default),
25
+ validate: () => import("./validate-fje7ca9g.js").then((m) => m.default),
26
+ preflight: () => import("./preflight-re9v4skc.js").then((m) => m.default),
27
+ doctor: () => import("./doctor-gxf8ty3n.js").then((m) => m.default),
28
+ ext: () => import("./ext-7j388f5z.js").then((m) => m.default)
29
29
  };
30
30
  var VERSION = package_default.version;
31
31
  var main = defineCommand({
@@ -50,7 +50,7 @@ async function preflightRequiredInvocations(rawArgs) {
50
50
  const [command, ...commandArgs] = rawArgs;
51
51
  try {
52
52
  if (command === "new") {
53
- const { newArgs } = await import("./new-rnnavkc8.js");
53
+ const { newArgs } = await import("./new-0wyn55dn.js");
54
54
  rejectUnknownFlags(commandArgs, newArgs, "new");
55
55
  const parsed = parseArgs(commandArgs, {
56
56
  ...newArgs,
@@ -62,7 +62,7 @@ async function preflightRequiredInvocations(rawArgs) {
62
62
  return;
63
63
  }
64
64
  if (command === "preflight") {
65
- const { preflightArgs } = await import("./preflight-4769yg8n.js");
65
+ const { preflightArgs } = await import("./preflight-re9v4skc.js");
66
66
  rejectUnknownFlags(commandArgs, preflightArgs, "preflight");
67
67
  const parsed = parseArgs(commandArgs, {
68
68
  ...preflightArgs,
@@ -82,7 +82,7 @@ async function preflightRequiredInvocations(rawArgs) {
82
82
  }
83
83
  if (subcommand.startsWith("-"))
84
84
  return;
85
- const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-pkaghr3f.js");
85
+ const { EXT_SUBCOMMANDS, extAddArgs, extRemoveArgs, extEnableArgs, extDisableArgs } = await import("./ext-7j388f5z.js");
86
86
  if (!EXT_SUBCOMMANDS.includes(subcommand)) {
87
87
  throw new UsageError(`gutterpress ext: unknown command "${subcommand}"`);
88
88
  }
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  getSystemDiagnostics,
3
3
  log
4
- } from "./cli-xe29z84b.js";
4
+ } from "./cli-5j0a5hmc.js";
5
5
  import {
6
6
  UsageError,
7
7
  rejectExtraPositionals,
8
8
  rejectUnknownFlags
9
- } from "./cli-rg8jq04a.js";
9
+ } from "./cli-n3nd3sav.js";
10
10
  import"./cli-46ycxe6r.js";
11
- import"./cli-7fkhgtfn.js";
11
+ import"./cli-asap4t1z.js";
12
12
  import"./cli-37x76zdn.js";
13
13
 
14
14
  // src/commands/doctor.ts
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  build
3
- } from "./cli-ptez7zcc.js";
3
+ } from "./cli-xx94a1k6.js";
4
4
  import {
5
5
  BuildError
6
6
  } from "./cli-46ycxe6r.js";
7
- import"./cli-kkzka31p.js";
8
- import"./cli-7fkhgtfn.js";
7
+ import"./cli-eyyggect.js";
8
+ import"./cli-asap4t1z.js";
9
9
  import"./cli-37x76zdn.js";
10
10
 
11
11
  // src/lib/engine.ts
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  build
3
- } from "./index-n8vs6pss.js";
3
+ } from "./index-b866g2ph.js";
4
4
  import {
5
5
  BuildError
6
6
  } from "./index-jt552wtf.js";
7
- import"./index-rzm1phg0.js";
7
+ import"./index-bt0qws5c.js";
8
8
  import"./index-37x76zdn.js";
9
9
 
10
10
  // src/lib/engine.ts
@@ -12,22 +12,22 @@ import {
12
12
  searchNpmExtensions,
13
13
  setExtensionEnabled,
14
14
  updateExtensions
15
- } from "./cli-6q7trt7z.js";
16
- import"./cli-xe29z84b.js";
15
+ } from "./cli-mkn97gxp.js";
16
+ import"./cli-5j0a5hmc.js";
17
17
  import {
18
18
  UsageError,
19
19
  exitForUsage,
20
20
  rejectExtraPositionals,
21
21
  rejectUnknownFlags,
22
22
  resolveProjectDir
23
- } from "./cli-rg8jq04a.js";
23
+ } from "./cli-n3nd3sav.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-ptez7zcc.js";
25
+ import"./cli-xx94a1k6.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";
29
- import"./cli-kkzka31p.js";
30
- import"./cli-7fkhgtfn.js";
29
+ import"./cli-eyyggect.js";
30
+ import"./cli-asap4t1z.js";
31
31
  import"./cli-5qr3z0qa.js";
32
32
  import"./cli-v5mp7a6q.js";
33
33
  import"./cli-37x76zdn.js";
@@ -22,7 +22,7 @@ import {
22
22
  } from "./index-jt552wtf.js";
23
23
  import {
24
24
  launchChromium
25
- } from "./index-rzm1phg0.js";
25
+ } from "./index-bt0qws5c.js";
26
26
 
27
27
  // src/engine/compiler/build.ts
28
28
  import { readFile } from "node:fs/promises";
@@ -26,11 +26,12 @@ function run(cmd, args, opts = {}) {
26
26
  return new Promise((resolve, reject) => {
27
27
  const p = spawn(cmd, args, { stdio: "inherit", cwd: opts.cwd, env: enhancedEnv() });
28
28
  p.on("error", reject);
29
- p.on("exit", (code) => {
29
+ p.on("exit", (code, signal) => {
30
30
  if (code === 0)
31
31
  return resolve();
32
32
  const cwd = opts.cwd ?? process.cwd();
33
- reject(new Error(`${cmd} ${args.join(" ")} (cwd=${cwd}) exited ${code}`));
33
+ const what = signal ? `was killed by signal ${signal}` : `exited ${code}`;
34
+ reject(Object.assign(new Error(`${cmd} ${args.join(" ")} (cwd=${cwd}) ${what}`), { signal }));
34
35
  });
35
36
  });
36
37
  }
@@ -498,4 +499,4 @@ class SessionImpl {
498
499
  }
499
500
  }
500
501
 
501
- export { run, spawnCapture, execCapture, isToolAvailable, findTool, INSTALL_HINTS, fullInstallHint, resolveChromiumExecutable, requireChromiumExecutable, REQUIRED_MILESTONE, assertMilestone, readyProbeExpr, DEFAULT_PRINT_OPTS, launchChromium };
502
+ export { enhancedPath, run, spawnCapture, execCapture, isToolAvailable, findTool, INSTALL_HINTS, fullInstallHint, resolveChromiumExecutable, requireChromiumExecutable, REQUIRED_MILESTONE, assertMilestone, readyProbeExpr, DEFAULT_PRINT_OPTS, launchChromium };
@@ -12,6 +12,7 @@ import {
12
12
  } from "./index-jt552wtf.js";
13
13
  import {
14
14
  INSTALL_HINTS,
15
+ enhancedPath,
15
16
  execCapture,
16
17
  findTool,
17
18
  fullInstallHint,
@@ -20,7 +21,7 @@ import {
20
21
  resolveChromiumExecutable,
21
22
  run,
22
23
  spawnCapture
23
- } from "./index-rzm1phg0.js";
24
+ } from "./index-bt0qws5c.js";
24
25
  import {
25
26
  PLUGINS_DIR,
26
27
  VENDORED_NPM_DIR,
@@ -5774,6 +5775,58 @@ async function hasLiveTransparency(pdfBytes) {
5774
5775
  }
5775
5776
  return false;
5776
5777
  }
5778
+ async function findPostScriptShadingPages(pdfBytes) {
5779
+ const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } = await import("pdf-lib");
5780
+ let doc;
5781
+ try {
5782
+ doc = await PDFDocument.load(pdfBytes, {
5783
+ updateMetadata: false,
5784
+ throwOnInvalidObject: false
5785
+ });
5786
+ } catch {
5787
+ return [];
5788
+ }
5789
+ const ctx = doc.context;
5790
+ const asDict = (obj) => {
5791
+ const o = obj instanceof PDFRef ? ctx.lookup(obj) : obj;
5792
+ return o instanceof PDFRawStream ? o.dict : o instanceof PDFDict ? o : undefined;
5793
+ };
5794
+ const num = (obj) => {
5795
+ const o = obj instanceof PDFRef ? ctx.lookup(obj) : obj;
5796
+ return o instanceof PDFNumber ? o.asNumber() : undefined;
5797
+ };
5798
+ const isPostScriptShading = (shading) => {
5799
+ const dict = asDict(shading);
5800
+ if (!dict || num(dict.get(PDFName.of("ShadingType"))) !== 1)
5801
+ return false;
5802
+ return num(asDict(dict.get(PDFName.of("Function")))?.get(PDFName.of("FunctionType"))) === 4;
5803
+ };
5804
+ const subDicts = (resources, key) => {
5805
+ const group = asDict(resources.get(PDFName.of(key)));
5806
+ return group ? group.entries().map(([, v]) => v) : [];
5807
+ };
5808
+ const seen = new Set;
5809
+ const usesPostScriptShading = (resources) => {
5810
+ if (!resources || seen.has(resources))
5811
+ return false;
5812
+ seen.add(resources);
5813
+ if (subDicts(resources, "Shading").some(isPostScriptShading))
5814
+ return true;
5815
+ for (const pattern of subDicts(resources, "Pattern")) {
5816
+ const dict = asDict(pattern);
5817
+ if (dict && isPostScriptShading(dict.get(PDFName.of("Shading"))))
5818
+ return true;
5819
+ }
5820
+ return subDicts(resources, "XObject").some((x) => usesPostScriptShading(asDict(asDict(x)?.get(PDFName.of("Resources")))));
5821
+ };
5822
+ const pages = [];
5823
+ doc.getPages().forEach((page, i) => {
5824
+ seen.clear();
5825
+ if (usesPostScriptShading(asDict(page.node.Resources())))
5826
+ pages.push(i + 1);
5827
+ });
5828
+ return pages;
5829
+ }
5777
5830
  async function stripAnnotations(pdfPath, stagingDir) {
5778
5831
  const tmp = join3(stagingDir, "annotations-stripped.pdf");
5779
5832
  try {
@@ -5901,7 +5954,22 @@ async function convertToPdfxCmyk(inputPdf, outPdf, config) {
5901
5954
  if (!ghostscript) {
5902
5955
  throw new Error("Ghostscript executable not found. Install Ghostscript or set GHOSTSCRIPT_PATH to its command-line executable.");
5903
5956
  }
5904
- await run(ghostscript, args);
5957
+ const { code, signal, stdout, stderr } = await spawnCapture(ghostscript, args, {
5958
+ env: { ...process.env, PATH: enhancedPath }
5959
+ });
5960
+ if (signal) {
5961
+ const version = await execCapture(ghostscript, ["--version"]).then((r) => r.stdout.trim()).catch(() => "unknown version");
5962
+ throw new Error(`Ghostscript ${version} crashed (${signal}) while converting to PDF/X. This is a Ghostscript bug, not a problem with the PDF. Ghostscript 10.02.1 has been seen to crash in the PDF 1.3 transparency flattening PDF/X requires, on a page with an alpha-channel image. Flattening that image against its background avoided it there; a newer Ghostscript may too. A plain PDF (--format pdf) does not run this step.`);
5963
+ }
5964
+ if (code !== 0) {
5965
+ throw new Error(`${ghostscript} ${args.join(" ")} exited ${code}
5966
+ ${stdout}${stderr}`);
5967
+ }
5968
+ const at = stderr.indexOf("The following warnings");
5969
+ if (at >= 0) {
5970
+ log.warn(`Ghostscript reported problems converting to PDF/X:
5971
+ ${stderr.slice(at).trim()}`);
5972
+ }
5905
5973
  } finally {
5906
5974
  await unlink2(tmpDef).catch(() => {});
5907
5975
  }
@@ -5916,7 +5984,7 @@ import git from "isomorphic-git";
5916
5984
  // package.json
5917
5985
  var package_default = {
5918
5986
  name: "gutterpress",
5919
- version: "0.11.18",
5987
+ version: "0.11.19-alpha.1",
5920
5988
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5921
5989
  author: "itlackey",
5922
5990
  license: "MPL-2.0",
@@ -7843,6 +7911,9 @@ function isDroppedPageGradient(decl) {
7843
7911
  return false;
7844
7912
  return isInPageBox(decl);
7845
7913
  }
7914
+ function isPostScriptShadingGradient(decl) {
7915
+ return /(repeating-(linear|radial|conic)|conic)-gradient\(/i.test(decl.value);
7916
+ }
7846
7917
  function isInPageMarginBox(decl) {
7847
7918
  const box = decl.parent;
7848
7919
  if (!box || box.type !== "atrule")
@@ -8091,6 +8162,13 @@ function checkCssRaw(css, from) {
8091
8162
  message: `"${decl.prop}: ${decl.value}" on @page paints nothing in print — Chromium drops a gradient in the @page box's own background, with no error and a valid-looking PDF. A solid colour there paints the whole sheet; the same gradient also works on "html" (it covers the content area, not the sheet) or in a margin box.`,
8092
8163
  ...nodeLoc(decl)
8093
8164
  });
8165
+ } else if (isPostScriptShadingGradient(decl)) {
8166
+ warnings.push({
8167
+ rule: ruleRiskyProps,
8168
+ severity: "warning",
8169
+ 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.`,
8170
+ ...nodeLoc(decl)
8171
+ });
8094
8172
  } else if (isUnpairedFitContentWidth(decl)) {
8095
8173
  warnings.push({
8096
8174
  rule: ruleRiskyProps,
@@ -10662,7 +10740,7 @@ class PdfOutput {
10662
10740
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10663
10741
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10664
10742
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10665
- const { buildNativePdf } = await import("./engine-zy6fzs5a.js");
10743
+ const { buildNativePdf } = await import("./engine-zehw6tkv.js");
10666
10744
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10667
10745
  title: config.title,
10668
10746
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10687,6 +10765,10 @@ class PdfOutput {
10687
10765
  if (await hasLiveTransparency(await fsp2.readFile(rawPdf))) {
10688
10766
  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.");
10689
10767
  }
10768
+ const blankGradientPages = await findPostScriptShadingPages(await fsp2.readFile(rawPdf));
10769
+ if (blankGradientPages.length > 0) {
10770
+ 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.");
10771
+ }
10690
10772
  const shouldStrip = opts.stripAnnotations ?? config.pdfx.stripAnnotations;
10691
10773
  shouldStripAnnotations = shouldStrip;
10692
10774
  if (shouldStrip) {
@@ -10741,7 +10823,7 @@ async function runBuild(opts) {
10741
10823
  if (ctx.format !== "html") {
10742
10824
  await preflightBuildTools(ctx.format, opts, ctx.config);
10743
10825
  }
10744
- const launch = opts.engineBrowser ?? (async () => (await import("./cdp-jg95s674.js")).launchChromium());
10826
+ const launch = opts.engineBrowser ?? (async () => (await import("./cdp-h0wa42cz.js")).launchChromium());
10745
10827
  const browserPromise = ctx.format === "html" ? null : launch();
10746
10828
  browserPromise?.catch(() => {});
10747
10829
  try {
package/dist/index.js CHANGED
@@ -154,11 +154,11 @@ import {
154
154
  testRemoteAccess,
155
155
  updateExtensions,
156
156
  validateProjectExtensions
157
- } from "./index-e1hbmb5a.js";
157
+ } from "./index-n4ek1358.js";
158
158
  import"./index-ht1bcqdd.js";
159
159
  import {
160
160
  BUILD_DIAGNOSTIC_CODES
161
- } from "./index-n8vs6pss.js";
161
+ } from "./index-b866g2ph.js";
162
162
  import {
163
163
  BuildError
164
164
  } from "./index-jt552wtf.js";
@@ -166,7 +166,7 @@ import {
166
166
  DEFAULT_PRINT_OPTS,
167
167
  assertMilestone,
168
168
  readyProbeExpr
169
- } from "./index-rzm1phg0.js";
169
+ } from "./index-bt0qws5c.js";
170
170
  import {
171
171
  AUTO_SNAPSHOT_MESSAGE,
172
172
  HISTORY_PAGE_LIMIT,
@@ -16,7 +16,8 @@ export declare const localBin: string;
16
16
  */
17
17
  export declare const enhancedPath: string;
18
18
  /**
19
- * Spawn a child process, inherit stdio, reject on non-zero exit.
19
+ * Spawn a child process, inherit stdio, reject on non-zero exit or death by
20
+ * signal (the error's `signal` property is set in that case).
20
21
  */
21
22
  export declare function run(cmd: string, args: string[], opts?: {
22
23
  cwd?: string;
@@ -40,6 +40,26 @@ export declare function resolveGhostscript(targetPlatform?: NodeJS.Platform, env
40
40
  * CSS-`opacity` book, which rasterizes to zero embedded fonts.
41
41
  */
42
42
  export declare function hasLiveTransparency(pdfBytes: Uint8Array): Promise<boolean>;
43
+ /**
44
+ * 1-based numbers of the pages that paint a `/ShadingType 1` shading whose
45
+ * function is `/FunctionType 4` (a PostScript calculator function).
46
+ *
47
+ * That is how Chromium (Skia) emits `repeating-linear-gradient()`,
48
+ * `repeating-radial-gradient()`, `repeating-conic-gradient()` and
49
+ * `conic-gradient()`. Ghostscript 10.x
50
+ * reports "error in pattern" on it while converting to PDF/X and paints
51
+ * nothing, so the gradient prints blank (#386). Plain `linear-gradient()` and
52
+ * `radial-gradient()` are ShadingType 2/3 and convert fine. Detecting it lets
53
+ * the build name the affected pages before the conversion drops them; the
54
+ * pipeline does not rewrite the gradient. Delete this when Ghostscript paints
55
+ * these shadings.
56
+ *
57
+ * Walks page resources (and form XObjects they draw) rather than every
58
+ * object, so only shadings a page can actually paint are reported. Like
59
+ * {@link hasLiveTransparency}, it never fails a build: an unparseable PDF
60
+ * reports no pages.
61
+ */
62
+ export declare function findPostScriptShadingPages(pdfBytes: Uint8Array): Promise<number[]>;
43
63
  /**
44
64
  * Strip all annotations from a PDF.
45
65
  *
@@ -7,24 +7,24 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-6q7trt7z.js";
10
+ } from "./cli-mkn97gxp.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-xe29z84b.js";
13
+ } from "./cli-5j0a5hmc.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-rg8jq04a.js";
18
+ } from "./cli-n3nd3sav.js";
19
19
  import"./cli-ht1bcqdd.js";
20
- import"./cli-ptez7zcc.js";
20
+ import"./cli-xx94a1k6.js";
21
21
  import {
22
22
  EXIT_CODES
23
23
  } from "./cli-46ycxe6r.js";
24
- import"./cli-kkzka31p.js";
24
+ import"./cli-eyyggect.js";
25
25
  import {
26
26
  isToolAvailable
27
- } from "./cli-7fkhgtfn.js";
27
+ } from "./cli-asap4t1z.js";
28
28
  import"./cli-5qr3z0qa.js";
29
29
  import"./cli-v5mp7a6q.js";
30
30
  import"./cli-37x76zdn.js";
@@ -2,23 +2,23 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-6q7trt7z.js";
5
+ } from "./cli-mkn97gxp.js";
6
6
  import {
7
7
  log
8
- } from "./cli-xe29z84b.js";
8
+ } from "./cli-5j0a5hmc.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-rg8jq04a.js";
13
+ } from "./cli-n3nd3sav.js";
14
14
  import"./cli-ht1bcqdd.js";
15
- import"./cli-ptez7zcc.js";
15
+ import"./cli-xx94a1k6.js";
16
16
  import {
17
17
  BuildError,
18
18
  EXIT_CODES
19
19
  } from "./cli-46ycxe6r.js";
20
- import"./cli-kkzka31p.js";
21
- import"./cli-7fkhgtfn.js";
20
+ import"./cli-eyyggect.js";
21
+ import"./cli-asap4t1z.js";
22
22
  import"./cli-5qr3z0qa.js";
23
23
  import"./cli-v5mp7a6q.js";
24
24
  import"./cli-37x76zdn.js";
@@ -6,10 +6,10 @@ import {
6
6
  runBuild,
7
7
  splitOutPath,
8
8
  startPreviewServer
9
- } from "./cli-6q7trt7z.js";
9
+ } from "./cli-mkn97gxp.js";
10
10
  import {
11
11
  log
12
- } from "./cli-xe29z84b.js";
12
+ } from "./cli-5j0a5hmc.js";
13
13
  import {
14
14
  UsageError,
15
15
  parseFormat,
@@ -17,14 +17,14 @@ import {
17
17
  rejectExtraPositionals,
18
18
  rejectUnknownFlags,
19
19
  resolvePort
20
- } from "./cli-rg8jq04a.js";
20
+ } from "./cli-n3nd3sav.js";
21
21
  import"./cli-ht1bcqdd.js";
22
- import"./cli-ptez7zcc.js";
22
+ import"./cli-xx94a1k6.js";
23
23
  import {
24
24
  BuildError
25
25
  } from "./cli-46ycxe6r.js";
26
- import"./cli-kkzka31p.js";
27
- import"./cli-7fkhgtfn.js";
26
+ import"./cli-eyyggect.js";
27
+ import"./cli-asap4t1z.js";
28
28
  import"./cli-5qr3z0qa.js";
29
29
  import"./cli-v5mp7a6q.js";
30
30
  import"./cli-37x76zdn.js";
@@ -11,23 +11,23 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-6q7trt7z.js";
14
+ } from "./cli-mkn97gxp.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-xe29z84b.js";
18
+ } from "./cli-5j0a5hmc.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-rg8jq04a.js";
23
+ } from "./cli-n3nd3sav.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-ptez7zcc.js";
25
+ import"./cli-xx94a1k6.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";
29
- import"./cli-kkzka31p.js";
30
- import"./cli-7fkhgtfn.js";
29
+ import"./cli-eyyggect.js";
30
+ import"./cli-asap4t1z.js";
31
31
  import"./cli-5qr3z0qa.js";
32
32
  import"./cli-v5mp7a6q.js";
33
33
  import"./cli-37x76zdn.js";
@@ -1,22 +1,22 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-6q7trt7z.js";
3
+ } from "./cli-mkn97gxp.js";
4
4
  import {
5
5
  log
6
- } from "./cli-xe29z84b.js";
6
+ } from "./cli-5j0a5hmc.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-rg8jq04a.js";
11
+ } from "./cli-n3nd3sav.js";
12
12
  import"./cli-ht1bcqdd.js";
13
- import"./cli-ptez7zcc.js";
13
+ import"./cli-xx94a1k6.js";
14
14
  import {
15
15
  BuildError,
16
16
  EXIT_CODES
17
17
  } from "./cli-46ycxe6r.js";
18
- import"./cli-kkzka31p.js";
19
- import"./cli-7fkhgtfn.js";
18
+ import"./cli-eyyggect.js";
19
+ import"./cli-asap4t1z.js";
20
20
  import"./cli-5qr3z0qa.js";
21
21
  import"./cli-v5mp7a6q.js";
22
22
  import"./cli-37x76zdn.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gutterpress",
3
- "version": "0.11.18",
3
+ "version": "0.11.19-alpha.1",
4
4
  "description": "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5
5
  "author": "itlackey",
6
6
  "license": "MPL-2.0",