gutterpress 0.10.0-beta.1 → 0.10.0

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 (27) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{audit-85zn35v8.js → audit-cvarpa72.js} +4 -4
  3. package/dist/{build-1faqmp0y.js → build-6r502chw.js} +4 -4
  4. package/dist/{cli-7e79d7se.js → cli-149edp6b.js} +1 -1
  5. package/dist/{cli-v935c2ws.js → cli-cqtggsng.js} +1 -1
  6. package/dist/{cli-5tffrg48.js → cli-eq5naw4m.js} +136 -122
  7. package/dist/{cli-afm8pa0h.js → cli-zfcryxg8.js} +2 -2
  8. package/dist/cli.js +15 -15
  9. package/dist/{doctor-69v5c9wy.js → doctor-akvxbtjb.js} +2 -2
  10. package/dist/{engine-nbfdy0pp.js → engine-b159tbns.js} +2 -2
  11. package/dist/{engine-hrtn0b28.js → engine-ft4cr3ep.js} +1 -1
  12. package/dist/{gutterpress-viewer-fb6fhwsy.js → gutterpress-viewer-te8g5grx.js} +55 -21
  13. package/dist/{index-052x5fef.js → index-ge7q9xj3.js} +244 -230
  14. package/dist/{index-wsydvp08.js → index-wq3r5pj7.js} +1 -1
  15. package/dist/{index-c2b6gyeg.js → index-ycpvr0am.js} +2 -2
  16. package/dist/index.js +3 -3
  17. package/dist/lib/build-staging.d.ts +51 -0
  18. package/dist/{lint-17404bsx.js → lint-p2sw53d9.js} +4 -4
  19. package/dist/{new-vsgpxzek.js → new-hvq0x91q.js} +4 -4
  20. package/dist/{plugin-s4yfpx6p.js → plugin-pssmk0dx.js} +4 -4
  21. package/dist/{preflight-1h3apvqh.js → preflight-4j00yd0g.js} +4 -4
  22. package/dist/{preview-cqf383g4.js → preview-d4s6gk6p.js} +4 -4
  23. package/dist/{preview-interface-hg5eqv88.js → preview-interface-0ssk8bmm.js} +4 -2
  24. package/dist/{publish-arvycera.js → publish-tkc26en7.js} +4 -4
  25. package/dist/{repair-vmwh2bhe.js → repair-2va4w12t.js} +4 -4
  26. package/dist/{validate-ztzq7p35.js → validate-w9vfefrw.js} +4 -4
  27. package/package.json +1 -1
@@ -17,7 +17,7 @@ import {
17
17
  resolveChromiumExecutable,
18
18
  run,
19
19
  spawnCapture
20
- } from "./index-c2b6gyeg.js";
20
+ } from "./index-ycpvr0am.js";
21
21
  import {
22
22
  gitDirFor,
23
23
  gitScopeFor,
@@ -3950,7 +3950,7 @@ import git from "isomorphic-git";
3950
3950
  // package.json
3951
3951
  var package_default = {
3952
3952
  name: "gutterpress",
3953
- version: "0.10.0-beta.1",
3953
+ version: "0.10.0",
3954
3954
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
3955
3955
  author: "itlackey",
3956
3956
  license: "MPL-2.0",
@@ -4219,183 +4219,6 @@ async function writeBuildFingerprint(input) {
4219
4219
  return outPath;
4220
4220
  }
4221
4221
 
4222
- // src/lib/missing-asset-placeholder.ts
4223
- import { createHash as createHash5 } from "node:crypto";
4224
- import { deflateSync } from "node:zlib";
4225
- function placeholderOutputPath(missingOutputPath) {
4226
- const hash = createHash5("sha256").update(missingOutputPath).digest("hex").slice(0, 16);
4227
- return `assets/gutterpress-missing/${hash}.png`;
4228
- }
4229
- function decodeHtmlAttribute(value) {
4230
- return value.replace(/&quot;/g, '"').replace(/&#39;|&apos;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
4231
- }
4232
- function rewriteMissingImageReferences(html, replacements) {
4233
- if (replacements.size === 0)
4234
- return html;
4235
- const replacementFor = (raw) => replacements.get(raw) ?? replacements.get(decodeHtmlAttribute(raw));
4236
- const cssUrl = /url\(\s*(?:"([^"]*)"|'([^']*)'|&quot;((?:(?!&quot;).)*)&quot;|([^'"()\s]*))\s*\)/giy;
4237
- const rewriteCssUrls = (css) => {
4238
- let out2 = "";
4239
- let copiedThrough = 0;
4240
- let index = 0;
4241
- while (index < css.length) {
4242
- if (css.startsWith("/*", index)) {
4243
- const end = css.indexOf("*/", index + 2);
4244
- index = end < 0 ? css.length : end + 2;
4245
- continue;
4246
- }
4247
- const char = css[index];
4248
- if (char === '"' || char === "'") {
4249
- const quote = char;
4250
- index++;
4251
- while (index < css.length) {
4252
- if (css[index] === "\\") {
4253
- index += 2;
4254
- continue;
4255
- }
4256
- const current = css[index++];
4257
- if (current === quote)
4258
- break;
4259
- }
4260
- continue;
4261
- }
4262
- const previous = index > 0 ? css[index - 1] : "";
4263
- if (!/[a-z0-9_-]/i.test(previous) && css.slice(index, index + 3).toLowerCase() === "url") {
4264
- cssUrl.lastIndex = index;
4265
- const match = cssUrl.exec(css);
4266
- if (match) {
4267
- const raw = match[1] ?? match[2] ?? match[3] ?? match[4] ?? "";
4268
- const replacement = replacementFor(raw);
4269
- if (replacement) {
4270
- out2 += css.slice(copiedThrough, index);
4271
- out2 += match[3] !== undefined ? `url(&quot;${replacement}&quot;)` : `url("${replacement}")`;
4272
- index = cssUrl.lastIndex;
4273
- copiedThrough = index;
4274
- continue;
4275
- }
4276
- }
4277
- }
4278
- index++;
4279
- }
4280
- return copiedThrough === 0 ? css : out2 + css.slice(copiedThrough);
4281
- };
4282
- const rewriteTag = (tag) => {
4283
- let out2 = tag;
4284
- if (/^<img\b/i.test(tag)) {
4285
- out2 = out2.replace(/(\s+src\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi, (whole, prefix, double, single, bare) => {
4286
- const raw = double ?? single ?? bare ?? "";
4287
- const replacement = replacementFor(raw);
4288
- if (!replacement)
4289
- return whole;
4290
- if (double !== undefined)
4291
- return `${prefix}"${replacement}"`;
4292
- if (single !== undefined)
4293
- return `${prefix}'${replacement}'`;
4294
- return `${prefix}${replacement}`;
4295
- });
4296
- }
4297
- if (/^<(?:img|source)\b/i.test(tag)) {
4298
- out2 = out2.replace(/(\s+srcset\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi, (whole, prefix, double, single, bare) => {
4299
- const value = double ?? single ?? bare ?? "";
4300
- let rewritten = "";
4301
- let copiedThrough = 0;
4302
- let changed = false;
4303
- for (const candidate of parseSrcsetUrlCandidates(value)) {
4304
- const replacement = replacementFor(candidate.url);
4305
- if (!replacement)
4306
- continue;
4307
- rewritten += value.slice(copiedThrough, candidate.start) + replacement;
4308
- copiedThrough = candidate.end;
4309
- changed = true;
4310
- }
4311
- if (!changed)
4312
- return whole;
4313
- rewritten += value.slice(copiedThrough);
4314
- if (double !== undefined)
4315
- return `${prefix}"${rewritten}"`;
4316
- if (single !== undefined)
4317
- return `${prefix}'${rewritten}'`;
4318
- return `${prefix}${rewritten}`;
4319
- });
4320
- }
4321
- out2 = out2.replace(/(\s+style\s*=\s*)(["'])(.*?)\2/gi, (whole, prefix, quote, value) => {
4322
- const rewritten = rewriteCssUrls(value);
4323
- return rewritten === value ? whole : `${prefix}${quote}${rewritten}${quote}`;
4324
- });
4325
- return out2;
4326
- };
4327
- const rewriteActiveHtml = (active) => active.replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g, (tag) => rewriteTag(tag));
4328
- const protectedRegion = /<!--[\s\S]*?-->|<(script|style|pre|code|textarea)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
4329
- let out = "";
4330
- let last = 0;
4331
- for (const match of html.matchAll(protectedRegion)) {
4332
- out += rewriteActiveHtml(html.slice(last, match.index));
4333
- if (match[1]?.toLowerCase() === "style") {
4334
- const style = /^(<style\b[^>]*>)([\s\S]*)(<\/style\s*>)$/i.exec(match[0]);
4335
- out += style ? `${rewriteTag(style[1])}${rewriteCssUrls(style[2])}${style[3]}` : match[0];
4336
- } else {
4337
- out += match[0];
4338
- }
4339
- last = (match.index ?? 0) + match[0].length;
4340
- }
4341
- out += rewriteActiveHtml(html.slice(last));
4342
- return out;
4343
- }
4344
- function crc32(buf) {
4345
- let c = ~0;
4346
- for (let i = 0;i < buf.length; i++) {
4347
- c ^= buf[i];
4348
- for (let k = 0;k < 8; k++)
4349
- c = c >>> 1 ^ 3988292384 & -(c & 1);
4350
- }
4351
- return ~c >>> 0;
4352
- }
4353
- function chunk(type, data) {
4354
- const out = new Uint8Array(data.length + 12);
4355
- const view = new DataView(out.buffer);
4356
- view.setUint32(0, data.length);
4357
- for (let i = 0;i < 4; i++)
4358
- out[4 + i] = type.charCodeAt(i);
4359
- out.set(data, 8);
4360
- view.setUint32(data.length + 8, crc32(out.subarray(4, data.length + 8)));
4361
- return out;
4362
- }
4363
- function placeholderPng(width = 640, height = 480, cell = 32) {
4364
- const A = [217, 70, 239];
4365
- const B = [26, 26, 26];
4366
- const raw = new Uint8Array(height * (1 + width * 3));
4367
- let p = 0;
4368
- for (let y = 0;y < height; y++) {
4369
- raw[p++] = 0;
4370
- for (let x = 0;x < width; x++) {
4371
- const c = ((x / cell | 0) + (y / cell | 0)) % 2 === 0 ? A : B;
4372
- raw[p++] = c[0];
4373
- raw[p++] = c[1];
4374
- raw[p++] = c[2];
4375
- }
4376
- }
4377
- const ihdr = new Uint8Array(13);
4378
- const hv = new DataView(ihdr.buffer);
4379
- hv.setUint32(0, width);
4380
- hv.setUint32(4, height);
4381
- ihdr[8] = 8;
4382
- ihdr[9] = 2;
4383
- const parts = [
4384
- new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]),
4385
- chunk("IHDR", ihdr),
4386
- chunk("IDAT", new Uint8Array(deflateSync(raw))),
4387
- chunk("IEND", new Uint8Array(0))
4388
- ];
4389
- const total = parts.reduce((n, x) => n + x.length, 0);
4390
- const png = new Uint8Array(total);
4391
- let o = 0;
4392
- for (const part of parts) {
4393
- png.set(part, o);
4394
- o += part.length;
4395
- }
4396
- return png;
4397
- }
4398
-
4399
4222
  // src/lib/lint-runner.ts
4400
4223
  import { resolve as resolve4 } from "node:path";
4401
4224
  import { readFile as readFile8 } from "node:fs/promises";
@@ -7615,6 +7438,185 @@ function computeGates(format, opts, config) {
7615
7438
  import path6 from "node:path";
7616
7439
  import os from "node:os";
7617
7440
  import fsp from "node:fs/promises";
7441
+
7442
+ // src/lib/missing-asset-placeholder.ts
7443
+ import { createHash as createHash5 } from "node:crypto";
7444
+ import { deflateSync } from "node:zlib";
7445
+ function placeholderOutputPath(missingOutputPath) {
7446
+ const hash = createHash5("sha256").update(missingOutputPath).digest("hex").slice(0, 16);
7447
+ return `assets/gutterpress-missing/${hash}.png`;
7448
+ }
7449
+ function decodeHtmlAttribute(value) {
7450
+ return value.replace(/&quot;/g, '"').replace(/&#39;|&apos;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
7451
+ }
7452
+ function rewriteMissingImageReferences(html, replacements) {
7453
+ if (replacements.size === 0)
7454
+ return html;
7455
+ const replacementFor = (raw) => replacements.get(raw) ?? replacements.get(decodeHtmlAttribute(raw));
7456
+ const cssUrl = /url\(\s*(?:"([^"]*)"|'([^']*)'|&quot;((?:(?!&quot;).)*)&quot;|([^'"()\s]*))\s*\)/giy;
7457
+ const rewriteCssUrls = (css) => {
7458
+ let out2 = "";
7459
+ let copiedThrough = 0;
7460
+ let index = 0;
7461
+ while (index < css.length) {
7462
+ if (css.startsWith("/*", index)) {
7463
+ const end = css.indexOf("*/", index + 2);
7464
+ index = end < 0 ? css.length : end + 2;
7465
+ continue;
7466
+ }
7467
+ const char = css[index];
7468
+ if (char === '"' || char === "'") {
7469
+ const quote = char;
7470
+ index++;
7471
+ while (index < css.length) {
7472
+ if (css[index] === "\\") {
7473
+ index += 2;
7474
+ continue;
7475
+ }
7476
+ const current = css[index++];
7477
+ if (current === quote)
7478
+ break;
7479
+ }
7480
+ continue;
7481
+ }
7482
+ const previous = index > 0 ? css[index - 1] : "";
7483
+ if (!/[a-z0-9_-]/i.test(previous) && css.slice(index, index + 3).toLowerCase() === "url") {
7484
+ cssUrl.lastIndex = index;
7485
+ const match = cssUrl.exec(css);
7486
+ if (match) {
7487
+ const raw = match[1] ?? match[2] ?? match[3] ?? match[4] ?? "";
7488
+ const replacement = replacementFor(raw);
7489
+ if (replacement) {
7490
+ out2 += css.slice(copiedThrough, index);
7491
+ out2 += match[3] !== undefined ? `url(&quot;${replacement}&quot;)` : `url("${replacement}")`;
7492
+ index = cssUrl.lastIndex;
7493
+ copiedThrough = index;
7494
+ continue;
7495
+ }
7496
+ }
7497
+ }
7498
+ index++;
7499
+ }
7500
+ return copiedThrough === 0 ? css : out2 + css.slice(copiedThrough);
7501
+ };
7502
+ const rewriteTag = (tag) => {
7503
+ let out2 = tag;
7504
+ if (/^<img\b/i.test(tag)) {
7505
+ out2 = out2.replace(/(\s+src\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi, (whole, prefix, double, single, bare) => {
7506
+ const raw = double ?? single ?? bare ?? "";
7507
+ const replacement = replacementFor(raw);
7508
+ if (!replacement)
7509
+ return whole;
7510
+ if (double !== undefined)
7511
+ return `${prefix}"${replacement}"`;
7512
+ if (single !== undefined)
7513
+ return `${prefix}'${replacement}'`;
7514
+ return `${prefix}${replacement}`;
7515
+ });
7516
+ }
7517
+ if (/^<(?:img|source)\b/i.test(tag)) {
7518
+ out2 = out2.replace(/(\s+srcset\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi, (whole, prefix, double, single, bare) => {
7519
+ const value = double ?? single ?? bare ?? "";
7520
+ let rewritten = "";
7521
+ let copiedThrough = 0;
7522
+ let changed = false;
7523
+ for (const candidate of parseSrcsetUrlCandidates(value)) {
7524
+ const replacement = replacementFor(candidate.url);
7525
+ if (!replacement)
7526
+ continue;
7527
+ rewritten += value.slice(copiedThrough, candidate.start) + replacement;
7528
+ copiedThrough = candidate.end;
7529
+ changed = true;
7530
+ }
7531
+ if (!changed)
7532
+ return whole;
7533
+ rewritten += value.slice(copiedThrough);
7534
+ if (double !== undefined)
7535
+ return `${prefix}"${rewritten}"`;
7536
+ if (single !== undefined)
7537
+ return `${prefix}'${rewritten}'`;
7538
+ return `${prefix}${rewritten}`;
7539
+ });
7540
+ }
7541
+ out2 = out2.replace(/(\s+style\s*=\s*)(["'])(.*?)\2/gi, (whole, prefix, quote, value) => {
7542
+ const rewritten = rewriteCssUrls(value);
7543
+ return rewritten === value ? whole : `${prefix}${quote}${rewritten}${quote}`;
7544
+ });
7545
+ return out2;
7546
+ };
7547
+ const rewriteActiveHtml = (active) => active.replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g, (tag) => rewriteTag(tag));
7548
+ const protectedRegion = /<!--[\s\S]*?-->|<(script|style|pre|code|textarea)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
7549
+ let out = "";
7550
+ let last = 0;
7551
+ for (const match of html.matchAll(protectedRegion)) {
7552
+ out += rewriteActiveHtml(html.slice(last, match.index));
7553
+ if (match[1]?.toLowerCase() === "style") {
7554
+ const style = /^(<style\b[^>]*>)([\s\S]*)(<\/style\s*>)$/i.exec(match[0]);
7555
+ out += style ? `${rewriteTag(style[1])}${rewriteCssUrls(style[2])}${style[3]}` : match[0];
7556
+ } else {
7557
+ out += match[0];
7558
+ }
7559
+ last = (match.index ?? 0) + match[0].length;
7560
+ }
7561
+ out += rewriteActiveHtml(html.slice(last));
7562
+ return out;
7563
+ }
7564
+ function crc32(buf) {
7565
+ let c = ~0;
7566
+ for (let i = 0;i < buf.length; i++) {
7567
+ c ^= buf[i];
7568
+ for (let k = 0;k < 8; k++)
7569
+ c = c >>> 1 ^ 3988292384 & -(c & 1);
7570
+ }
7571
+ return ~c >>> 0;
7572
+ }
7573
+ function chunk(type, data) {
7574
+ const out = new Uint8Array(data.length + 12);
7575
+ const view = new DataView(out.buffer);
7576
+ view.setUint32(0, data.length);
7577
+ for (let i = 0;i < 4; i++)
7578
+ out[4 + i] = type.charCodeAt(i);
7579
+ out.set(data, 8);
7580
+ view.setUint32(data.length + 8, crc32(out.subarray(4, data.length + 8)));
7581
+ return out;
7582
+ }
7583
+ function placeholderPng(width = 640, height = 480, cell = 32) {
7584
+ const A = [217, 70, 239];
7585
+ const B = [26, 26, 26];
7586
+ const raw = new Uint8Array(height * (1 + width * 3));
7587
+ let p = 0;
7588
+ for (let y = 0;y < height; y++) {
7589
+ raw[p++] = 0;
7590
+ for (let x = 0;x < width; x++) {
7591
+ const c = ((x / cell | 0) + (y / cell | 0)) % 2 === 0 ? A : B;
7592
+ raw[p++] = c[0];
7593
+ raw[p++] = c[1];
7594
+ raw[p++] = c[2];
7595
+ }
7596
+ }
7597
+ const ihdr = new Uint8Array(13);
7598
+ const hv = new DataView(ihdr.buffer);
7599
+ hv.setUint32(0, width);
7600
+ hv.setUint32(4, height);
7601
+ ihdr[8] = 8;
7602
+ ihdr[9] = 2;
7603
+ const parts = [
7604
+ new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]),
7605
+ chunk("IHDR", ihdr),
7606
+ chunk("IDAT", new Uint8Array(deflateSync(raw))),
7607
+ chunk("IEND", new Uint8Array(0))
7608
+ ];
7609
+ const total = parts.reduce((n, x) => n + x.length, 0);
7610
+ const png = new Uint8Array(total);
7611
+ let o = 0;
7612
+ for (const part of parts) {
7613
+ png.set(part, o);
7614
+ o += part.length;
7615
+ }
7616
+ return png;
7617
+ }
7618
+
7619
+ // src/lib/build-staging.ts
7618
7620
  async function shipViewerHtml(htmlFile, outDir) {
7619
7621
  await fsp.mkdir(path6.join(outDir, "engine"), { recursive: true });
7620
7622
  await fsp.copyFile(await getAssetPath("engine/gutterpress-viewer.js"), path6.join(outDir, "engine/gutterpress-viewer.js"));
@@ -7623,6 +7625,50 @@ async function shipViewerHtml(htmlFile, outDir) {
7623
7625
  const html = await fsp.readFile(htmlFile, "utf-8");
7624
7626
  await fsp.writeFile(htmlFile, /<\/head>/i.test(html) ? html.replace(/<\/head>/i, tag + "</head>") : tag + html, "utf-8");
7625
7627
  }
7628
+ async function stageBookAssets(options) {
7629
+ const { renderDir, outDir, htmlFile, imageRefs, cssAssets, onPlan } = options;
7630
+ const { copies: imageCopies, errors, destinations } = await planImageCopies(renderDir, imageRefs);
7631
+ const copies = [...cssAssets, ...imageCopies];
7632
+ onPlan?.({ unresolved: errors, copyCount: copies.length });
7633
+ const missingPlaceholders = copies.length ? await copyReferencedAssets(copies, outDir) : new Map;
7634
+ let staged = await fsp.readFile(htmlFile, "utf8");
7635
+ if (missingPlaceholders.size > 0) {
7636
+ const rewrites = new Map(missingPlaceholders);
7637
+ for (const [ref, dest] of destinations) {
7638
+ const placeholder = missingPlaceholders.get(dest);
7639
+ if (placeholder)
7640
+ rewrites.set(ref, placeholder);
7641
+ }
7642
+ staged = rewriteMissingImageReferences(staged, rewrites);
7643
+ await fsp.writeFile(htmlFile, staged, "utf8");
7644
+ }
7645
+ if (staged.includes("--gp-shape:")) {
7646
+ await fsp.writeFile(htmlFile, await inlineShapeUrls(staged, outDir), "utf8");
7647
+ }
7648
+ return { missing: [...missingPlaceholders.keys()].sort() };
7649
+ }
7650
+ async function copyReferencedAssets(copies, outDir) {
7651
+ const dirs = new Set(copies.map((c) => path6.dirname(path6.resolve(outDir, c.to))));
7652
+ await Promise.all([...dirs].map((d) => fsp.mkdir(d, { recursive: true })));
7653
+ const missing = new Map;
7654
+ await Promise.all(copies.map(async (c) => {
7655
+ const dest = path6.resolve(outDir, c.to);
7656
+ try {
7657
+ await fsp.copyFile(c.from, dest);
7658
+ } catch (err) {
7659
+ if (err?.code === "ENOENT") {
7660
+ const placeholder = placeholderOutputPath(c.to);
7661
+ const placeholderDest = path6.resolve(outDir, placeholder);
7662
+ await fsp.mkdir(path6.dirname(placeholderDest), { recursive: true });
7663
+ await fsp.writeFile(placeholderDest, placeholderPng());
7664
+ missing.set(c.to, placeholder);
7665
+ return;
7666
+ }
7667
+ throw new BuildError(`Could not copy asset ${c.from} → ${c.to}: ` + (err instanceof Error ? err.message : String(err)), 1);
7668
+ }
7669
+ }));
7670
+ return missing;
7671
+ }
7626
7672
  async function createStageRoot() {
7627
7673
  return fsp.mkdtemp(path6.join(os.tmpdir(), "gutterpress-stage-"));
7628
7674
  }
@@ -7735,67 +7781,35 @@ async function renderBook(ctx) {
7735
7781
  }
7736
7782
  },
7737
7783
  onImageRefs: (refs) => imageRefs.push(...refs),
7738
- onCssAssets: (copies2) => cssAssets.push(...copies2),
7784
+ onCssAssets: (copies) => cssAssets.push(...copies),
7739
7785
  onStyleWarnings: (warnings) => {
7740
7786
  for (const w of warnings)
7741
7787
  log.warn(` ${w}`);
7742
7788
  }
7743
7789
  });
7744
7790
  log.success(`Wrote ${htmlFile}`);
7745
- const { copies: imageCopies, errors, destinations } = await planImageCopies(renderDir, imageRefs);
7746
- if (errors.length > 0) {
7747
- throw new BuildError(`Cannot resolve ${errors.length} image reference(s):
7748
- ` + errors.map((e) => ` - ${e}`).join(`
7791
+ const { missing } = await stageBookAssets({
7792
+ renderDir,
7793
+ outDir: workDir,
7794
+ htmlFile,
7795
+ imageRefs,
7796
+ cssAssets,
7797
+ onPlan: ({ unresolved, copyCount }) => {
7798
+ if (unresolved.length > 0) {
7799
+ throw new BuildError(`Cannot resolve ${unresolved.length} image reference(s):
7800
+ ` + unresolved.map((e) => ` - ${e}`).join(`
7749
7801
  `), 1);
7750
- }
7751
- const copies = [...cssAssets, ...imageCopies];
7752
- let missingPlaceholders = new Map;
7753
- if (copies.length > 0) {
7754
- log.info(`Copying ${copies.length} referenced asset(s)`);
7755
- missingPlaceholders = await copyReferencedAssets(copies, workDir);
7756
- }
7757
- let staged = await fsp2.readFile(htmlFile, "utf8");
7758
- if (missingPlaceholders.size > 0) {
7759
- const rewrites = new Map(missingPlaceholders);
7760
- for (const [ref, dest] of destinations) {
7761
- const placeholder = missingPlaceholders.get(dest);
7762
- if (placeholder)
7763
- rewrites.set(ref, placeholder);
7764
- }
7765
- staged = rewriteMissingImageReferences(staged, rewrites);
7766
- await fsp2.writeFile(htmlFile, staged, "utf8");
7767
- }
7768
- if (staged.includes("--gp-shape:")) {
7769
- await fsp2.writeFile(htmlFile, await inlineShapeUrls(staged, workDir), "utf8");
7770
- }
7771
- return htmlFile;
7772
- }
7773
- async function copyReferencedAssets(copies, outDir) {
7774
- const dirs = new Set(copies.map((c) => path7.dirname(path7.resolve(outDir, c.to))));
7775
- await Promise.all([...dirs].map((d) => fsp2.mkdir(d, { recursive: true })));
7776
- const missing = new Map;
7777
- await Promise.all(copies.map(async (c) => {
7778
- const dest = path7.resolve(outDir, c.to);
7779
- try {
7780
- await fsp2.copyFile(c.from, dest);
7781
- } catch (err) {
7782
- if (err?.code === "ENOENT") {
7783
- const placeholder = placeholderOutputPath(c.to);
7784
- const placeholderDest = path7.resolve(outDir, placeholder);
7785
- await fsp2.mkdir(path7.dirname(placeholderDest), { recursive: true });
7786
- await fsp2.writeFile(placeholderDest, placeholderPng());
7787
- missing.set(c.to, placeholder);
7788
- return;
7789
7802
  }
7790
- throw new BuildError(`Could not copy asset ${c.from} → ${c.to}: ` + (err instanceof Error ? err.message : String(err)), 1);
7803
+ if (copyCount > 0)
7804
+ log.info(`Copying ${copyCount} referenced asset(s)`);
7791
7805
  }
7792
- }));
7793
- if (missing.size > 0) {
7794
- log.warn(`${missing.size} referenced image(s) do not exist — a magenta placeholder ` + `was substituted so the build could finish. Each one is a visible hole ` + `in the PDF:`);
7795
- for (const m of [...missing.keys()].sort())
7806
+ });
7807
+ if (missing.length > 0) {
7808
+ log.warn(`${missing.length} referenced image(s) do not exist — a magenta placeholder ` + `was substituted so the build could finish. Each one is a visible hole ` + `in the PDF:`);
7809
+ for (const m of missing)
7796
7810
  log.warn(` missing: ${m}`);
7797
7811
  }
7798
- return missing;
7812
+ return htmlFile;
7799
7813
  }
7800
7814
  async function resolveIccProfile(icc, manifestDir, explicitIccPath) {
7801
7815
  const iccCandidates = path7.isAbsolute(icc) ? [icc] : [path7.resolve(manifestDir, icc), path7.resolve(icc)];
@@ -7893,7 +7907,7 @@ class PdfOutput {
7893
7907
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
7894
7908
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
7895
7909
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
7896
- const { buildNativePdf } = await import("./engine-nbfdy0pp.js");
7910
+ const { buildNativePdf } = await import("./engine-b159tbns.js");
7897
7911
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
7898
7912
  title: config.title,
7899
7913
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -4,7 +4,7 @@ import {
4
4
  launchChromium,
5
5
  marginBoxAlign,
6
6
  marginBoxRectPt
7
- } from "./index-c2b6gyeg.js";
7
+ } from "./index-ycpvr0am.js";
8
8
 
9
9
  // src/engine/compiler/build.ts
10
10
  import { readFile } from "node:fs/promises";
@@ -596,7 +596,7 @@ var favicon_default = "./favicon-wkbm9cjn.ico";
596
596
  var manifest_schema_default = "./manifest.schema-zxgxnbg7.json";
597
597
 
598
598
  // src/assets/preview/scripts/preview-interface.js
599
- var preview_interface_default = "./preview-interface-hg5eqv88.js";
599
+ var preview_interface_default = "./preview-interface-0ssk8bmm.js";
600
600
 
601
601
  // src/assets/preview/scripts/preview-bridge.js
602
602
  var preview_bridge_default = "./preview-bridge-fz7vpk8m.js";
@@ -608,7 +608,7 @@ var preview_shell_default = "./preview-shell-c5mfa3q0.js";
608
608
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
609
609
 
610
610
  // src/assets/engine/gutterpress-viewer.js
611
- var gutterpress_viewer_default = "./gutterpress-viewer-fb6fhwsy.js";
611
+ var gutterpress_viewer_default = "./gutterpress-viewer-te8g5grx.js";
612
612
 
613
613
  // src/assets/engine/gutterpress-agent.js
614
614
  var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
package/dist/index.js CHANGED
@@ -140,15 +140,15 @@ import {
140
140
  validateProjectPlugins,
141
141
  verifyRepoReadable,
142
142
  writeAppHeartbeat
143
- } from "./index-052x5fef.js";
143
+ } from "./index-ge7q9xj3.js";
144
144
  import {
145
145
  BUILD_DIAGNOSTIC_CODES
146
- } from "./index-wsydvp08.js";
146
+ } from "./index-wq3r5pj7.js";
147
147
  import {
148
148
  BuildError,
149
149
  DEFAULT_PRINT_OPTS,
150
150
  readyProbeExpr
151
- } from "./index-c2b6gyeg.js";
151
+ } from "./index-ycpvr0am.js";
152
152
  import {
153
153
  AUTO_SNAPSHOT_MESSAGE,
154
154
  HISTORY_PAGE_LIMIT,
@@ -1,3 +1,4 @@
1
+ import { type AssetCopy } from "./asset-inline";
1
2
  /**
2
3
  * `--format html`: ship the self-contained `book.html` (already fully
3
4
  * inlined — see `lib/asset-inline.ts`) alongside a copy of the native engine's
@@ -8,6 +9,56 @@
8
9
  * in the migration plan).
9
10
  */
10
11
  export declare function shipViewerHtml(htmlFile: string, outDir: string): Promise<void>;
12
+ /** The copy plan, handed to `onPlan` before a single byte is written. */
13
+ export interface StagingPlan {
14
+ /** Refs that name no in-project file at all (absolute, or outside the book). */
15
+ unresolved: string[];
16
+ /** How many files the staging is about to copy. */
17
+ copyCount: number;
18
+ }
19
+ /** What `stageBookAssets` could not stage, for the caller to report. */
20
+ export interface StagedAssets {
21
+ /** Output-relative paths whose source file does not exist; a placeholder shipped. */
22
+ missing: string[];
23
+ }
24
+ /**
25
+ * Turn a freshly rendered `book.html` plus its reported asset references into a
26
+ * COMPLETE, self-contained staged book in `outDir` — the exact document both
27
+ * renderers must paginate.
28
+ *
29
+ * THE one implementation: `renderBook` (every real build/export) and the
30
+ * preview/print parity gate both call this, so the gate can never measure a
31
+ * document the build would not have produced. It previously hand-rolled a bare
32
+ * `copyFile` loop, which (a) hard-crashed with a raw `ENOENT` on any book
33
+ * carrying a stale image path — i.e. the tool that enforces preview↔print
34
+ * parity could not run on the real books that need it — and (b) skipped
35
+ * `inlineShapeUrls`, so `.gp-shape` wrapping silently differed from the build.
36
+ *
37
+ * A missing image is NOT fatal: the same magenta placeholder the build ships
38
+ * (see missing-asset-placeholder.ts) is substituted and every reference
39
+ * rewritten to it, so the staged layout matches the build's. What could not be
40
+ * staged is returned, never swallowed — callers decide how loud to be.
41
+ *
42
+ * A ref that resolves to no in-project file at all is a different matter, and
43
+ * the two callers disagree about it: a real build refuses to ship (it throws
44
+ * from `onPlan`, before any bytes are copied), while the gate reports it and
45
+ * measures the book anyway. That is why the plan is handed out rather than
46
+ * judged here.
47
+ */
48
+ export declare function stageBookAssets(options: {
49
+ /** Project dir every image ref resolves against. */
50
+ renderDir: string;
51
+ /** Directory the staged book is being assembled in. */
52
+ outDir: string;
53
+ /** The rendered `book.html`, rewritten in place. */
54
+ htmlFile: string;
55
+ /** Image `src` values the render reported. */
56
+ imageRefs: Iterable<string>;
57
+ /** CSS images too large to inline, already planned by the render. */
58
+ cssAssets: AssetCopy[];
59
+ /** Called once with the copy plan; throw here to abort before copying. */
60
+ onPlan?: (plan: StagingPlan) => void;
61
+ }): Promise<StagedAssets>;
11
62
  /**
12
63
  * Create a unique scratch directory under the OS temp dir. Used only for
13
64
  * PDF/X intermediates (`raw.pdf`, Ghostscript work files) — never for staging
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  MANIFEST_FILENAMES,
3
3
  runLint
4
- } from "./cli-5tffrg48.js";
4
+ } from "./cli-eq5naw4m.js";
5
5
  import {
6
6
  log
7
- } from "./cli-v935c2ws.js";
7
+ } from "./cli-cqtggsng.js";
8
8
  import {
9
9
  UsageError,
10
10
  rejectExtraPositionals,
11
11
  rejectUnknownFlags
12
- } from "./cli-7e79d7se.js";
13
- import"./cli-afm8pa0h.js";
12
+ } from "./cli-149edp6b.js";
13
+ import"./cli-zfcryxg8.js";
14
14
  import"./cli-c41yr7he.js";
15
15
  import {
16
16
  EXIT_CODES
@@ -5,16 +5,16 @@ import {
5
5
  TARGETS,
6
6
  TARGET_IDS,
7
7
  scaffoldProject
8
- } from "./cli-5tffrg48.js";
8
+ } from "./cli-eq5naw4m.js";
9
9
  import {
10
10
  resolveGhostscript
11
- } from "./cli-v935c2ws.js";
11
+ } from "./cli-cqtggsng.js";
12
12
  import {
13
13
  UsageError,
14
14
  rejectExtraPositionals,
15
15
  rejectUnknownFlags
16
- } from "./cli-7e79d7se.js";
17
- import"./cli-afm8pa0h.js";
16
+ } from "./cli-149edp6b.js";
17
+ import"./cli-zfcryxg8.js";
18
18
  import {
19
19
  isToolAvailable
20
20
  } from "./cli-c41yr7he.js";