gutterpress 0.11.17-alpha.3 → 0.11.17-alpha.5

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 (29) hide show
  1. package/dist/api/index.js +3 -2
  2. package/dist/black-text-gray-7dz61456.js +15 -0
  3. package/dist/{build-394c1ee1.js → build-jr9mqwgr.js} +5 -4
  4. package/dist/checks/pdf/black-text.d.ts +3 -0
  5. package/dist/checks/pdf/index.d.ts +1 -0
  6. package/dist/{cli-d57bsfpb.js → cli-2xpwjanr.js} +1 -1
  7. package/dist/{cli-3p1e95zc.js → cli-5sg6hcqa.js} +1 -1
  8. package/dist/{cli-9nba5ve6.js → cli-82tvm0sg.js} +237 -607
  9. package/dist/{cli-jyy9pqf3.js → cli-c3enmb58.js} +1 -1
  10. package/dist/cli-ht1bcqdd.js +493 -0
  11. package/dist/cli.js +12 -12
  12. package/dist/{doctor-c459qv5j.js → doctor-zxk3pamr.js} +2 -2
  13. package/dist/{engine-zdb41ay1.js → engine-63r7pxsy.js} +1 -1
  14. package/dist/{engine-3xqgm82d.js → engine-eactk9nn.js} +2 -2
  15. package/dist/{ext-ha7gg8h9.js → ext-4vapkh0p.js} +5 -4
  16. package/dist/favicon-272ga6y0.ico +0 -0
  17. package/dist/{index-bscfmg5r.js → index-5ez30g3a.js} +232 -602
  18. package/dist/{index-q1xqxrh9.js → index-5gp6mkmf.js} +1 -1
  19. package/dist/index-ht1bcqdd.js +493 -0
  20. package/dist/{index-hrx0gfkj.js → index-mb8mzwye.js} +1 -1
  21. package/dist/index.js +4 -3
  22. package/dist/lib/black-text-gray.d.ts +17 -0
  23. package/dist/{new-kky3z7kv.js → new-hzs75cmd.js} +5 -4
  24. package/dist/{preflight-t8v6szq6.js → preflight-4k8dtsfs.js} +5 -4
  25. package/dist/{preview-cxab4y5x.js → preview-whg4rwg6.js} +5 -4
  26. package/dist/{publish-s2mtenb5.js → publish-4vtd7qve.js} +5 -4
  27. package/dist/{validate-xaajdw4x.js → validate-c32mm2bh.js} +5 -4
  28. package/package.json +1 -1
  29. package/dist/favicon-wkbm9cjn.ico +0 -0
@@ -13,18 +13,21 @@ import {
13
13
  stampCreator,
14
14
  stripAnnotations,
15
15
  warn
16
- } from "./cli-3p1e95zc.js";
16
+ } from "./cli-5sg6hcqa.js";
17
17
  import {
18
18
  DEBOUNCE,
19
19
  UsageError,
20
20
  resolvePort
21
- } from "./cli-d57bsfpb.js";
21
+ } from "./cli-2xpwjanr.js";
22
+ import {
23
+ blackTextToGray
24
+ } from "./cli-ht1bcqdd.js";
22
25
  import {
23
26
  MARGIN_BOX_IGNORED_PROPERTIES,
24
27
  getAssetPath,
25
28
  getAssetsDir,
26
29
  lastPageMode
27
- } from "./cli-jyy9pqf3.js";
30
+ } from "./cli-c3enmb58.js";
28
31
  import {
29
32
  BuildError,
30
33
  EXIT_CODES
@@ -5544,8 +5547,46 @@ var check7 = {
5544
5547
  };
5545
5548
  registerCheck(check7);
5546
5549
 
5547
- // src/checks/pdf/signature.ts
5550
+ // src/checks/pdf/black-text.ts
5551
+ import { readFile as readFile9 } from "node:fs/promises";
5548
5552
  var check8 = {
5553
+ id: "pdf.print.black-text",
5554
+ name: "Black Text",
5555
+ description: "Warns when text at or below 24pt is painted in a dark four-colour mix (black, or a near-black colour such as a dark brown)",
5556
+ category: "pdf",
5557
+ phase: "post-build",
5558
+ enabledWhen: (config) => config.pdfx.blackText === "k-only",
5559
+ async run(ctx) {
5560
+ if (!ctx.pdfPath)
5561
+ return [];
5562
+ try {
5563
+ const { findRichBlackText, LARGE_TEXT_PT } = await import("./black-text-gray-7dz61456.js");
5564
+ const found = await findRichBlackText(await readFile9(ctx.pdfPath));
5565
+ if (found.runs === 0)
5566
+ return [];
5567
+ return [
5568
+ finding(check8.id, {
5569
+ severity: "warning",
5570
+ message: `Dark four-colour text at ${LARGE_TEXT_PT}pt or smaller on page(s) ${found.pages.join(", ")}.`,
5571
+ file: ctx.pdfPath,
5572
+ detail: "Small text printed with all four inks can look fuzzy if the plates are slightly out of register, which is why print vendors such as DriveThruRPG want small black text on the black plate alone. `pdfx.blackText: k-only` already does that for near-black text; this text is either a dark colour (for example a dark brown, which keeps its colour) or painted in a way the build cannot rewrite (an image or a pattern). To print it on the black plate, make the text black (#000). If the colour is intended, set `lint.rules: { pdf.print.black-text: off }` in manifest.yaml.",
5573
+ code: "rich-black-text",
5574
+ data: { runs: found.runs, pages: found.pages }
5575
+ })
5576
+ ];
5577
+ } catch {
5578
+ return [
5579
+ inspectionFailed(check8.id, "Could not inspect the PDF's text colours.", {
5580
+ file: ctx.pdfPath
5581
+ })
5582
+ ];
5583
+ }
5584
+ }
5585
+ };
5586
+ registerCheck(check8);
5587
+
5588
+ // src/checks/pdf/signature.ts
5589
+ var check9 = {
5549
5590
  id: "pdf.print.signature",
5550
5591
  name: "Signature",
5551
5592
  description: "Fails when the interior's page count does not fit the print signature (a multiple of it ending on a blank page, or one short of a multiple)",
@@ -5557,7 +5598,7 @@ var check8 = {
5557
5598
  return [];
5558
5599
  const doc = await loadPdf(ctx.pdfPath);
5559
5600
  if (!doc) {
5560
- return [inspectionFailed(check8.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
5601
+ return [inspectionFailed(check9.id, "Could not parse the PDF's page count.", { file: ctx.pdfPath })];
5561
5602
  }
5562
5603
  const size = ctx.config.print.signature;
5563
5604
  const pages = doc.numPages;
@@ -5566,7 +5607,7 @@ var check8 = {
5566
5607
  const data = { pages, signature: size };
5567
5608
  if (pages % size !== 0) {
5568
5609
  return [
5569
- finding(check8.id, {
5610
+ finding(check9.id, {
5570
5611
  severity: "error",
5571
5612
  message: `Page count ${pages} does not fit a signature of ${size}: it must be a multiple of ${size}, or one short of one.`,
5572
5613
  file: ctx.pdfPath,
@@ -5582,10 +5623,10 @@ var check8 = {
5582
5623
  if (!await pageHasMarks(doc, pages))
5583
5624
  return [];
5584
5625
  } catch {
5585
- return [inspectionFailed(check8.id, "Could not read the last page.", { file: ctx.pdfPath })];
5626
+ return [inspectionFailed(check9.id, "Could not read the last page.", { file: ctx.pdfPath })];
5586
5627
  }
5587
5628
  return [
5588
- finding(check8.id, {
5629
+ finding(check9.id, {
5589
5630
  severity: "error",
5590
5631
  message: `The last page (${pages}) has content, but the printer needs it blank for its own information.`,
5591
5632
  file: ctx.pdfPath,
@@ -5596,10 +5637,10 @@ var check8 = {
5596
5637
  ];
5597
5638
  }
5598
5639
  };
5599
- registerCheck(check8);
5640
+ registerCheck(check9);
5600
5641
 
5601
5642
  // src/checks/pdf/bookmarks.ts
5602
- var check9 = {
5643
+ var check10 = {
5603
5644
  id: "pdf.nav.bookmarks",
5604
5645
  name: "Bookmarks",
5605
5646
  description: "Checks for PDF outline (bookmarks) tree",
@@ -5613,14 +5654,14 @@ var check9 = {
5613
5654
  const doc = await loadPdf(ctx.pdfPath);
5614
5655
  if (!doc) {
5615
5656
  return [
5616
- inspectionFailed(check9.id, "Could not inspect PDF for bookmarks.", {
5657
+ inspectionFailed(check10.id, "Could not inspect PDF for bookmarks.", {
5617
5658
  file: ctx.pdfPath
5618
5659
  })
5619
5660
  ];
5620
5661
  }
5621
5662
  if (await getOutlineCount(doc) === 0) {
5622
5663
  return [
5623
- finding(check9.id, {
5664
+ finding(check10.id, {
5624
5665
  severity: "warning",
5625
5666
  message: "PDF does not contain bookmarks (outline tree).",
5626
5667
  file: ctx.pdfPath
@@ -5630,10 +5671,10 @@ var check9 = {
5630
5671
  return [];
5631
5672
  }
5632
5673
  };
5633
- registerCheck(check9);
5674
+ registerCheck(check10);
5634
5675
 
5635
5676
  // src/checks/pdf/toc-links.ts
5636
- var check10 = {
5677
+ var check11 = {
5637
5678
  id: "pdf.nav.toc-links",
5638
5679
  name: "TOC Links",
5639
5680
  description: "Detects link annotations on TOC pages",
@@ -5648,7 +5689,7 @@ var check10 = {
5648
5689
  if (!doc) {
5649
5690
  return [
5650
5691
  {
5651
- checkId: check10.id,
5692
+ checkId: check11.id,
5652
5693
  severity: "warning",
5653
5694
  message: "Could not inspect PDF for TOC links.",
5654
5695
  file: ctx.pdfPath
@@ -5659,7 +5700,7 @@ var check10 = {
5659
5700
  if (!hasLinks) {
5660
5701
  return [
5661
5702
  {
5662
- checkId: check10.id,
5703
+ checkId: check11.id,
5663
5704
  severity: "warning",
5664
5705
  message: "No link annotations found in PDF (expected for TOC).",
5665
5706
  file: ctx.pdfPath
@@ -5669,10 +5710,10 @@ var check10 = {
5669
5710
  return [];
5670
5711
  }
5671
5712
  };
5672
- registerCheck(check10);
5713
+ registerCheck(check11);
5673
5714
 
5674
5715
  // src/checks/pdf/cross-refs.ts
5675
- var check11 = {
5716
+ var check12 = {
5676
5717
  id: "pdf.nav.cross-refs",
5677
5718
  name: "Cross References",
5678
5719
  description: "Counts and verifies internal link annotations",
@@ -5685,7 +5726,7 @@ var check11 = {
5685
5726
  if (!doc) {
5686
5727
  return [
5687
5728
  {
5688
- checkId: check11.id,
5729
+ checkId: check12.id,
5689
5730
  severity: "info",
5690
5731
  message: "Could not inspect PDF for cross-references.",
5691
5732
  file: ctx.pdfPath
@@ -5695,7 +5736,7 @@ var check11 = {
5695
5736
  const linkCount = await countLinkAnnotations(doc);
5696
5737
  return [
5697
5738
  {
5698
- checkId: check11.id,
5739
+ checkId: check12.id,
5699
5740
  severity: "info",
5700
5741
  message: `PDF contains ${linkCount} internal link annotation${linkCount !== 1 ? "s" : ""}.`,
5701
5742
  file: ctx.pdfPath
@@ -5703,10 +5744,10 @@ var check11 = {
5703
5744
  ];
5704
5745
  }
5705
5746
  };
5706
- registerCheck(check11);
5747
+ registerCheck(check12);
5707
5748
 
5708
5749
  // src/checks/pdf/page-labels.ts
5709
- var check12 = {
5750
+ var check13 = {
5710
5751
  id: "pdf.nav.page-labels",
5711
5752
  name: "Page Labels",
5712
5753
  description: "Verifies PDF page labels / folio numbering",
@@ -5721,7 +5762,7 @@ var check12 = {
5721
5762
  const labels = await getPageLabels(doc);
5722
5763
  return [
5723
5764
  {
5724
- checkId: check12.id,
5765
+ checkId: check13.id,
5725
5766
  severity: "info",
5726
5767
  message: labels && labels.length > 0 ? "PDF contains page labels." : "PDF does not contain explicit page labels.",
5727
5768
  file: ctx.pdfPath
@@ -5729,10 +5770,10 @@ var check12 = {
5729
5770
  ];
5730
5771
  }
5731
5772
  };
5732
- registerCheck(check12);
5773
+ registerCheck(check13);
5733
5774
 
5734
5775
  // src/checks/pdf/image-resolution.ts
5735
- var check13 = {
5776
+ var check14 = {
5736
5777
  id: "pdf.print.image-resolution",
5737
5778
  name: "Image Resolution",
5738
5779
  description: "Checks DPI of images embedded in the PDF",
@@ -5759,7 +5800,7 @@ var check13 = {
5759
5800
  lowResPages.sort((a, b) => a - b);
5760
5801
  return [
5761
5802
  {
5762
- checkId: check13.id,
5803
+ checkId: check14.id,
5763
5804
  severity: "warning",
5764
5805
  message: `Low-resolution images (below ${minDpi} DPI) found on pages: ${lowResPages.join(", ")}`,
5765
5806
  file: ctx.pdfPath
@@ -5769,10 +5810,10 @@ var check13 = {
5769
5810
  return [];
5770
5811
  }
5771
5812
  };
5772
- registerCheck(check13);
5813
+ registerCheck(check14);
5773
5814
 
5774
5815
  // src/checks/pdf/transparency.ts
5775
- var check14 = {
5816
+ var check15 = {
5776
5817
  id: "pdf.print.transparency",
5777
5818
  name: "Transparency",
5778
5819
  description: "Checks for transparency markers (/Transparency, /SMask, /BM) in the PDF",
@@ -5794,7 +5835,7 @@ var check14 = {
5794
5835
  if (found.length > 0) {
5795
5836
  return [
5796
5837
  {
5797
- checkId: check14.id,
5838
+ checkId: check15.id,
5798
5839
  severity: "warning",
5799
5840
  message: `Transparency detected in PDF: ${found.join(", ")}. This may cause issues with some printers.`,
5800
5841
  file: ctx.pdfPath
@@ -5804,7 +5845,7 @@ var check14 = {
5804
5845
  return [];
5805
5846
  }
5806
5847
  };
5807
- registerCheck(check14);
5848
+ registerCheck(check15);
5808
5849
 
5809
5850
  // src/checks/pdf/bleed.ts
5810
5851
  function findBox(bytes, box) {
@@ -5814,7 +5855,7 @@ function findBox(bytes, box) {
5814
5855
  return null;
5815
5856
  return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
5816
5857
  }
5817
- var check15 = {
5858
+ var check16 = {
5818
5859
  id: "pdf.print.bleed",
5819
5860
  name: "Bleed Box",
5820
5861
  description: "Compares MediaBox vs TrimBox/BleedBox to verify bleed area",
@@ -5828,7 +5869,7 @@ var check15 = {
5828
5869
  const bytes = await readPdfBytes(ctx.pdfPath).catch(() => "");
5829
5870
  if (!bytes) {
5830
5871
  return [
5831
- inspectionFailed(check15.id, "Could not inspect PDF for bleed boxes.", {
5872
+ inspectionFailed(check16.id, "Could not inspect PDF for bleed boxes.", {
5832
5873
  file: ctx.pdfPath
5833
5874
  })
5834
5875
  ];
@@ -5838,14 +5879,14 @@ var check15 = {
5838
5879
  const bleed = findBox(bytes, "BleedBox");
5839
5880
  if (!media) {
5840
5881
  return [
5841
- inspectionFailed(check15.id, "Could not parse MediaBox from PDF.", {
5882
+ inspectionFailed(check16.id, "Could not parse MediaBox from PDF.", {
5842
5883
  file: ctx.pdfPath
5843
5884
  })
5844
5885
  ];
5845
5886
  }
5846
5887
  if (!trim && !bleed) {
5847
5888
  return [
5848
- finding(check15.id, {
5889
+ finding(check16.id, {
5849
5890
  severity: "warning",
5850
5891
  message: "No TrimBox or BleedBox found. Bleed area cannot be verified.",
5851
5892
  file: ctx.pdfPath
@@ -5858,7 +5899,7 @@ var check15 = {
5858
5899
  const bleedSize = ctx.config.validate.pdf.bleedSize;
5859
5900
  if (mediaW - trimW < bleedSize * 2 * 0.9) {
5860
5901
  return [
5861
- finding(check15.id, {
5902
+ finding(check16.id, {
5862
5903
  severity: "warning",
5863
5904
  message: `Bleed area appears insufficient. Expected at least ${bleedSize}pt on each side.`,
5864
5905
  file: ctx.pdfPath
@@ -5869,10 +5910,10 @@ var check15 = {
5869
5910
  return [];
5870
5911
  }
5871
5912
  };
5872
- registerCheck(check15);
5913
+ registerCheck(check16);
5873
5914
 
5874
5915
  // src/checks/pdf/pdfx-metadata.ts
5875
- var check16 = {
5916
+ var check17 = {
5876
5917
  id: "pdf.print.pdfx-metadata",
5877
5918
  name: "PDF/X Metadata",
5878
5919
  description: "Verifies PDF/X DOCINFO metadata via qpdf JSON",
@@ -5891,25 +5932,25 @@ var check16 = {
5891
5932
  const objects = parseQpdfObjectsJson(stdout);
5892
5933
  if (!objects) {
5893
5934
  return [
5894
- inspectionFailed(check16.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
5935
+ inspectionFailed(check17.id, "Unable to parse qpdf JSON output for PDF/X metadata checks.", { file: ctx.pdfPath })
5895
5936
  ];
5896
5937
  }
5897
- return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check16.id, {
5938
+ return getPdfxMetadataIssues(objects, ctx.config.pdfx.flavor).map((message) => finding(check17.id, {
5898
5939
  severity: "error",
5899
5940
  message,
5900
5941
  file: ctx.pdfPath
5901
5942
  }));
5902
5943
  } catch {
5903
5944
  return [
5904
- inspectionFailed(check16.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
5945
+ inspectionFailed(check17.id, "Failed to inspect PDF objects with qpdf for PDF/X metadata.", { file: ctx.pdfPath })
5905
5946
  ];
5906
5947
  }
5907
5948
  }
5908
5949
  };
5909
- registerCheck(check16);
5950
+ registerCheck(check17);
5910
5951
 
5911
5952
  // src/checks/source/markdownlint.ts
5912
- import { readFile as readFile9 } from "node:fs/promises";
5953
+ import { readFile as readFile10 } from "node:fs/promises";
5913
5954
  import { lint } from "markdownlint/sync";
5914
5955
  import { parse as parseYaml2 } from "yaml";
5915
5956
 
@@ -5942,7 +5983,7 @@ function stripJsonComments(s) {
5942
5983
  return s.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
5943
5984
  }
5944
5985
  async function loadConfig(configPath) {
5945
- const raw = await readFile9(configPath, "utf8");
5986
+ const raw = await readFile10(configPath, "utf8");
5946
5987
  let parsed;
5947
5988
  if (configPath.endsWith(".json")) {
5948
5989
  parsed = JSON.parse(raw);
@@ -5961,7 +6002,7 @@ async function loadConfig(configPath) {
5961
6002
  return {};
5962
6003
  }
5963
6004
  var MARKDOWNLINT_CHECK_ID = "source.markdownlint";
5964
- var check17 = {
6005
+ var check18 = {
5965
6006
  id: MARKDOWNLINT_CHECK_ID,
5966
6007
  name: "Markdownlint",
5967
6008
  description: "Runs markdownlint with project config to validate Markdown files",
@@ -5986,7 +6027,7 @@ var check17 = {
5986
6027
  continue;
5987
6028
  for (const v of violations) {
5988
6029
  out.push({
5989
- checkId: check17.id,
6030
+ checkId: check18.id,
5990
6031
  severity: "warning",
5991
6032
  message: `${v.ruleDescription} (${v.ruleNames.join("/")})`,
5992
6033
  file,
@@ -5999,15 +6040,15 @@ var check17 = {
5999
6040
  return out;
6000
6041
  }
6001
6042
  };
6002
- registerCheck(check17);
6043
+ registerCheck(check18);
6003
6044
 
6004
6045
  // src/checks/source/htmlhint.ts
6005
6046
  import { existsSync as existsSync7 } from "node:fs";
6006
- import { readFile as readFile10 } from "node:fs/promises";
6047
+ import { readFile as readFile11 } from "node:fs/promises";
6007
6048
  import htmlhint from "htmlhint";
6008
6049
  var { HTMLHint } = htmlhint;
6009
6050
  var CONFIG_NAMES2 = [".htmlhintrc"];
6010
- var check18 = {
6051
+ var check19 = {
6011
6052
  id: "source.htmlhint",
6012
6053
  name: "HTMLHint",
6013
6054
  description: "Runs htmlhint with project config to validate generated HTML",
@@ -6023,11 +6064,11 @@ var check18 = {
6023
6064
  const resolvedConfig = findConfigFile(ctx.inputDir, CONFIG_NAMES2, configPath);
6024
6065
  if (!resolvedConfig && !configPath)
6025
6066
  return [];
6026
- const ruleset = resolvedConfig ? JSON.parse(await readFile10(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
6027
- const html = await readFile10(ctx.htmlPath, "utf8");
6067
+ const ruleset = resolvedConfig ? JSON.parse(await readFile11(resolvedConfig, "utf8")) : HTMLHint.defaultRuleset;
6068
+ const html = await readFile11(ctx.htmlPath, "utf8");
6028
6069
  const messages = HTMLHint.verify(html, ruleset);
6029
6070
  return messages.map((m) => ({
6030
- checkId: check18.id,
6071
+ checkId: check19.id,
6031
6072
  severity: m.type === "error" ? "error" : "warning",
6032
6073
  message: `${m.rule.id}: ${m.message}`,
6033
6074
  file: ctx.htmlPath,
@@ -6036,10 +6077,10 @@ var check18 = {
6036
6077
  }));
6037
6078
  }
6038
6079
  };
6039
- registerCheck(check18);
6080
+ registerCheck(check19);
6040
6081
 
6041
6082
  // src/checks/source/stylelint.ts
6042
- import { readFile as readFile11 } from "node:fs/promises";
6083
+ import { readFile as readFile12 } from "node:fs/promises";
6043
6084
 
6044
6085
  // src/lib/printsafe.ts
6045
6086
  import postcss from "postcss";
@@ -6356,7 +6397,7 @@ function checkCssRaw(css, from) {
6356
6397
  }
6357
6398
 
6358
6399
  // src/checks/source/stylelint.ts
6359
- var check19 = {
6400
+ var check20 = {
6360
6401
  id: "source.stylelint",
6361
6402
  name: "Print-safety CSS",
6362
6403
  description: "Checks CSS for print-safety issues (remote URLs, rasterizing effects, and page-layer containment)",
@@ -6373,10 +6414,10 @@ var check19 = {
6373
6414
  for (const file of files) {
6374
6415
  let css;
6375
6416
  try {
6376
- css = await readFile11(file, "utf8");
6417
+ css = await readFile12(file, "utf8");
6377
6418
  } catch (err) {
6378
6419
  results.push({
6379
- checkId: check19.id,
6420
+ checkId: check20.id,
6380
6421
  severity: "error",
6381
6422
  message: `Cannot read stylesheet: ${err instanceof Error ? err.message : String(err)}`,
6382
6423
  file
@@ -6385,7 +6426,7 @@ var check19 = {
6385
6426
  }
6386
6427
  for (const w of checkCss(css, file, { rules: ctx.config.lint.rules })) {
6387
6428
  results.push({
6388
- checkId: check19.id,
6429
+ checkId: check20.id,
6389
6430
  severity: w.severity,
6390
6431
  message: `${w.rule}: ${w.message}`,
6391
6432
  file,
@@ -6397,10 +6438,10 @@ var check19 = {
6397
6438
  return results;
6398
6439
  }
6399
6440
  };
6400
- registerCheck(check19);
6441
+ registerCheck(check20);
6401
6442
 
6402
6443
  // src/checks/source/css-ownership.ts
6403
- import { readFile as readFile12 } from "node:fs/promises";
6444
+ import { readFile as readFile13 } from "node:fs/promises";
6404
6445
 
6405
6446
  // src/lib/style-resolver.ts
6406
6447
  import { existsSync as existsSync8 } from "node:fs";
@@ -6468,7 +6509,7 @@ import postcss2 from "postcss";
6468
6509
  import { parse as parseYaml3 } from "yaml";
6469
6510
  var CONFIG_NAMES3 = [".gutterpress/css-contract.yaml", ".gutterpress/css-contract.yml"];
6470
6511
  async function loadContract(configPath) {
6471
- const raw = await readFile12(configPath, "utf8");
6512
+ const raw = await readFile13(configPath, "utf8");
6472
6513
  const parsed = parseYaml3(raw);
6473
6514
  if (parsed && typeof parsed === "object")
6474
6515
  return parsed;
@@ -6581,7 +6622,7 @@ function checkOwnership(root, file, maps) {
6581
6622
  });
6582
6623
  return out;
6583
6624
  }
6584
- var check20 = {
6625
+ var check21 = {
6585
6626
  id: "source.css-ownership",
6586
6627
  name: "CSS ownership contract",
6587
6628
  description: "Checks an optional per-project CSS ownership contract (.gutterpress/css-contract.yaml): which file may declare which properties/at-rules, and which selectors must never appear unscoped.",
@@ -6604,7 +6645,7 @@ var check20 = {
6604
6645
  } catch (err) {
6605
6646
  return [
6606
6647
  {
6607
- checkId: check20.id,
6648
+ checkId: check21.id,
6608
6649
  severity: "error",
6609
6650
  message: `Failed to parse CSS ownership contract "${configPath}": ${err instanceof Error ? err.message : String(err)}`,
6610
6651
  file: configPath
@@ -6619,7 +6660,7 @@ var check20 = {
6619
6660
  continue;
6620
6661
  let css;
6621
6662
  try {
6622
- css = await readFile12(absFile, "utf8");
6663
+ css = await readFile13(absFile, "utf8");
6623
6664
  } catch {
6624
6665
  continue;
6625
6666
  }
@@ -6646,7 +6687,7 @@ var check20 = {
6646
6687
  }
6647
6688
  for (const v of violations) {
6648
6689
  results.push({
6649
- checkId: check20.id,
6690
+ checkId: check21.id,
6650
6691
  severity: "warning",
6651
6692
  message: v.message,
6652
6693
  file: absFile,
@@ -6658,7 +6699,7 @@ var check20 = {
6658
6699
  return results;
6659
6700
  }
6660
6701
  };
6661
- registerCheck(check20);
6702
+ registerCheck(check21);
6662
6703
 
6663
6704
  // src/checks/source/local-refs.ts
6664
6705
  import { existsSync as existsSync9 } from "node:fs";
@@ -6666,7 +6707,7 @@ import { dirname as dirname3, resolve as resolve5 } from "node:path";
6666
6707
 
6667
6708
  // src/lib/asset-inline.ts
6668
6709
  import { createHash as createHash2 } from "node:crypto";
6669
- import { readFile as readFile13 } from "node:fs/promises";
6710
+ import { readFile as readFile14 } from "node:fs/promises";
6670
6711
  import path8 from "node:path";
6671
6712
  import postcss3 from "postcss";
6672
6713
  var FONT_EXTS = new Set([".woff2", ".woff", ".ttf", ".otf"]);
@@ -6731,7 +6772,7 @@ function mimeFor(ext) {
6731
6772
  }
6732
6773
  async function readOrThrow(absPath, kind, referencedFrom) {
6733
6774
  try {
6734
- return await readFile13(absPath);
6775
+ return await readFile14(absPath);
6735
6776
  } catch {
6736
6777
  throw new BuildError(`Missing ${kind}: ${absPath}
6737
6778
  referenced from ${referencedFrom}
@@ -6874,7 +6915,7 @@ async function collectStyleDependencies(projectDir, stylePaths) {
6874
6915
  found.add(abs);
6875
6916
  let source;
6876
6917
  try {
6877
- source = await readFile13(abs, "utf-8");
6918
+ source = await readFile14(abs, "utf-8");
6878
6919
  } catch {
6879
6920
  return;
6880
6921
  }
@@ -6951,7 +6992,7 @@ async function inlineShapeUrls(html, baseDir) {
6951
6992
  continue;
6952
6993
  let bytes;
6953
6994
  try {
6954
- bytes = await readFile13(abs);
6995
+ bytes = await readFile14(abs);
6955
6996
  } catch {
6956
6997
  continue;
6957
6998
  }
@@ -6969,7 +7010,7 @@ async function inlineShapeUrls(html, baseDir) {
6969
7010
  }
6970
7011
 
6971
7012
  // src/checks/source/local-ref-parser.ts
6972
- import { readFile as readFile14 } from "node:fs/promises";
7013
+ import { readFile as readFile15 } from "node:fs/promises";
6973
7014
  function newlineOffsets(content) {
6974
7015
  const offsets = [];
6975
7016
  for (let i = 0;i < content.length; i++)
@@ -7038,7 +7079,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
7038
7079
  for (const file of files) {
7039
7080
  let refs;
7040
7081
  try {
7041
- refs = collectRenderedLocalRefs(await readFile14(file, "utf8"));
7082
+ refs = collectRenderedLocalRefs(await readFile15(file, "utf8"));
7042
7083
  } catch {
7043
7084
  results.push(inspectionFailed(checkId, `Could not read source file: ${file}`, { file }));
7044
7085
  continue;
@@ -7049,7 +7090,7 @@ async function* renderedLocalRefs(ctx, checkId, results) {
7049
7090
  }
7050
7091
 
7051
7092
  // src/checks/source/local-refs.ts
7052
- var check21 = {
7093
+ var check22 = {
7053
7094
  id: "source.links.local-refs",
7054
7095
  name: "Local Markdown References",
7055
7096
  description: "Checks local markdown links and image refs resolve on disk",
@@ -7057,12 +7098,12 @@ var check21 = {
7057
7098
  phase: "pre-build",
7058
7099
  async run(ctx) {
7059
7100
  const results = [];
7060
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check21.id, results)) {
7101
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check22.id, results)) {
7061
7102
  if (isNonFilesystemRef(ref))
7062
7103
  continue;
7063
7104
  const escape = kind === "image" ? proseImageRefError(ref, ctx.inputDir) : null;
7064
7105
  if (escape) {
7065
- results.push(finding(check21.id, {
7106
+ results.push(finding(check22.id, {
7066
7107
  severity: "error",
7067
7108
  message: escape,
7068
7109
  file,
@@ -7072,7 +7113,7 @@ var check21 = {
7072
7113
  }
7073
7114
  if (localRefExists(ref, kind, file, ctx.inputDir))
7074
7115
  continue;
7075
- results.push(finding(check21.id, {
7116
+ results.push(finding(check22.id, {
7076
7117
  severity: kind === "image" ? "warning" : "error",
7077
7118
  code: kind === "image" ? "missing-image-placeholder" : undefined,
7078
7119
  message: kind === "image" ? `Local image not found; build will substitute a magenta placeholder: ${ref}` : `Local reference not found: ${ref}`,
@@ -7097,7 +7138,7 @@ function localRefExists(ref, kind, sourceFile, projectRoot) {
7097
7138
  return existsSync9(fileRelative);
7098
7139
  }
7099
7140
  }
7100
- registerCheck(check21);
7141
+ registerCheck(check22);
7101
7142
 
7102
7143
  // src/lib/build-staging.ts
7103
7144
  import path9 from "node:path";
@@ -7363,7 +7404,7 @@ async function createStageRoot() {
7363
7404
  }
7364
7405
 
7365
7406
  // src/checks/source/dangling-links.ts
7366
- var check22 = {
7407
+ var check23 = {
7367
7408
  id: "source.links.dangling",
7368
7409
  name: "Dangling Links",
7369
7410
  description: "Flags relative links the printed book cannot open (their href is dropped at print time)",
@@ -7371,10 +7412,10 @@ var check22 = {
7371
7412
  phase: "pre-build",
7372
7413
  async run(ctx) {
7373
7414
  const results = [];
7374
- for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check22.id, results)) {
7415
+ for await (const { ref, kind, line, file } of renderedLocalRefs(ctx, check23.id, results)) {
7375
7416
  if (kind !== "link" || isPrintResolvableHref(ref))
7376
7417
  continue;
7377
- results.push(finding(check22.id, {
7418
+ results.push(finding(check23.id, {
7378
7419
  severity: "warning",
7379
7420
  code: "dangling-link",
7380
7421
  data: { ref },
@@ -7386,11 +7427,11 @@ var check22 = {
7386
7427
  return results;
7387
7428
  }
7388
7429
  };
7389
- registerCheck(check22);
7430
+ registerCheck(check23);
7390
7431
 
7391
7432
  // src/checks/source/accessibility-alt-text.ts
7392
- import { readFile as readFile15 } from "node:fs/promises";
7393
- var check23 = {
7433
+ import { readFile as readFile16 } from "node:fs/promises";
7434
+ var check24 = {
7394
7435
  id: "source.accessibility.alt-text",
7395
7436
  name: "Image Alt Text",
7396
7437
  description: "Checks markdown images include non-empty alt text",
@@ -7403,7 +7444,7 @@ var check23 = {
7403
7444
  const results = [];
7404
7445
  for (const file of files) {
7405
7446
  try {
7406
- const content = await readFile15(file, "utf8");
7447
+ const content = await readFile16(file, "utf8");
7407
7448
  const lines = content.split(`
7408
7449
  `);
7409
7450
  let inFence = false;
@@ -7419,7 +7460,7 @@ var check23 = {
7419
7460
  if (alt.trim())
7420
7461
  continue;
7421
7462
  results.push({
7422
- checkId: check23.id,
7463
+ checkId: check24.id,
7423
7464
  severity: "warning",
7424
7465
  message: "Image is missing alt text",
7425
7466
  file,
@@ -7444,11 +7485,11 @@ function extractImageAlts(line) {
7444
7485
  }
7445
7486
  return alts;
7446
7487
  }
7447
- registerCheck(check23);
7488
+ registerCheck(check24);
7448
7489
 
7449
7490
  // src/checks/source/accessibility-heading-order.ts
7450
- import { readFile as readFile16 } from "node:fs/promises";
7451
- var check24 = {
7491
+ import { readFile as readFile17 } from "node:fs/promises";
7492
+ var check25 = {
7452
7493
  id: "source.accessibility.heading-order",
7453
7494
  name: "Heading Order",
7454
7495
  description: "Checks markdown heading levels do not jump by more than one",
@@ -7461,7 +7502,7 @@ var check24 = {
7461
7502
  const results = [];
7462
7503
  for (const file of files) {
7463
7504
  try {
7464
- const content = await readFile16(file, "utf8");
7505
+ const content = await readFile17(file, "utf8");
7465
7506
  const lines = content.split(`
7466
7507
  `);
7467
7508
  let inFence = false;
@@ -7476,7 +7517,7 @@ var check24 = {
7476
7517
  if (inFence)
7477
7518
  continue;
7478
7519
  const comment = /^\s*<!--(.*)-->\s*$/.exec(line);
7479
- if (comment && parseDisableDirective(comment[1])?.includes(check24.id)) {
7520
+ if (comment && parseDisableDirective(comment[1])?.includes(check25.id)) {
7480
7521
  silencedLine = i + 1;
7481
7522
  continue;
7482
7523
  }
@@ -7486,7 +7527,7 @@ var check24 = {
7486
7527
  const level = match[1].length;
7487
7528
  if (prevLevel != null && level > prevLevel + 1 && i !== silencedLine) {
7488
7529
  results.push({
7489
- checkId: check24.id,
7530
+ checkId: check25.id,
7490
7531
  severity: "warning",
7491
7532
  message: `Heading level jump from h${prevLevel} to h${level}`,
7492
7533
  file,
@@ -7500,11 +7541,11 @@ var check24 = {
7500
7541
  return results;
7501
7542
  }
7502
7543
  };
7503
- registerCheck(check24);
7544
+ registerCheck(check25);
7504
7545
 
7505
7546
  // src/checks/source/layout-markers.ts
7506
- import { readFile as readFile17 } from "node:fs/promises";
7507
- var check25 = {
7547
+ import { readFile as readFile18 } from "node:fs/promises";
7548
+ var check26 = {
7508
7549
  id: "source.markdown.layout-markers",
7509
7550
  name: "Layout Markers",
7510
7551
  description: "Reports @page/@section/@chapter marker arguments Gutterpress could not understand, plus any unknown gp-* class",
@@ -7516,16 +7557,16 @@ var check25 = {
7516
7557
  return [];
7517
7558
  const results = [];
7518
7559
  const plugins = await loadPlugins(ctx.config.extensions, ctx.inputDir, (ref, error2) => {
7519
- results.push(inspectionFailed(check25.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
7560
+ results.push(inspectionFailed(check26.id, `Plugin "${ref}" could not be loaded, so markers it defines were not checked: ${error2.message}`));
7520
7561
  });
7521
7562
  const md = createMarkdownRenderer(plugins);
7522
7563
  for (const file of files) {
7523
7564
  try {
7524
- const content = await readFile17(file, "utf8");
7565
+ const content = await readFile18(file, "utf8");
7525
7566
  const env = {};
7526
7567
  md.render(content, env);
7527
7568
  for (const w of env.layoutWarnings ?? []) {
7528
- results.push(finding(check25.id, {
7569
+ results.push(finding(check26.id, {
7529
7570
  severity: w.severity ?? "warning",
7530
7571
  message: w.message,
7531
7572
  file,
@@ -7534,16 +7575,16 @@ var check25 = {
7534
7575
  }));
7535
7576
  }
7536
7577
  } catch (error2) {
7537
- results.push(inspectionFailed(check25.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
7578
+ results.push(inspectionFailed(check26.id, `Could not check layout markers in ${file}: ${error2 instanceof Error ? error2.message : String(error2)}`, { file }));
7538
7579
  }
7539
7580
  }
7540
7581
  return results;
7541
7582
  }
7542
7583
  };
7543
- registerCheck(check25);
7584
+ registerCheck(check26);
7544
7585
 
7545
7586
  // src/checks/source/merge-markers.ts
7546
- import { readFile as readFile18 } from "node:fs/promises";
7587
+ import { readFile as readFile19 } from "node:fs/promises";
7547
7588
  import path10 from "node:path";
7548
7589
 
7549
7590
  // src/checks/asset/extensions.ts
@@ -7573,7 +7614,7 @@ var CLOSE_SENTINEL = ">>>>>>> online version";
7573
7614
  function withoutOnlineTag(relPath) {
7574
7615
  return relPath.replace(/\.online(?=\.[^./]*$|$)/, "");
7575
7616
  }
7576
- var check26 = {
7617
+ var check27 = {
7577
7618
  id: "source.sync.merge-markers",
7578
7619
  name: "Combined Versions",
7579
7620
  description: "Finds passages and files still holding two versions (yours and the online copy) after a sync",
@@ -7585,9 +7626,9 @@ var check26 = {
7585
7626
  for (const file of textFiles) {
7586
7627
  let content;
7587
7628
  try {
7588
- content = await readFile18(file, "utf8");
7629
+ content = await readFile19(file, "utf8");
7589
7630
  } catch {
7590
- results.push(inspectionFailed(check26.id, `Could not read source file: ${file}`, { file }));
7631
+ results.push(inspectionFailed(check27.id, `Could not read source file: ${file}`, { file }));
7591
7632
  continue;
7592
7633
  }
7593
7634
  if (!content.includes(OPEN_SENTINEL) && !content.includes(CLOSE_SENTINEL))
@@ -7599,7 +7640,7 @@ var check26 = {
7599
7640
  const line = lines[i].endsWith("\r") ? lines[i].slice(0, -1) : lines[i];
7600
7641
  if (line === OPEN_SENTINEL) {
7601
7642
  inBlock = true;
7602
- results.push(finding(check26.id, {
7643
+ results.push(finding(check27.id, {
7603
7644
  severity: "error",
7604
7645
  code: "two-versions-passage",
7605
7646
  message: "This passage has two versions (yours and the online copy) — keep what you want, then delete the marker lines.",
@@ -7608,7 +7649,7 @@ var check26 = {
7608
7649
  }));
7609
7650
  } else if (line === CLOSE_SENTINEL) {
7610
7651
  if (!inBlock) {
7611
- results.push(finding(check26.id, {
7652
+ results.push(finding(check27.id, {
7612
7653
  severity: "error",
7613
7654
  code: "leftover-version-marker",
7614
7655
  message: "This marker line is left over from combining two versions — delete it.",
@@ -7629,7 +7670,7 @@ var check26 = {
7629
7670
  });
7630
7671
  for (const sibling of siblings.sort()) {
7631
7672
  const rel = path10.relative(ctx.inputDir, sibling).split(path10.sep).join("/");
7632
- results.push(finding(check26.id, {
7673
+ results.push(finding(check27.id, {
7633
7674
  severity: "warning",
7634
7675
  code: "kept-both-versions",
7635
7676
  message: `Two versions of ${withoutOnlineTag(rel)} are in your book — keep the one you want, then delete the other.`,
@@ -7637,18 +7678,18 @@ var check26 = {
7637
7678
  }));
7638
7679
  }
7639
7680
  } catch (error2) {
7640
- results.push(inspectionFailed(check26.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
7681
+ results.push(inspectionFailed(check27.id, `Could not scan for kept-both files: ${error2 instanceof Error ? error2.message : String(error2)}`));
7641
7682
  }
7642
7683
  return results;
7643
7684
  }
7644
7685
  };
7645
- registerCheck(check26);
7686
+ registerCheck(check27);
7646
7687
 
7647
7688
  // src/checks/asset/image-file-size.ts
7648
7689
  import { stat as stat4 } from "node:fs/promises";
7649
7690
 
7650
7691
  // src/lib/image-inspect.ts
7651
- import { stat as stat3, readFile as readFile19 } from "node:fs/promises";
7692
+ import { stat as stat3, readFile as readFile20 } from "node:fs/promises";
7652
7693
  var DEFAULT_DPI = 72;
7653
7694
  var cache = new Map;
7654
7695
  async function inspectImage(path11) {
@@ -7663,7 +7704,7 @@ async function inspectImage(path11) {
7663
7704
  return hit.info;
7664
7705
  let info2 = null;
7665
7706
  try {
7666
- const buf = await readFile19(path11);
7707
+ const buf = await readFile20(path11);
7667
7708
  info2 = parseImage(buf);
7668
7709
  } catch {
7669
7710
  info2 = null;
@@ -7868,7 +7909,7 @@ async function collectImageFiles(dirs, exts, ignore2 = ASSET_SCAN_IGNORE_GLOBS)
7868
7909
  }
7869
7910
 
7870
7911
  // src/checks/asset/image-file-size.ts
7871
- var check27 = {
7912
+ var check28 = {
7872
7913
  id: "asset.image.file-size",
7873
7914
  name: "Image File Size",
7874
7915
  description: "Checks that image files do not exceed the maximum size limit",
@@ -7889,14 +7930,14 @@ var check27 = {
7889
7930
  if (info2.size > maxSize) {
7890
7931
  const sizeMb = (info2.size / 1e6).toFixed(1);
7891
7932
  const maxMb = (maxSize / 1e6).toFixed(1);
7892
- results.push(finding(check27.id, {
7933
+ results.push(finding(check28.id, {
7893
7934
  severity: "warning",
7894
7935
  message: `Image file too large: ${sizeMb}MB (max ${maxMb}MB)`,
7895
7936
  file
7896
7937
  }));
7897
7938
  }
7898
7939
  } catch {
7899
- results.push(inspectionFailed(check27.id, `Could not stat image file: ${file}`, {
7940
+ results.push(inspectionFailed(check28.id, `Could not stat image file: ${file}`, {
7900
7941
  file
7901
7942
  }));
7902
7943
  }
@@ -7904,10 +7945,10 @@ var check27 = {
7904
7945
  return results;
7905
7946
  }
7906
7947
  };
7907
- registerCheck(check27);
7948
+ registerCheck(check28);
7908
7949
 
7909
7950
  // src/checks/asset/image-resolution.ts
7910
- var check28 = {
7951
+ var check29 = {
7911
7952
  id: "asset.image.resolution",
7912
7953
  name: "Image Resolution",
7913
7954
  description: "Checks source image DPI from embedded density metadata",
@@ -7929,7 +7970,7 @@ var check28 = {
7929
7970
  const { xDpi, yDpi } = info2;
7930
7971
  if (xDpi > 0 && yDpi > 0 && (xDpi < minDpi || yDpi < minDpi)) {
7931
7972
  results.push({
7932
- checkId: check28.id,
7973
+ checkId: check29.id,
7933
7974
  severity: "warning",
7934
7975
  message: `Image resolution too low: ${xDpi}x${yDpi} DPI (minimum ${minDpi} DPI)`,
7935
7976
  file
@@ -7939,7 +7980,7 @@ var check28 = {
7939
7980
  return results;
7940
7981
  }
7941
7982
  };
7942
- registerCheck(check28);
7983
+ registerCheck(check29);
7943
7984
 
7944
7985
  // src/checks/asset/image-color-space.ts
7945
7986
  var LABEL = {
@@ -7947,7 +7988,7 @@ var LABEL = {
7947
7988
  gray: "Gray",
7948
7989
  cmyk: "CMYK"
7949
7990
  };
7950
- var check29 = {
7991
+ var check30 = {
7951
7992
  id: "asset.image.color-space",
7952
7993
  name: "Image Color Space",
7953
7994
  description: "Validates image color spaces against allowed list",
@@ -7968,7 +8009,7 @@ var check29 = {
7968
8009
  const cs = info2?.colorSpace;
7969
8010
  if (cs && !allowedLower.has(cs)) {
7970
8011
  results.push({
7971
- checkId: check29.id,
8012
+ checkId: check30.id,
7972
8013
  severity: "warning",
7973
8014
  message: `Image uses ${LABEL[cs] ?? cs} color space (allowed: ${allowed.join(", ")})`,
7974
8015
  file
@@ -7978,10 +8019,10 @@ var check29 = {
7978
8019
  return results;
7979
8020
  }
7980
8021
  };
7981
- registerCheck(check29);
8022
+ registerCheck(check30);
7982
8023
 
7983
8024
  // src/checks/asset/image-alpha.ts
7984
- var check30 = {
8025
+ var check31 = {
7985
8026
  id: "asset.image.alpha-channel",
7986
8027
  name: "Image Alpha Channel",
7987
8028
  description: "Checks for alpha channels in PNG/TIFF images",
@@ -7999,7 +8040,7 @@ var check30 = {
7999
8040
  const info2 = await inspectImage(file);
8000
8041
  if (info2?.hasAlpha) {
8001
8042
  results.push({
8002
- checkId: check30.id,
8043
+ checkId: check31.id,
8003
8044
  severity: "warning",
8004
8045
  message: "Image contains alpha channel, which may cause print issues",
8005
8046
  file
@@ -8009,10 +8050,10 @@ var check30 = {
8009
8050
  return results;
8010
8051
  }
8011
8052
  };
8012
- registerCheck(check30);
8053
+ registerCheck(check31);
8013
8054
 
8014
8055
  // src/checks/asset/image-tac.ts
8015
- var check31 = {
8056
+ var check32 = {
8016
8057
  id: "asset.image.tac-raster",
8017
8058
  name: "Image TAC (Raster)",
8018
8059
  description: "Rasterizes and checks TAC per image using Ghostscript",
@@ -8031,7 +8072,7 @@ var check31 = {
8031
8072
  const ghostscript = await resolveGhostscript();
8032
8073
  if (!ghostscript) {
8033
8074
  return [
8034
- inspectionFailed(check31.id, "Could not inspect image ink coverage: Ghostscript executable not found")
8075
+ inspectionFailed(check32.id, "Could not inspect image ink coverage: Ghostscript executable not found")
8035
8076
  ];
8036
8077
  }
8037
8078
  for (const file of files) {
@@ -8049,7 +8090,7 @@ var check31 = {
8049
8090
  if (nums.length === 4 && nums.every((n) => Number.isFinite(n))) {
8050
8091
  const tac = (nums[0] + nums[1] + nums[2] + nums[3]) * 100;
8051
8092
  if (tac > maxTac) {
8052
- results.push(finding(check31.id, {
8093
+ results.push(finding(check32.id, {
8053
8094
  severity: "warning",
8054
8095
  message: `Image TAC exceeds limit: ${tac.toFixed(1)}% (max ${maxTac}%)`,
8055
8096
  file,
@@ -8061,16 +8102,16 @@ var check31 = {
8061
8102
  }
8062
8103
  }
8063
8104
  } catch {
8064
- results.push(inspectionFailed(check31.id, `Could not inspect image ink coverage: ${file}`, { file }));
8105
+ results.push(inspectionFailed(check32.id, `Could not inspect image ink coverage: ${file}`, { file }));
8065
8106
  }
8066
8107
  }
8067
8108
  return results;
8068
8109
  }
8069
8110
  };
8070
- registerCheck(check31);
8111
+ registerCheck(check32);
8071
8112
 
8072
8113
  // src/checks/asset/approved-fonts.ts
8073
- var check32 = {
8114
+ var check33 = {
8074
8115
  id: "asset.font.approved-files",
8075
8116
  name: "Approved Font Files",
8076
8117
  description: "Checks font files against the approved file patterns",
@@ -8109,7 +8150,7 @@ var check32 = {
8109
8150
  for (const font of allFonts) {
8110
8151
  if (!approvedFonts.has(font)) {
8111
8152
  results.push({
8112
- checkId: check32.id,
8153
+ checkId: check33.id,
8113
8154
  severity: "warning",
8114
8155
  message: "Font file not in approved list",
8115
8156
  file: font
@@ -8119,7 +8160,7 @@ var check32 = {
8119
8160
  return results;
8120
8161
  }
8121
8162
  };
8122
- registerCheck(check32);
8163
+ registerCheck(check33);
8123
8164
 
8124
8165
  // src/checks/asset/font-license.ts
8125
8166
  import { existsSync as existsSync10 } from "node:fs";
@@ -8134,7 +8175,7 @@ var LICENSE_NAMES = [
8134
8175
  "OFL-1.1.txt",
8135
8176
  "COPYING"
8136
8177
  ];
8137
- var check33 = {
8178
+ var check34 = {
8138
8179
  id: "asset.font.license",
8139
8180
  name: "Font License",
8140
8181
  description: "Checks for license files in font directories",
@@ -8161,7 +8202,7 @@ var check33 = {
8161
8202
  const hasLicense = LICENSE_NAMES.some((name) => existsSync10(resolve6(fontDir, name)));
8162
8203
  if (!hasLicense) {
8163
8204
  results.push({
8164
- checkId: check33.id,
8205
+ checkId: check34.id,
8165
8206
  severity: "warning",
8166
8207
  message: `No font license file found in directory`,
8167
8208
  file: fontDir
@@ -8171,10 +8212,10 @@ var check33 = {
8171
8212
  return results;
8172
8213
  }
8173
8214
  };
8174
- registerCheck(check33);
8215
+ registerCheck(check34);
8175
8216
 
8176
8217
  // src/checks/heuristic/text-density.ts
8177
- var check34 = {
8218
+ var check35 = {
8178
8219
  id: "heuristic.whitespace.text-density",
8179
8220
  name: "Text Density",
8180
8221
  description: "Checks characters-per-page ratio",
@@ -8203,7 +8244,7 @@ var check34 = {
8203
8244
  });
8204
8245
  if (lowPages.length > 0) {
8205
8246
  results.push({
8206
- checkId: check34.id,
8247
+ checkId: check35.id,
8207
8248
  severity: "info",
8208
8249
  message: `Low text density on pages: ${lowPages.join(", ")} (below ${range.min} chars)`,
8209
8250
  file: ctx.pdfPath
@@ -8211,7 +8252,7 @@ var check34 = {
8211
8252
  }
8212
8253
  if (highPages.length > 0) {
8213
8254
  results.push({
8214
- checkId: check34.id,
8255
+ checkId: check35.id,
8215
8256
  severity: "info",
8216
8257
  message: `High text density on pages: ${highPages.join(", ")} (above ${range.max} chars)`,
8217
8258
  file: ctx.pdfPath
@@ -8220,11 +8261,11 @@ var check34 = {
8220
8261
  return results;
8221
8262
  }
8222
8263
  };
8223
- registerCheck(check34);
8264
+ registerCheck(check35);
8224
8265
 
8225
8266
  // src/checks/heuristic/section-density.ts
8226
- import { readFile as readFile20 } from "node:fs/promises";
8227
- var check35 = {
8267
+ import { readFile as readFile21 } from "node:fs/promises";
8268
+ var check36 = {
8228
8269
  id: "heuristic.chunking.section-density",
8229
8270
  name: "Section Density",
8230
8271
  description: "Checks heading/paragraph/callout density from source Markdown",
@@ -8240,7 +8281,7 @@ var check35 = {
8240
8281
  const results = [];
8241
8282
  for (const file of files) {
8242
8283
  try {
8243
- const content = await readFile20(file, "utf8");
8284
+ const content = await readFile21(file, "utf8");
8244
8285
  const lines = content.split(`
8245
8286
  `);
8246
8287
  let currentSectionStart = 0;
@@ -8250,7 +8291,7 @@ var check35 = {
8250
8291
  const line = lines[i];
8251
8292
  if (/^#{1,6}\s/.test(line)) {
8252
8293
  if (paragraphCount > maxParas) {
8253
- results.push(finding(check35.id, {
8294
+ results.push(finding(check36.id, {
8254
8295
  severity: "info",
8255
8296
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
8256
8297
  file,
@@ -8270,7 +8311,7 @@ var check35 = {
8270
8311
  }
8271
8312
  }
8272
8313
  if (paragraphCount > maxParas) {
8273
- results.push(finding(check35.id, {
8314
+ results.push(finding(check36.id, {
8274
8315
  severity: "info",
8275
8316
  message: `Section has ${paragraphCount} paragraphs (max recommended: ${maxParas})`,
8276
8317
  file,
@@ -8278,7 +8319,7 @@ var check35 = {
8278
8319
  }));
8279
8320
  }
8280
8321
  } catch {
8281
- results.push(inspectionFailed(check35.id, `Could not read source file: ${file}`, {
8322
+ results.push(inspectionFailed(check36.id, `Could not read source file: ${file}`, {
8282
8323
  file
8283
8324
  }));
8284
8325
  }
@@ -8286,10 +8327,10 @@ var check35 = {
8286
8327
  return results;
8287
8328
  }
8288
8329
  };
8289
- registerCheck(check35);
8330
+ registerCheck(check36);
8290
8331
 
8291
8332
  // src/checks/heuristic/layer-count.ts
8292
- var check36 = {
8333
+ var check37 = {
8293
8334
  id: "heuristic.decoration.layer-count",
8294
8335
  name: "Layer Count",
8295
8336
  description: "Counts image objects per page",
@@ -8314,7 +8355,7 @@ var check36 = {
8314
8355
  heavyPages.sort((a, b) => a - b);
8315
8356
  return [
8316
8357
  {
8317
- checkId: check36.id,
8358
+ checkId: check37.id,
8318
8359
  severity: "info",
8319
8360
  message: `Pages with many image layers (>${maxLayers}): ${heavyPages.join(", ")}`,
8320
8361
  file: ctx.pdfPath
@@ -8324,10 +8365,10 @@ var check36 = {
8324
8365
  return [];
8325
8366
  }
8326
8367
  };
8327
- registerCheck(check36);
8368
+ registerCheck(check37);
8328
8369
 
8329
8370
  // src/checks/heuristic/placement-variance.ts
8330
- var check37 = {
8371
+ var check38 = {
8331
8372
  id: "heuristic.layout.placement-variance",
8332
8373
  name: "Placement Variance",
8333
8374
  description: "Analyzes text baseline coordinates for layout consistency",
@@ -8345,7 +8386,7 @@ var check37 = {
8345
8386
  const uniqueX = new Set(positions.map((p) => Math.round(p.x)));
8346
8387
  return [
8347
8388
  {
8348
- checkId: check37.id,
8389
+ checkId: check38.id,
8349
8390
  severity: "info",
8350
8391
  message: `Layout analysis: ${uniqueX.size} unique horizontal text positions across ${positions.length} text blocks.`,
8351
8392
  file: ctx.pdfPath
@@ -8353,21 +8394,21 @@ var check37 = {
8353
8394
  ];
8354
8395
  }
8355
8396
  };
8356
- registerCheck(check37);
8397
+ registerCheck(check38);
8357
8398
 
8358
8399
  // src/checks/tool-check.ts
8359
8400
  async function checkToolAvailability(config, opts = {}) {
8360
8401
  const { checks: checks2 } = selectChecks(opts, config);
8361
8402
  const toolToChecks = new Map;
8362
- for (const check38 of checks2) {
8363
- if (!check38.requiredTools?.length)
8403
+ for (const check39 of checks2) {
8404
+ if (!check39.requiredTools?.length)
8364
8405
  continue;
8365
- for (const tool of check38.requiredTools) {
8406
+ for (const tool of check39.requiredTools) {
8366
8407
  const existing = toolToChecks.get(tool);
8367
8408
  if (existing) {
8368
- existing.push(check38.id);
8409
+ existing.push(check39.id);
8369
8410
  } else {
8370
- toolToChecks.set(tool, [check38.id]);
8411
+ toolToChecks.set(tool, [check39.id]);
8371
8412
  }
8372
8413
  }
8373
8414
  }
@@ -8383,11 +8424,11 @@ async function checkToolAvailability(config, opts = {}) {
8383
8424
  const missing = results.filter((r) => !r.found).map((r) => r.tool);
8384
8425
  const missingSet = new Set(missing);
8385
8426
  const skippedChecks = [];
8386
- for (const check38 of checks2) {
8387
- if (!check38.requiredTools?.length)
8427
+ for (const check39 of checks2) {
8428
+ if (!check39.requiredTools?.length)
8388
8429
  continue;
8389
- if (check38.requiredTools.some((t) => missingSet.has(t))) {
8390
- skippedChecks.push(check38.id);
8430
+ if (check39.requiredTools.some((t) => missingSet.has(t))) {
8431
+ skippedChecks.push(check39.id);
8391
8432
  }
8392
8433
  }
8393
8434
  return { available, missing, skippedChecks, toolToChecks };
@@ -8426,7 +8467,7 @@ function artifactName(title, format) {
8426
8467
  var BOOK_HTML_FILENAME = "book.html";
8427
8468
 
8428
8469
  // src/lib/markdown/index.ts
8429
- import { readdir as readdir3, readFile as readFile21, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
8470
+ import { readdir as readdir3, readFile as readFile22, writeFile as writeFile2, mkdir as mkdir4 } from "node:fs/promises";
8430
8471
 
8431
8472
  // src/lib/remote-auth/sync-messages.ts
8432
8473
  function isOnlineSibling(filepath) {
@@ -8566,7 +8607,7 @@ ${css.trim()}
8566
8607
  }
8567
8608
  return assembleBookHtml({
8568
8609
  files,
8569
- readText: (relPath) => readFile21(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
8610
+ readText: (relPath) => readFile22(join2(inputDir, canonicalChapterId(relPath)), "utf-8"),
8570
8611
  projectCss: inlined.css,
8571
8612
  preloadImages: cssAssetCopies.map((c) => c.to),
8572
8613
  title: opts.title,
@@ -8598,7 +8639,7 @@ async function renderChaptersToFile(inputDir, outDir, opts = {}) {
8598
8639
  }
8599
8640
 
8600
8641
  // src/lib/markdownlint-fix.ts
8601
- import { readFile as readFile22, writeFile as writeFile3 } from "node:fs/promises";
8642
+ import { readFile as readFile23, writeFile as writeFile3 } from "node:fs/promises";
8602
8643
  import { applyFixes } from "markdownlint";
8603
8644
  import { lint as lint2 } from "markdownlint/sync";
8604
8645
  async function uncommittedAmong(inputDir, files) {
@@ -8638,7 +8679,7 @@ async function applyMarkdownlintFixes(ctx) {
8638
8679
  for (const [file, violations] of Object.entries(results)) {
8639
8680
  if (!Array.isArray(violations) || violations.length === 0)
8640
8681
  continue;
8641
- const before = await readFile22(file, "utf8");
8682
+ const before = await readFile23(file, "utf8");
8642
8683
  const after = applyFixes(before, violations);
8643
8684
  if (after !== before)
8644
8685
  pending.push({ file, content: after });
@@ -8747,16 +8788,16 @@ async function runChecks(ctx, opts = {}) {
8747
8788
  }
8748
8789
  const release = retainPdfCache();
8749
8790
  try {
8750
- for (const check38 of checks2) {
8791
+ for (const check39 of checks2) {
8751
8792
  try {
8752
- let results = await check38.run(ctx);
8753
- const severityOverride = getCheckSeverityOverride(check38.id, ctx.config);
8793
+ let results = await check39.run(ctx);
8794
+ const severityOverride = getCheckSeverityOverride(check39.id, ctx.config);
8754
8795
  if (severityOverride) {
8755
8796
  for (const r of results) {
8756
8797
  r.severity = severityOverride;
8757
8798
  }
8758
8799
  }
8759
- const lintLevel = ruleLevel(ctx.config.lint.rules, check38.id);
8800
+ const lintLevel = ruleLevel(ctx.config.lint.rules, check39.id);
8760
8801
  if (lintLevel === "off")
8761
8802
  results = [];
8762
8803
  else if (lintLevel) {
@@ -8764,15 +8805,15 @@ async function runChecks(ctx, opts = {}) {
8764
8805
  r.severity = lintLevel === "warn" ? "warning" : "error";
8765
8806
  }
8766
8807
  if (results.length === 0) {
8767
- passed.push(check38.id);
8808
+ passed.push(check39.id);
8768
8809
  } else {
8769
8810
  allResults.push(...results);
8770
8811
  }
8771
8812
  } catch (err) {
8772
8813
  allResults.push({
8773
- checkId: check38.id,
8814
+ checkId: check39.id,
8774
8815
  severity: "error",
8775
- message: `Check "${check38.id}" threw: ${err instanceof Error ? err.message : String(err)}`
8816
+ message: `Check "${check39.id}" threw: ${err instanceof Error ? err.message : String(err)}`
8776
8817
  });
8777
8818
  }
8778
8819
  }
@@ -9064,7 +9105,7 @@ async function executeValidation(args) {
9064
9105
  };
9065
9106
  if (args.fix) {
9066
9107
  const selected = selectChecks(runnerOptions, config).checks;
9067
- if (selected.some((check38) => check38.id === MARKDOWNLINT_CHECK_ID)) {
9108
+ if (selected.some((check39) => check39.id === MARKDOWNLINT_CHECK_ID)) {
9068
9109
  await applyMarkdownlintFixes(context);
9069
9110
  } else {
9070
9111
  log.warn(`--fix: ${MARKDOWNLINT_CHECK_ID} is not part of this run — no files were fixed.`);
@@ -9151,7 +9192,7 @@ import { randomBytes } from "node:crypto";
9151
9192
 
9152
9193
  // src/lib/build-fingerprint.ts
9153
9194
  import * as fs from "node:fs";
9154
- import { mkdir as mkdir5, readFile as readFile23, writeFile as writeFile4 } from "node:fs/promises";
9195
+ import { mkdir as mkdir5, readFile as readFile24, writeFile as writeFile4 } from "node:fs/promises";
9155
9196
  import { createHash as createHash4 } from "node:crypto";
9156
9197
  import path12 from "node:path";
9157
9198
  import git from "isomorphic-git";
@@ -9238,8 +9279,8 @@ async function getEngineBundleHash() {
9238
9279
  getAssetPath("engine/gutterpress-agent.js")
9239
9280
  ]);
9240
9281
  const [viewerBytes, agentBytes] = await Promise.all([
9241
- readFile23(viewerPath),
9242
- readFile23(agentPath)
9282
+ readFile24(viewerPath),
9283
+ readFile24(agentPath)
9243
9284
  ]);
9244
9285
  const hash = createHash4("sha256");
9245
9286
  hash.update(viewerBytes);
@@ -9314,417 +9355,6 @@ async function writeBuildFingerprint(input) {
9314
9355
  return outPath;
9315
9356
  }
9316
9357
 
9317
- // src/lib/black-text-gray.ts
9318
- var MAX_CHANNEL = 0.12;
9319
- var MAX_SPREAD = 0.04;
9320
- function isNearBlack([r, g, b]) {
9321
- const max = Math.max(r, g, b);
9322
- return max <= MAX_CHANNEL && max - Math.min(r, g, b) <= MAX_SPREAD;
9323
- }
9324
- var WS = new Set([0, 9, 10, 12, 13, 32]);
9325
- var DELIM = new Set("()<>[]{}/%".split("").map((c) => c.charCodeAt(0)));
9326
- function* tokenize(s) {
9327
- let i = 0;
9328
- const n = s.length;
9329
- while (i < n) {
9330
- const c = s.charCodeAt(i);
9331
- if (WS.has(c)) {
9332
- i++;
9333
- continue;
9334
- }
9335
- const start = i;
9336
- if (c === 37) {
9337
- while (i < n && s[i] !== `
9338
- ` && s[i] !== "\r")
9339
- i++;
9340
- continue;
9341
- }
9342
- if (c === 40) {
9343
- let depth = 0;
9344
- while (i < n) {
9345
- const d = s[i++];
9346
- if (d === "\\")
9347
- i++;
9348
- else if (d === "(")
9349
- depth++;
9350
- else if (d === ")" && --depth === 0)
9351
- break;
9352
- }
9353
- yield { text: s.slice(start, i), start, end: i, op: false };
9354
- continue;
9355
- }
9356
- if (c === 60 && s[i + 1] === "<") {
9357
- i += 2;
9358
- yield { text: "<<", start, end: i, op: false };
9359
- continue;
9360
- }
9361
- if (c === 62 && s[i + 1] === ">") {
9362
- i += 2;
9363
- yield { text: ">>", start, end: i, op: false };
9364
- continue;
9365
- }
9366
- if (c === 60) {
9367
- while (i < n && s[i] !== ">")
9368
- i++;
9369
- i++;
9370
- yield { text: s.slice(start, i), start, end: i, op: false };
9371
- continue;
9372
- }
9373
- if (c === 91 || c === 93 || c === 62 || c === 123 || c === 125 || c === 41) {
9374
- i++;
9375
- yield { text: s[start], start, end: i, op: false };
9376
- continue;
9377
- }
9378
- i++;
9379
- while (i < n && !WS.has(s.charCodeAt(i)) && !DELIM.has(s.charCodeAt(i)))
9380
- i++;
9381
- const text = s.slice(start, i);
9382
- const isOperand = c === 47 || /^[+-]?(\d+\.?\d*|\.\d+)$/.test(text);
9383
- yield { text, start, end: i, op: !isOperand };
9384
- if (text === "ID") {
9385
- const m = /\sEI(?=\s|$)/g;
9386
- m.lastIndex = i;
9387
- const hit = m.exec(s);
9388
- i = hit ? hit.index + hit[0].length : n;
9389
- }
9390
- }
9391
- }
9392
- var LARGE_TEXT_PT = 24;
9393
- var SIZE_EPS = 0.05;
9394
- var area = (m) => Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
9395
-
9396
- class TextScale {
9397
- ctm;
9398
- font = 0;
9399
- tm = 1;
9400
- stack = [];
9401
- constructor(ctm) {
9402
- this.ctm = ctm;
9403
- }
9404
- get unit() {
9405
- return this.ctm;
9406
- }
9407
- get size() {
9408
- return this.font * this.tm * this.ctm;
9409
- }
9410
- op(op, nums) {
9411
- switch (op) {
9412
- case "q":
9413
- this.stack.push([this.ctm, this.font]);
9414
- return false;
9415
- case "Q": {
9416
- const p = this.stack.pop();
9417
- if (p)
9418
- [this.ctm, this.font] = p;
9419
- return true;
9420
- }
9421
- case "cm":
9422
- if (nums.length === 6)
9423
- this.ctm *= area(nums);
9424
- return true;
9425
- case "BT":
9426
- this.tm = 1;
9427
- return true;
9428
- case "Tm":
9429
- if (nums.length === 6)
9430
- this.tm = area(nums);
9431
- return true;
9432
- case "Tf":
9433
- this.font = Math.abs(nums[nums.length - 1] ?? 0) || 0;
9434
- return true;
9435
- }
9436
- return false;
9437
- }
9438
- }
9439
- function rewriteBlackText(src, ctx = {}) {
9440
- let fill = null;
9441
- let stroke = null;
9442
- const stack = [];
9443
- const scale = new TextScale(ctx.baseScale ?? 1);
9444
- let inText = false;
9445
- let fillDirty = false;
9446
- let strokeDirty = false;
9447
- const spans = [];
9448
- let lastOp = "";
9449
- let rect = null;
9450
- const edits = [];
9451
- let operands = [];
9452
- const rgbOf = () => {
9453
- if (operands.length !== 3)
9454
- return null;
9455
- const v = operands.map((t) => Number(t.text));
9456
- return v.some((x) => Number.isNaN(x)) ? null : { rgb: v, raw: operands.map((t) => t.text).join(" ") };
9457
- };
9458
- const isSmall = () => scale.size <= LARGE_TEXT_PT + SIZE_EPS;
9459
- let fillIns = null;
9460
- let strokeIns = null;
9461
- const syncOne = (colour, dirty, ins, at, gray, op) => {
9462
- if (!colour || !isNearBlack(colour.rgb))
9463
- return [dirty, ins];
9464
- const small = isSmall();
9465
- if (small && !dirty) {
9466
- const e = { start: at, end: at, text: ` ${gray}` };
9467
- edits.push(e);
9468
- return [true, e];
9469
- }
9470
- if (!small && dirty) {
9471
- if (ins)
9472
- ins.text = "";
9473
- else
9474
- edits.push({ start: at, end: at, text: ` ${colour.raw} ${op}` });
9475
- return [false, null];
9476
- }
9477
- return [dirty, ins];
9478
- };
9479
- const sync = (at) => {
9480
- [fillDirty, fillIns] = syncOne(fill, fillDirty, fillIns, at, "0 g", "rg");
9481
- [strokeDirty, strokeIns] = syncOne(stroke, strokeDirty, strokeIns, at, "0 G", "RG");
9482
- };
9483
- const flush = (s) => {
9484
- for (const r of s.rects) {
9485
- if (r.thickness > 0.2 * s.maxSmall)
9486
- continue;
9487
- edits.push({ start: r.start, end: r.start, text: "q 0 g " }, { start: r.end, end: r.end, text: " Q" });
9488
- }
9489
- };
9490
- for (const t of tokenize(src)) {
9491
- if (!t.op) {
9492
- operands.push(t);
9493
- continue;
9494
- }
9495
- const op = t.text;
9496
- const nums = operands.map((o) => Number(o.text));
9497
- const sizeMayHaveChanged = scale.op(op, nums);
9498
- switch (op) {
9499
- case "q":
9500
- stack.push([fill, stroke]);
9501
- break;
9502
- case "Q": {
9503
- const p = stack.pop();
9504
- if (p)
9505
- [fill, stroke] = p;
9506
- break;
9507
- }
9508
- case "BT":
9509
- inText = true;
9510
- fillDirty = strokeDirty = false;
9511
- fillIns = strokeIns = null;
9512
- sync(t.end);
9513
- break;
9514
- case "Tf":
9515
- case "Tm":
9516
- if (inText && sizeMayHaveChanged)
9517
- sync(t.end);
9518
- break;
9519
- case "ET":
9520
- if (inText) {
9521
- let ins = "";
9522
- if (fillDirty && fill)
9523
- ins += `${fill.raw} rg `;
9524
- if (strokeDirty && stroke)
9525
- ins += `${stroke.raw} RG `;
9526
- if (ins)
9527
- edits.push({ start: t.start, end: t.start, text: ins });
9528
- }
9529
- inText = false;
9530
- fillIns = strokeIns = null;
9531
- break;
9532
- case "rg":
9533
- case "RG": {
9534
- const c = rgbOf();
9535
- const isFill = op === "rg";
9536
- if (isFill)
9537
- fill = c;
9538
- else
9539
- stroke = c;
9540
- if (inText && c && isNearBlack(c.rgb) && isSmall()) {
9541
- edits.push({ start: operands[0].start, end: t.end, text: isFill ? "0 g" : "0 G" });
9542
- if (isFill)
9543
- fillDirty = true;
9544
- else
9545
- strokeDirty = true;
9546
- } else if (inText) {
9547
- if (isFill)
9548
- fillDirty = false;
9549
- else
9550
- strokeDirty = false;
9551
- }
9552
- break;
9553
- }
9554
- case "g":
9555
- case "k":
9556
- case "sc":
9557
- case "scn":
9558
- case "cs":
9559
- fill = null;
9560
- if (inText)
9561
- fillDirty = false;
9562
- break;
9563
- case "G":
9564
- case "K":
9565
- case "SC":
9566
- case "SCN":
9567
- case "CS":
9568
- stroke = null;
9569
- if (inText)
9570
- strokeDirty = false;
9571
- break;
9572
- case "Do": {
9573
- const name = operands[operands.length - 1]?.text;
9574
- if (name?.startsWith("/"))
9575
- ctx.onDo?.(name.slice(1), scale.unit);
9576
- break;
9577
- }
9578
- case "BDC":
9579
- case "BMC":
9580
- spans.push({ hasSmall: false, maxSmall: 0, rects: [] });
9581
- break;
9582
- case "EMC": {
9583
- const s = spans.pop();
9584
- if (!s)
9585
- break;
9586
- const parent = spans[spans.length - 1];
9587
- if (s.hasSmall)
9588
- flush(s);
9589
- else
9590
- parent?.rects.push(...s.rects);
9591
- if (parent && s.hasSmall) {
9592
- parent.hasSmall = true;
9593
- parent.maxSmall = Math.max(parent.maxSmall, s.maxSmall);
9594
- }
9595
- break;
9596
- }
9597
- case "Tj":
9598
- case "TJ":
9599
- case "'":
9600
- case '"': {
9601
- fillIns = strokeIns = null;
9602
- const span = spans[spans.length - 1];
9603
- if (span && inText && isSmall()) {
9604
- span.hasSmall = true;
9605
- span.maxSmall = Math.max(span.maxSmall, scale.size);
9606
- }
9607
- break;
9608
- }
9609
- case "re":
9610
- rect = !inText && fill && isNearBlack(fill.rgb) && operands.length === 4 && nums.every((x) => !Number.isNaN(x)) ? { start: operands[0].start, thickness: Math.min(Math.abs(nums[2]), Math.abs(nums[3])) * scale.unit } : null;
9611
- break;
9612
- case "f":
9613
- case "F":
9614
- case "f*":
9615
- if (rect && lastOp === "re")
9616
- spans[spans.length - 1]?.rects.push({ ...rect, end: t.end });
9617
- break;
9618
- }
9619
- lastOp = op;
9620
- operands = [];
9621
- }
9622
- return applyEdits(src, edits);
9623
- }
9624
- function applyEdits(s, edits) {
9625
- if (!edits.length)
9626
- return s;
9627
- edits.sort((a, b) => a.start - b.start);
9628
- let out = "";
9629
- let pos = 0;
9630
- for (const e of edits) {
9631
- out += s.slice(pos, e.start) + e.text;
9632
- pos = e.end;
9633
- }
9634
- return out + s.slice(pos);
9635
- }
9636
- async function visitContent(doc, visit) {
9637
- const { PDFName, PDFRawStream, PDFArray, PDFRef, PDFDict, decodePDFRawStream } = await import("pdf-lib");
9638
- const ctx = doc.context;
9639
- const SKIP = ["Filter", "DecodeParms", "Length"].map((k) => PDFName.of(k));
9640
- let changed = false;
9641
- const decode = (s) => {
9642
- try {
9643
- return Buffer.from(decodePDFRawStream(s).decode()).toString("latin1");
9644
- } catch {
9645
- return null;
9646
- }
9647
- };
9648
- const replace = (ref, old, text) => {
9649
- const entries = {};
9650
- for (const [k, v] of old.dict.entries())
9651
- if (!SKIP.includes(k))
9652
- entries[k.decodeText()] = v;
9653
- ctx.assign(ref, ctx.flateStream(Buffer.from(text, "latin1"), entries));
9654
- changed = true;
9655
- };
9656
- const formScale = new Map;
9657
- const formMatrixScale = (form) => {
9658
- const m = form.dict.lookupMaybe(PDFName.of("Matrix"), PDFArray);
9659
- const nums = m?.asArray().map((n) => Number(n.toString()));
9660
- return nums?.length === 6 && !nums.some(Number.isNaN) ? area(nums) : 1;
9661
- };
9662
- const pages = doc.getPages();
9663
- for (let p = 0;p < pages.length; p++) {
9664
- const page = pages[p];
9665
- const contents = page.node.Contents();
9666
- const refs = [];
9667
- if (contents instanceof PDFArray) {
9668
- for (let i = 0;i < contents.size(); i++) {
9669
- const r = contents.get(i);
9670
- if (r instanceof PDFRef)
9671
- refs.push(r);
9672
- }
9673
- } else if (contents instanceof PDFRawStream) {
9674
- const r = page.node.get(PDFName.of("Contents"));
9675
- if (r instanceof PDFRef)
9676
- refs.push(r);
9677
- }
9678
- const streams = refs.map((r) => ctx.lookup(r));
9679
- if (!refs.length || !streams.every((s) => s instanceof PDFRawStream))
9680
- continue;
9681
- const parts = streams.map((s) => decode(s));
9682
- if (parts.some((q) => q === null))
9683
- continue;
9684
- const joined = parts.join(`
9685
- `);
9686
- const xobjects = page.node.Resources()?.lookupMaybe(PDFName.of("XObject"), PDFDict);
9687
- const next = visit(joined, {
9688
- baseScale: 1,
9689
- onDo: (name, scale) => {
9690
- const ref = xobjects?.get(PDFName.of(name));
9691
- const form = ref instanceof PDFRef ? ctx.lookup(ref) : undefined;
9692
- if (!(ref instanceof PDFRef) || !(form instanceof PDFRawStream))
9693
- return;
9694
- if (form.dict.get(PDFName.of("Subtype")) !== PDFName.of("Form"))
9695
- return;
9696
- const at = scale * formMatrixScale(form);
9697
- formScale.set(ref.tag, Math.min(formScale.get(ref.tag) ?? Infinity, at));
9698
- }
9699
- });
9700
- if (next === joined)
9701
- continue;
9702
- replace(refs[0], streams[0], next);
9703
- page.node.set(PDFName.of("Contents"), refs[0]);
9704
- }
9705
- for (const [ref, obj] of ctx.enumerateIndirectObjects()) {
9706
- if (!(obj instanceof PDFRawStream))
9707
- continue;
9708
- if (obj.dict.get(PDFName.of("Subtype")) !== PDFName.of("Form"))
9709
- continue;
9710
- const text = decode(obj);
9711
- if (text === null)
9712
- continue;
9713
- const next = visit(text, {
9714
- baseScale: formScale.get(ref.tag) ?? 0
9715
- });
9716
- if (next !== text)
9717
- replace(ref, obj, next);
9718
- }
9719
- return changed;
9720
- }
9721
- async function blackTextToGray(pdf) {
9722
- const { PDFDocument } = await import("pdf-lib");
9723
- const doc = await PDFDocument.load(pdf, { updateMetadata: false });
9724
- const changed = await visitContent(doc, (src, info2) => rewriteBlackText(src, info2));
9725
- return changed ? doc.save({ updateFieldAppearances: false }) : pdf;
9726
- }
9727
-
9728
9358
  // src/lib/build-preflight.ts
9729
9359
  var INSTALL_HINTS2 = {
9730
9360
  gs: INSTALL_HINTS.gs.body,
@@ -10020,7 +9650,7 @@ class PdfOutput {
10020
9650
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10021
9651
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10022
9652
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10023
- const { buildNativePdf } = await import("./engine-zdb41ay1.js");
9653
+ const { buildNativePdf } = await import("./engine-63r7pxsy.js");
10024
9654
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10025
9655
  title: config.title,
10026
9656
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -10155,7 +9785,7 @@ function openPath(filePath) {
10155
9785
  import path16 from "path";
10156
9786
  import { tmpdir } from "os";
10157
9787
  import { randomBytes as randomBytes2 } from "crypto";
10158
- import { readdir as readdir4, readFile as readFile24, writeFile as writeFile5 } from "node:fs/promises";
9788
+ import { readdir as readdir4, readFile as readFile25, writeFile as writeFile5 } from "node:fs/promises";
10159
9789
 
10160
9790
  // src/utils/file-utils.ts
10161
9791
  import { promises as fs3 } from "fs";
@@ -10704,7 +10334,7 @@ async function cleanupOrphanTempDirs() {
10704
10334
  const dir = path16.join(TEMP_DIR_BASE, entry);
10705
10335
  const pidFile = path16.join(dir, PID_FILE_NAME);
10706
10336
  try {
10707
- const raw = await readFile24(pidFile, "utf8");
10337
+ const raw = await readFile25(pidFile, "utf8");
10708
10338
  const pid = parseInt(raw.trim(), 10);
10709
10339
  if (!isProcessAlive(pid)) {
10710
10340
  debug(`Removing orphan temp dir ${dir} (pid ${pid} not alive)`);
@@ -10811,7 +10441,7 @@ function createServerState(inputPath, tempDir, config, options) {
10811
10441
  import http from "node:http";
10812
10442
  import net from "node:net";
10813
10443
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
10814
- import { readFile as readFile25, stat as stat5 } from "node:fs/promises";
10444
+ import { readFile as readFile26, stat as stat5 } from "node:fs/promises";
10815
10445
  import path17 from "path";
10816
10446
  import { WebSocket, WebSocketServer } from "ws";
10817
10447
  var HMR_PATH = "/__gutterpress-hmr";
@@ -10880,7 +10510,7 @@ async function serveStatic(absPath, res, cacheControl = "no-store", extraHeaders
10880
10510
  }
10881
10511
  let data;
10882
10512
  try {
10883
- data = await readFile25(filePath);
10513
+ data = await readFile26(filePath);
10884
10514
  } catch {
10885
10515
  res.writeHead(404);
10886
10516
  res.end("Not Found");
@@ -10929,7 +10559,7 @@ async function serveRevalidatedStatic(req, absPath, res, validators) {
10929
10559
  let data;
10930
10560
  if (!validator || validator.stamp !== stamp) {
10931
10561
  try {
10932
- data = await readFile25(absPath);
10562
+ data = await readFile26(absPath);
10933
10563
  } catch {
10934
10564
  await serveStatic(absPath, res);
10935
10565
  return;
@@ -11239,7 +10869,7 @@ async function startPreviewServer(options) {
11239
10869
  }
11240
10870
 
11241
10871
  // src/lib/project-scaffold.ts
11242
- import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile26, stat as stat6, writeFile as writeFile6 } from "node:fs/promises";
10872
+ import { access, copyFile, cp as cp2, mkdir as mkdir7, readdir as readdir5, readFile as readFile27, stat as stat6, writeFile as writeFile6 } from "node:fs/promises";
11243
10873
  import { constants as FS, existsSync as existsSync12 } from "node:fs";
11244
10874
  import path18 from "node:path";
11245
10875
 
@@ -11438,7 +11068,7 @@ async function scaffoldProject(options) {
11438
11068
  async function firstSourceFile(manifestPath) {
11439
11069
  let text;
11440
11070
  try {
11441
- text = await readFile26(manifestPath, "utf8");
11071
+ text = await readFile27(manifestPath, "utf8");
11442
11072
  } catch {
11443
11073
  return;
11444
11074
  }
@@ -11450,7 +11080,7 @@ async function firstSourceFile(manifestPath) {
11450
11080
  return m ? m[1].trim() : undefined;
11451
11081
  }
11452
11082
  async function fillTemplateFile(filePath, substitutions) {
11453
- let text = await readFile26(filePath, "utf8");
11083
+ let text = await readFile27(filePath, "utf8");
11454
11084
  for (const [token, value] of Object.entries(substitutions)) {
11455
11085
  text = text.split(token).join(value);
11456
11086
  }
@@ -11458,7 +11088,7 @@ async function fillTemplateFile(filePath, substitutions) {
11458
11088
  }
11459
11089
 
11460
11090
  // src/lib/extension-scaffold.ts
11461
- import { access as access2, mkdir as mkdir8, readFile as readFile27, writeFile as writeFile7 } from "node:fs/promises";
11091
+ import { access as access2, mkdir as mkdir8, readFile as readFile28, writeFile as writeFile7 } from "node:fs/promises";
11462
11092
  import { constants as FS2 } from "node:fs";
11463
11093
  import path19 from "node:path";
11464
11094
  var EXTENSION_KINDS = ["plugin", "theme"];
@@ -11575,7 +11205,7 @@ async function scaffoldExtension(options) {
11575
11205
  const src = await getAssetPath(`extension-templates/${kind}/${assetName}`);
11576
11206
  const dest = path19.join(extensionDir, relDest);
11577
11207
  await mkdir8(path19.dirname(dest), { recursive: true });
11578
- const text = await readFile27(src, "utf8");
11208
+ const text = await readFile28(src, "utf8");
11579
11209
  const table = relDest.endsWith(".json") ? jsonSafe : plain;
11580
11210
  await writeFile7(dest, substitute(text, table), "utf8");
11581
11211
  written.push(relDest);
@@ -11620,7 +11250,7 @@ var SKIP_ENTRIES = new Set([
11620
11250
 
11621
11251
  // src/lib/extension-import.ts
11622
11252
  import { existsSync as existsSync13 } from "node:fs";
11623
- import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile28, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11253
+ import { cp as cp3, mkdir as mkdir9, mkdtemp as mkdtemp2, readFile as readFile29, rm as rm4, writeFile as writeFile8 } from "node:fs/promises";
11624
11254
  import { tmpdir as tmpdir2 } from "node:os";
11625
11255
  import path20 from "node:path";
11626
11256
  import { unzipSync } from "fflate";
@@ -11693,7 +11323,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
11693
11323
  if (!existsSync13(cssPath)) {
11694
11324
  throw new Error("A theme must contain a theme.css file.");
11695
11325
  }
11696
- const css = await readFile28(cssPath, "utf8");
11326
+ const css = await readFile29(cssPath, "utf8");
11697
11327
  const { reject, warnings: printFindings } = classifyThemeCssFindings(checkCss(css, "theme.css"));
11698
11328
  if (reject) {
11699
11329
  throw new Error(`theme.css could not be parsed — ${reject.message}`);
@@ -11721,7 +11351,7 @@ async function finalizeExtensionImport(projectDir, sourceDir, relPaths, fallback
11721
11351
  for (let i = 0;i < extraSheets.length; i++) {
11722
11352
  const rel = extraSheets[i];
11723
11353
  const sheetPath = extraSheetPaths[i];
11724
- const sheetCss = await readFile28(sheetPath, "utf8");
11354
+ const sheetCss = await readFile29(sheetPath, "utf8");
11725
11355
  const { reject: sheetReject, warnings: sheetFindings } = classifyThemeCssFindings(checkCss(sheetCss, rel));
11726
11356
  if (sheetReject) {
11727
11357
  throw new Error(`${rel} could not be parsed — ${sheetReject.message}`);
@@ -11754,7 +11384,7 @@ async function completeLandedManifest(dest, id, meta, metaExists) {
11754
11384
  let pkg = {};
11755
11385
  if (metaExists) {
11756
11386
  try {
11757
- const parsed = JSON.parse(await readFile28(file, "utf8"));
11387
+ const parsed = JSON.parse(await readFile29(file, "utf8"));
11758
11388
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
11759
11389
  pkg = parsed;
11760
11390
  }
@@ -11856,11 +11486,11 @@ async function importExtensionFromCssText(projectDir, css, name) {
11856
11486
  async function importExtensionFromFile(projectDir, filePath) {
11857
11487
  const ext = path20.extname(filePath).toLowerCase();
11858
11488
  if (ext === ".zip") {
11859
- const buf = await readFile28(filePath);
11489
+ const buf = await readFile29(filePath);
11860
11490
  return importExtensionFromZip(projectDir, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
11861
11491
  }
11862
11492
  if (ext === ".css") {
11863
- const css = await readFile28(filePath, "utf8");
11493
+ const css = await readFile29(filePath, "utf8");
11864
11494
  return importExtensionFromCssText(projectDir, css, prettify(path20.basename(filePath, ".css")));
11865
11495
  }
11866
11496
  throw new Error("Choose a .zip extension package or a .css stylesheet.");
@@ -12644,7 +12274,7 @@ var drivethrurpgProvider = {
12644
12274
  };
12645
12275
 
12646
12276
  // src/lib/publish/providers/gdrive.ts
12647
- import { mkdtemp as mkdtemp3, readFile as readFile29, readdir as readdir6, rm as rm5, stat as stat7, writeFile as writeFile10 } from "node:fs/promises";
12277
+ import { mkdtemp as mkdtemp3, readFile as readFile30, readdir as readdir6, rm as rm5, stat as stat7, writeFile as writeFile10 } from "node:fs/promises";
12648
12278
  import { tmpdir as tmpdir3 } from "node:os";
12649
12279
  import path22 from "node:path";
12650
12280
  import { Zip, ZipDeflate } from "fflate";
@@ -13036,12 +12666,12 @@ async function collectZipEntries(root, dir, out, onWarn) {
13036
12666
  await collectZipEntries(root, full, out, onWarn);
13037
12667
  } else if (entry.isFile()) {
13038
12668
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13039
- out[rel] = await readFile29(full);
12669
+ out[rel] = await readFile30(full);
13040
12670
  } else if (entry.isSymbolicLink()) {
13041
12671
  const resolved = await stat7(full).catch(() => null);
13042
12672
  if (resolved?.isFile()) {
13043
12673
  const rel = path22.relative(root, full).split(path22.sep).join("/");
13044
- out[rel] = await readFile29(full);
12674
+ out[rel] = await readFile30(full);
13045
12675
  } else if (resolved?.isDirectory()) {
13046
12676
  await collectZipEntries(root, full, out, onWarn);
13047
12677
  } else {
@@ -14028,7 +13658,7 @@ import git8 from "isomorphic-git";
14028
13658
  // src/lib/remote-auth/scorched-earth.ts
14029
13659
  import git9 from "isomorphic-git";
14030
13660
  // src/lib/publish/selections.ts
14031
- import { chmod as chmod2, mkdir as mkdir13, readFile as readFile30, writeFile as writeFile12 } from "node:fs/promises";
13661
+ import { chmod as chmod2, mkdir as mkdir13, readFile as readFile31, writeFile as writeFile12 } from "node:fs/promises";
14032
13662
  import path26 from "node:path";
14033
13663
  class PublishSelectionsStore {
14034
13664
  filePath;
@@ -14038,7 +13668,7 @@ class PublishSelectionsStore {
14038
13668
  }
14039
13669
  async read() {
14040
13670
  try {
14041
- const raw = await readFile30(this.filePath, "utf8");
13671
+ const raw = await readFile31(this.filePath, "utf8");
14042
13672
  const parsed = JSON.parse(raw);
14043
13673
  if (parsed && typeof parsed === "object") {
14044
13674
  return {