gutterpress 0.11.23-beta.1 → 0.11.23-beta.2

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 (44) hide show
  1. package/README.md +1 -1
  2. package/dist/api/index.js +5 -5
  3. package/dist/{build-dg8a566j.js → build-3e18wnqj.js} +8 -8
  4. package/dist/{cli-vhav8g82.js → cli-0ep11b0q.js} +1 -1
  5. package/dist/{cli-e31jshz6.js → cli-88arpdke.js} +2 -2
  6. package/dist/{cli-1fy1pm9d.js → cli-9n9kg58f.js} +1 -1
  7. package/dist/{cli-k087gj0p.js → cli-pd2cje5b.js} +122 -11
  8. package/dist/{cli-989eg8jz.js → cli-pnrararp.js} +11 -10
  9. package/dist/{cli-ttj95aex.js → cli-tgnyvra3.js} +13 -3
  10. package/dist/{cli-31anr0r9.js → cli-v0evdpdf.js} +68 -28
  11. package/dist/{cli-s1zt779w.js → cli-x7gcdg6w.js} +1 -1
  12. package/dist/cli.js +12 -12
  13. package/dist/{doctor-g4a42tcz.js → doctor-2f4s9dyn.js} +3 -3
  14. package/dist/engine/compiler/postprocess.d.ts +3 -0
  15. package/dist/engine/shared/pdf-inspect.d.ts +33 -0
  16. package/dist/{engine-jvkcahr1.js → engine-c4pxz79g.js} +1 -1
  17. package/dist/{engine-wgkaqjsc.js → engine-cs5g2ez0.js} +2 -2
  18. package/dist/{ext-w9d5ftcg.js → ext-70gv86v5.js} +8 -8
  19. package/dist/{extension-restore-5x4mj79d.js → extension-restore-chj5nx3t.js} +2 -2
  20. package/dist/{extension-restore-as0g38p1.js → extension-restore-pd87p8r2.js} +4 -4
  21. package/dist/{gutterpress-agent-d29ry3ak.js → gutterpress-agent-wyryqehx.js} +13 -4
  22. package/dist/{gutterpress-viewer-ze691rrd.js → gutterpress-viewer-ywkf49q5.js} +38 -153
  23. package/dist/{index-c6y0p7jy.js → index-0k43evt0.js} +66 -22
  24. package/dist/{index-fk2kpf2t.js → index-2t1gdma7.js} +121 -10
  25. package/dist/{index-cw03e9p4.js → index-7j3hkwya.js} +68 -28
  26. package/dist/{index-a4j3xcfp.js → index-80vg3ed6.js} +13 -3
  27. package/dist/{index-798jt0qc.js → index-swfdgn2n.js} +2 -2
  28. package/dist/{index-rj5vrhz6.js → index-ytjkvwy5.js} +1 -1
  29. package/dist/index.js +6 -6
  30. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  31. package/dist/lib/source-provider.d.ts +27 -0
  32. package/dist/lib/version-changes.d.ts +14 -4
  33. package/dist/{new-jtk028rf.js → new-d61n5cn0.js} +8 -8
  34. package/dist/{plugins-bcbe7xs8.js → plugins-0hhw6s17.js} +1 -1
  35. package/dist/{plugins-zsm6wv79.js → plugins-e0n46s6m.js} +3 -3
  36. package/dist/{preflight-cs6mj1d6.js → preflight-fjayhjr5.js} +8 -8
  37. package/dist/{preview-d1j1g4qf.js → preview-h815jrfb.js} +8 -8
  38. package/dist/{publish-7yw4jyrt.js → publish-ft39zgma.js} +8 -8
  39. package/dist/render.js +12 -2
  40. package/dist/{source-provider-n7vehh6t.js → source-provider-j1w7byeq.js} +5 -1
  41. package/dist/{source-provider-tgzet3j5.js → source-provider-tjaf303m.js} +5 -1
  42. package/dist/test-helpers/vocabulary.d.ts +12 -0
  43. package/dist/{validate-rkcdnzhw.js → validate-b201dbrb.js} +8 -8
  44. package/package.json +1 -1
package/README.md CHANGED
@@ -221,7 +221,7 @@ gutterpress build [input-dir] [options]
221
221
  --skip-lint Skip the CSS print-safety check (default: it runs for pdf/pdfx)
222
222
  --skip-pre-validate Skip pre-build validation
223
223
  --skip-post-validate Skip post-build PDF/X validation
224
- --allow-shrink Build anyway when content is wider than the page content box. Chromium then scales the WHOLE book down to fit it — the build reports that whole-document scale (e.g. "about 0.72x its declared size") plus every offender, as warnings.
224
+ --allow-shrink Build anyway when content is wider than the page content box. Chromium then scales the WHOLE book down to fit it — the build reports the scale the PDF printed at (e.g. "printed at 72.0000% of its declared size") plus every offender, as warnings.
225
225
  ```
226
226
 
227
227
  A `--out <dir>` is shared between formats safely: each build delivers only
package/dist/api/index.js CHANGED
@@ -105,7 +105,7 @@ import {
105
105
  substituteVariables,
106
106
  syncProject,
107
107
  testRemoteAccess
108
- } from "../index-c6y0p7jy.js";
108
+ } from "../index-0k43evt0.js";
109
109
  import {
110
110
  DEFAULT_NPM_REGISTRY,
111
111
  MANIFEST_FILENAMES,
@@ -134,7 +134,7 @@ import {
134
134
  setExtensionEnabled,
135
135
  updateExtensions,
136
136
  validateProjectExtensions
137
- } from "../index-rj5vrhz6.js";
137
+ } from "../index-ytjkvwy5.js";
138
138
  import {
139
139
  BUNDLED_EXTENSIONS,
140
140
  EXTENSION_MANIFEST_FILENAME,
@@ -149,9 +149,9 @@ import {
149
149
  pathEscapesFolder,
150
150
  readExtensionMeta,
151
151
  resolveExtension
152
- } from "../index-a4j3xcfp.js";
152
+ } from "../index-80vg3ed6.js";
153
153
  import"../index-ht1bcqdd.js";
154
- import"../index-798jt0qc.js";
154
+ import"../index-swfdgn2n.js";
155
155
  import {
156
156
  BuildError
157
157
  } from "../index-tayzfr9c.js";
@@ -176,7 +176,7 @@ import {
176
176
  revisionNameFrom,
177
177
  switchBranch,
178
178
  whenGitIdle
179
- } from "../index-cw03e9p4.js";
179
+ } from "../index-7j3hkwya.js";
180
180
  import {
181
181
  EXTENSIONS_DIR
182
182
  } from "../index-5x42amh8.js";
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  runBuild,
3
3
  splitOutPath
4
- } from "./cli-989eg8jz.js";
5
- import"./cli-vhav8g82.js";
4
+ } from "./cli-pnrararp.js";
5
+ import"./cli-0ep11b0q.js";
6
6
  import"./cli-ht1bcqdd.js";
7
- import"./cli-k087gj0p.js";
7
+ import"./cli-pd2cje5b.js";
8
8
  import"./cli-5crpmz42.js";
9
9
  import"./cli-eyyggect.js";
10
10
  import"./cli-asap4t1z.js";
11
- import"./cli-31anr0r9.js";
11
+ import"./cli-v0evdpdf.js";
12
12
  import"./cli-v5mp7a6q.js";
13
- import"./cli-e31jshz6.js";
14
- import"./cli-ttj95aex.js";
13
+ import"./cli-88arpdke.js";
14
+ import"./cli-tgnyvra3.js";
15
15
  import {
16
16
  log
17
- } from "./cli-1fy1pm9d.js";
17
+ } from "./cli-9n9kg58f.js";
18
18
  import {
19
19
  UsageError,
20
20
  parseFormat,
@@ -22,7 +22,7 @@ import {
22
22
  rejectExtraPositionals,
23
23
  rejectUnknownFlags
24
24
  } from "./cli-d8xjns66.js";
25
- import"./cli-s1zt779w.js";
25
+ import"./cli-x7gcdg6w.js";
26
26
  import"./cli-65gy8ask.js";
27
27
  import {
28
28
  BuildError
@@ -14,7 +14,7 @@ import {
14
14
  import {
15
15
  PACKAGE_VERSION,
16
16
  log
17
- } from "./cli-1fy1pm9d.js";
17
+ } from "./cli-9n9kg58f.js";
18
18
  import {
19
19
  __require
20
20
  } from "./cli-37x76zdn.js";
@@ -11,10 +11,10 @@ import {
11
11
  prettify,
12
12
  readExtensionMeta,
13
13
  slugify
14
- } from "./cli-ttj95aex.js";
14
+ } from "./cli-tgnyvra3.js";
15
15
  import {
16
16
  log
17
- } from "./cli-1fy1pm9d.js";
17
+ } from "./cli-9n9kg58f.js";
18
18
  import {
19
19
  UsageError
20
20
  } from "./cli-d8xjns66.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  package_default
3
- } from "./cli-s1zt779w.js";
3
+ } from "./cli-x7gcdg6w.js";
4
4
 
5
5
  // src/utils/logger.ts
6
6
  var currentLevel = "INFO";
@@ -1032,12 +1032,14 @@ function needsMeasurement(value) {
1032
1032
 
1033
1033
  // src/engine/shared/pdf-inspect.ts
1034
1034
  import {
1035
+ decodePDFRawStream,
1035
1036
  PDFArray,
1036
1037
  PDFDict,
1037
1038
  PDFDocument,
1038
1039
  PDFHexString,
1039
1040
  PDFName,
1040
1041
  PDFNumber,
1042
+ PDFRawStream,
1041
1043
  PDFRef,
1042
1044
  PDFString
1043
1045
  } from "pdf-lib";
@@ -1158,6 +1160,52 @@ async function inspectPdf(bytes) {
1158
1160
  walkOutline(outlines);
1159
1161
  return { pageCount: pages.length, boxes, linkTargets, namedDests, outline };
1160
1162
  }
1163
+ var PROLOGUE = /^\s*(\d*\.?\d+) 0 0 -\d*\.?\d+ -?\d*\.?\d+ -?\d*\.?\d+ cm/;
1164
+ var CLIP = /(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+) re\s+W\*? n\b/g;
1165
+ var UNIFORM = /\bq\s+(\d*\.?\d+) 0 0 \1 (-?\d*\.?\d+) -?\d*\.?\d+ cm/g;
1166
+ var UNSCALED_EPSILON = 0.000001;
1167
+ function printScale(doc) {
1168
+ const readings = new Map;
1169
+ for (const page of doc.getPages()) {
1170
+ const contents = page.node.Contents();
1171
+ const streams = contents instanceof PDFArray ? contents.asArray().map((ref) => doc.context.lookup(ref)) : [contents];
1172
+ let text = "";
1173
+ for (const stream of streams) {
1174
+ if (!(stream instanceof PDFRawStream))
1175
+ continue;
1176
+ try {
1177
+ text += Buffer.from(decodePDFRawStream(stream).decode()).toString("latin1") + `
1178
+ `;
1179
+ } catch {}
1180
+ }
1181
+ const prologue = PROLOGUE.exec(text);
1182
+ if (!prologue)
1183
+ continue;
1184
+ let area;
1185
+ for (const m of text.matchAll(CLIP)) {
1186
+ const size = Number(m[3]) * Number(m[4]);
1187
+ if (!area || size > area.size)
1188
+ area = { x: Number(m[1]), size, end: m.index + m[0].length };
1189
+ }
1190
+ if (!area)
1191
+ continue;
1192
+ UNIFORM.lastIndex = area.end;
1193
+ for (let m;m = UNIFORM.exec(text); ) {
1194
+ if (Math.abs(Number(m[2]) - area.x) > 0.5)
1195
+ continue;
1196
+ const scale = Number(m[1]) * Number(prologue[1]) * 96 / 72;
1197
+ readings.set(scale, (readings.get(scale) ?? 0) + 1);
1198
+ break;
1199
+ }
1200
+ if ([...readings.values()].some((n) => n >= 3))
1201
+ break;
1202
+ }
1203
+ let best = null;
1204
+ for (const [scale, n] of readings)
1205
+ if (best === null || n > readings.get(best))
1206
+ best = scale;
1207
+ return best === null ? null : Math.abs(1 - best) < UNSCALED_EPSILON ? 1 : best;
1208
+ }
1161
1209
 
1162
1210
  // src/lib/embedded-assets.ts
1163
1211
  import { existsSync } from "node:fs";
@@ -1185,10 +1233,10 @@ var preview_shell_default = "./preview-shell-wk9k3gcd.js";
1185
1233
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
1186
1234
 
1187
1235
  // src/assets/engine/gutterpress-viewer.js
1188
- var gutterpress_viewer_default = "./gutterpress-viewer-ze691rrd.js";
1236
+ var gutterpress_viewer_default = "./gutterpress-viewer-ywkf49q5.js";
1189
1237
 
1190
1238
  // src/assets/engine/gutterpress-agent.js
1191
- var gutterpress_agent_default = "./gutterpress-agent-d29ry3ak.js";
1239
+ var gutterpress_agent_default = "./gutterpress-agent-wyryqehx.js";
1192
1240
 
1193
1241
  // src/assets/templates/book/manifest.yaml
1194
1242
  var manifest_default = "./manifest-mffah3hr.yaml";
@@ -1483,6 +1531,7 @@ function round(n) {
1483
1531
  import { PDFDocument as PDFDocument2, PDFName as PDFName2, PDFNumber as PDFNumber2, rgb } from "pdf-lib";
1484
1532
  async function postprocess(input, opts) {
1485
1533
  const doc = await PDFDocument2.load(input, { updateMetadata: false });
1534
+ const scale = printScale(doc);
1486
1535
  const g = opts.geometry;
1487
1536
  const inset = g.bleed + g.slug;
1488
1537
  const media = [0, 0, g.media.width, g.media.height];
@@ -1529,7 +1578,8 @@ async function postprocess(input, opts) {
1529
1578
  bytes,
1530
1579
  pageCount: doc.getPageCount(),
1531
1580
  padded,
1532
- boxes: { media, bleed: bleedBox, trim: trimBox }
1581
+ boxes: { media, bleed: bleedBox, trim: trimBox },
1582
+ scale
1533
1583
  };
1534
1584
  }
1535
1585
  function drawCropMarks(page, g) {
@@ -1820,8 +1870,6 @@ ${pinCss}`);
1820
1870
  const msg = `${headline}:
1821
1871
  ${describe(widthOffenders.boxes)}`;
1822
1872
  if (opts.allowShrink) {
1823
- if (scale !== null)
1824
- diagnose("engine.width.overflow", `${headline}.`);
1825
1873
  for (const o of widthOffenders.boxes)
1826
1874
  diagnose("engine.width.overflow", `${o.desc} is ${Math.round(o.px)}px wide, past the ${Math.round(widthOffenders.limitPx)}px page content box. ${o.left < -1 ? "Keep it inside the page content box." : "Give it an explicit width that fits."}`);
1827
1875
  log(`WARNING: ${msg}`);
@@ -1837,6 +1885,54 @@ ${describe(widthOffenders.intrinsics)}`;
1837
1885
  diagnose("engine.width.intrinsic", `${o.desc} has no width set and is naturally wider than the page — if the printed output comes out smaller than your CSS says, give it an explicit width (e.g. width: 100%).`);
1838
1886
  log(`WARNING: ${msg}`);
1839
1887
  }
1888
+ let unclipped = null;
1889
+ if (!widthOffenders.boxes.length) {
1890
+ const g = tier2.geometry;
1891
+ const ext = g.bleed + g.slug;
1892
+ const contentPx = (name, pseudos) => {
1893
+ const { width, margin } = resolvePage(model, { name: name || undefined, pseudos }).geometry;
1894
+ return (width + 2 * ext - bleedMargin(margin.left, g) - bleedMargin(margin.right, g)) * 96 / 72;
1895
+ };
1896
+ const pages = await page.evaluate(`(() => {
1897
+ const body = document.body;
1898
+ const named = (el) => { const p = getComputedStyle(el).page; return p && p !== "auto" ? p : ""; };
1899
+ if (!body) return { first: "", used: [], reason: "there is no body" };
1900
+ const bs = getComputedStyle(body);
1901
+ const r = body.getBoundingClientRect();
1902
+ if (
1903
+ Math.abs(r.left + parseFloat(bs.borderLeftWidth)) > 0.01 ||
1904
+ Math.abs(r.right - parseFloat(bs.borderRightWidth) - window.innerWidth) > 0.01
1905
+ ) return { first: "", used: [], reason: "the body does not span the page content box" };
1906
+ // The first page's name: the root's, overridden down the chain of
1907
+ // first in-flow children (CSS Paged Media's start page value).
1908
+ let first = named(document.documentElement) || named(body);
1909
+ for (let el = body; el; ) {
1910
+ el = [...el.children].find((c) => {
1911
+ const cs = getComputedStyle(c);
1912
+ return cs.display !== "none" && cs.position !== "absolute" && cs.position !== "fixed";
1913
+ });
1914
+ if (el && named(el)) first = named(el);
1915
+ }
1916
+ const used = new Set([""]);
1917
+ for (const el of document.querySelectorAll("*")) used.add(named(el));
1918
+ return { first, used: [...used], reason: "" };
1919
+ })()`);
1920
+ const firstPx = contentPx(pages.first, ["first", "right", "recto"]);
1921
+ const wider = pages.used.find((name) => pseudoContexts.some((pseudos) => contentPx(name, pseudos) > firstPx + 0.01));
1922
+ unclipped = pages.reason || (wider !== undefined ? `${wider ? `the page "${wider}"` : "the unnamed page"} is wider than the first page` : await page.evaluate(`(() => {
1923
+ const hs = getComputedStyle(document.documentElement);
1924
+ const bs = getComputedStyle(document.body);
1925
+ let css;
1926
+ if (hs.overflowX === "visible" && hs.overflowY === "visible")
1927
+ css = "html { overflow-x: clip !important; } " +
1928
+ "body { overflow-x: clip !important; overflow-y: visible !important; }";
1929
+ else if (bs.overflowX === "visible") css = "body { overflow-x: clip !important; }";
1930
+ else return "the body clips its own overflow";
1931
+ window.__gp.addCss("gp-root-clip", css);
1932
+ return "";
1933
+ })()`)) || null;
1934
+ log(`body overflow ${unclipped ? `left as authored: ${unclipped}` : "held to the page content box"}`);
1935
+ }
1840
1936
  const rectoDecls = model.breaks.filter(isRectoVersoBreak);
1841
1937
  const needsMeasure = tier3Reasons.length > 0 || consumedStrings(model).size > 0 || rectoDecls.length > 0 || model.counterResets.length > 0 || furnitureRoots.length > 0 || sideRoots.length > 0;
1842
1938
  let tier = tier2.geometry.bleed > 0 || tier2.geometry.slug > 0 ? 2 : 1;
@@ -2358,6 +2454,20 @@ ${mapCss}`;
2358
2454
  author: opts.author
2359
2455
  });
2360
2456
  log(`postprocess: ${post.pageCount} pages` + (post.padded ? ` (+${post.padded} blank${opts.lastPage ? `; last page: ${opts.lastPage}` : ""})` : ""));
2457
+ {
2458
+ const scale = post.scale;
2459
+ if (scale !== null && scale < 1) {
2460
+ const pct = (scale * 100).toPrecision(6);
2461
+ const overPx = widthOffenders.defaultLimitPx * (1 / scale - 1);
2462
+ diagnose("engine.width.overflow", `This book printed at ${pct}% of its declared size: content reaches about ` + `${overPx < 0.1 ? overPx.toFixed(3) : overPx.toFixed(2)}px past the ` + `${Math.round(widthOffenders.defaultLimitPx)}px page content box, and Chromium shrinks ` + `the WHOLE print to fit it. The preview is not shrunk, so its lines and page ` + `breaks can differ from the PDF's.` + (unclipped ? ` Gutterpress could not hold it at the page content box: ${unclipped}.` : ""));
2463
+ if (!widthOffenders.boxes.length) {
2464
+ const named = await findWidthOffenders(page, model, 2 * (tier2.geometry.bleed + tier2.geometry.slug), sheetViewport, 0);
2465
+ for (const o of named.boxes)
2466
+ diagnose("engine.width.overflow", `${o.desc} reaches ${Math.max(o.px + Math.min(0, o.left) - (o.limit ?? named.limitPx), -o.left).toFixed(2)}px ` + `past the ${Math.round(o.limit ?? named.limitPx)}px page content box. Keep it ` + `inside the page content box.`);
2467
+ }
2468
+ log(`audit: printed at ${pct}% (shrink-to-fit)`);
2469
+ }
2470
+ }
2361
2471
  return {
2362
2472
  bytes: post.bytes,
2363
2473
  tier,
@@ -2392,7 +2502,7 @@ function shrinkScale(boxes, limitPx) {
2392
2502
  return null;
2393
2503
  return Math.max(1 / MAX_SHRINK, limitPx / maxRight);
2394
2504
  }
2395
- async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreViewport) {
2505
+ async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreViewport, tolerancePx = 1) {
2396
2506
  const contexts = [
2397
2507
  resolvePage(model),
2398
2508
  ...model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--pin-")).map((n) => resolvePage(model, { name: n }))
@@ -2418,7 +2528,8 @@ async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreView
2418
2528
  mobile: false
2419
2529
  });
2420
2530
  boxes.push(...JSON.parse(await page.evaluate(`(() => {
2421
- const LIMIT = ${widthPx} + 1;
2531
+ const TOL = ${tolerancePx};
2532
+ const LIMIT = Math.max(${widthPx} + TOL, ${Math.ceil(widthPx)});
2422
2533
  const NAMED = ${JSON.stringify(namedLimits)};
2423
2534
  const DEFAULT_LIMIT = ${defaultLimitPx};
2424
2535
  // The page an element lands on: the nearest ancestor with a non-auto
@@ -2458,10 +2569,10 @@ async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreView
2458
2569
  // pulls) AND left-edge protrusion (negative left with an ordinary
2459
2570
  // width) — both trigger the same whole-document shrink-to-fit,
2460
2571
  // measured: a left bleed alone cost 16%.
2461
- if (right <= LIMIT && r.width <= LIMIT && r.left >= -1) continue;
2572
+ if (right <= LIMIT && r.width <= LIMIT && r.left >= -TOL) continue;
2462
2573
  // ...but only where it actually escapes its clipping ancestors.
2463
2574
  const clip = clipEdges(el).edges;
2464
- if (Math.min(right, clip.right) <= LIMIT && Math.max(r.left, clip.left) >= -1) continue;
2575
+ if (Math.min(right, clip.right) <= LIMIT && Math.max(r.left, clip.left) >= -TOL) continue;
2465
2576
  let deepest = true;
2466
2577
  for (const c of el.children) {
2467
2578
  if (c.getBoundingClientRect().width >= r.width - 1) { deepest = false; break; }
@@ -2498,7 +2609,7 @@ async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreView
2498
2609
  const over = el.scrollWidth - el.clientWidth;
2499
2610
  // clientWidth is 0 on inline/replaced boxes, where scrollWidth
2500
2611
  // carries no overflow information -- skip rather than guess.
2501
- if (el.clientWidth <= 0 || over <= 1) continue;
2612
+ if (el.clientWidth <= 0 || over <= TOL) continue;
2502
2613
  // A descendant assigned to a DIFFERENT page is not overflowing this
2503
2614
  // element's page -- it is laid out against its own, wider one. The
2504
2615
  // full-bleed art plate is the standard case: it is legitimately
@@ -2552,7 +2663,7 @@ async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreView
2552
2663
  }
2553
2664
  return JSON.stringify(out);
2554
2665
  })()`));
2555
- return { limitPx, boxes, intrinsics: replaced };
2666
+ return { limitPx, defaultLimitPx, boxes, intrinsics: replaced };
2556
2667
  } finally {
2557
2668
  await page.send("Emulation.setDeviceMetricsOverride", restoreViewport);
2558
2669
  }
@@ -8,7 +8,7 @@ import {
8
8
  stampCreator,
9
9
  stripAnnotations,
10
10
  transparentPages
11
- } from "./cli-vhav8g82.js";
11
+ } from "./cli-0ep11b0q.js";
12
12
  import {
13
13
  blackTextToGray
14
14
  } from "./cli-ht1bcqdd.js";
@@ -17,7 +17,7 @@ import {
17
17
  MARGIN_BOX_IGNORED_PROPERTIES,
18
18
  getAssetPath,
19
19
  lastPageMode
20
- } from "./cli-k087gj0p.js";
20
+ } from "./cli-pd2cje5b.js";
21
21
  import {
22
22
  countLinkAnnotations,
23
23
  getImageResolutions,
@@ -42,7 +42,7 @@ import {
42
42
  import {
43
43
  hasUncommittedChanges,
44
44
  listWorkdirChanges
45
- } from "./cli-31anr0r9.js";
45
+ } from "./cli-v0evdpdf.js";
46
46
  import {
47
47
  detectProjectSource,
48
48
  repoSubPath
@@ -69,7 +69,7 @@ import {
69
69
  uniqueExtensionId,
70
70
  withFetchTimeout,
71
71
  writeManifestDoc
72
- } from "./cli-e31jshz6.js";
72
+ } from "./cli-88arpdke.js";
73
73
  import {
74
74
  EXTENSION_MANIFEST_FILENAME,
75
75
  GUTTERPRESS_CSS,
@@ -90,7 +90,7 @@ import {
90
90
  readExtensionMeta,
91
91
  resolveDeclaredStyles,
92
92
  slugify
93
- } from "./cli-ttj95aex.js";
93
+ } from "./cli-tgnyvra3.js";
94
94
  import {
95
95
  PACKAGE_META,
96
96
  PACKAGE_VERSION,
@@ -100,7 +100,7 @@ import {
100
100
  log,
101
101
  setLogLevel,
102
102
  warn
103
- } from "./cli-1fy1pm9d.js";
103
+ } from "./cli-9n9kg58f.js";
104
104
  import {
105
105
  DEBOUNCE,
106
106
  UsageError,
@@ -5103,7 +5103,7 @@ class PdfOutput {
5103
5103
  const rawPdf = pdfxMode ? path7.join(stage, "raw.pdf") : path7.resolve(pdfFile);
5104
5104
  await fsp2.mkdir(path7.dirname(path7.resolve(pdfFile)), { recursive: true });
5105
5105
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
5106
- const { buildNativePdf } = await import("./engine-jvkcahr1.js");
5106
+ const { buildNativePdf } = await import("./engine-c4pxz79g.js");
5107
5107
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
5108
5108
  title: config.title,
5109
5109
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -6571,7 +6571,7 @@ async function scaffoldProject(options) {
6571
6571
  let versionHistoryError;
6572
6572
  if (requested === "local-git") {
6573
6573
  try {
6574
- const { providerFor } = await import("./source-provider-n7vehh6t.js");
6574
+ const { providerFor } = await import("./source-provider-j1w7byeq.js");
6575
6575
  const provider = providerFor({ type: "local-folder", path: projectDir });
6576
6576
  await provider.initVersionHistory({
6577
6577
  projectDir,
@@ -8978,7 +8978,7 @@ async function resolvePublishProvider(id, book) {
8978
8978
  const ext = await bookExtensions(book);
8979
8979
  const npmEntry = ext.extensions.some((c) => !c.path && c.name === id);
8980
8980
  if (npmEntry) {
8981
- const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-as0g38p1.js");
8981
+ const { restoreForCommand: restoreForCommand2 } = await import("./extension-restore-pd87p8r2.js");
8982
8982
  await restoreForCommand2(book.projectDir, { manifestPath: book.manifestPath, failFast: true });
8983
8983
  }
8984
8984
  const publishers = await discover(ext);
@@ -9016,7 +9016,7 @@ function infoFor(p) {
9016
9016
  }
9017
9017
  function pluginProvider(p) {
9018
9018
  let loading;
9019
- const load = () => loading ??= import("./plugins-zsm6wv79.js").then((m) => m.loadPublishFunction(p));
9019
+ const load = () => loading ??= import("./plugins-e0n46s6m.js").then((m) => m.loadPublishFunction(p));
9020
9020
  return {
9021
9021
  info: infoFor(p),
9022
9022
  async authenticate() {
@@ -9404,6 +9404,7 @@ var defaultGitHttp = withIdleTimeout(httpNode);
9404
9404
 
9405
9405
  // src/lib/version-changes.ts
9406
9406
  var decoder = new TextDecoder;
9407
+ var CLOCK_SKEW_SECONDS = 10 * 60;
9407
9408
  // src/lib/host-policy.ts
9408
9409
  var AUTO_SYNC_MAX_MINUTES = 24 * 60;
9409
9410
  // src/lib/remote-auth/github-repos.ts
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PACKAGE_VERSION,
3
3
  debug
4
- } from "./cli-1fy1pm9d.js";
4
+ } from "./cli-9n9kg58f.js";
5
5
  import {
6
6
  isExactNpmVersion,
7
7
  isValidNpmPackageName,
@@ -1950,8 +1950,9 @@ img.gp-shape {
1950
1950
 
1951
1951
  /* Math (the bundled gutterpress-math feature, math.ts): MathJax's own rules
1952
1952
  for the <mjx-container> SVG output it emits, from its SVG output jax's
1953
- stylesheet, less the ones for its interactive menu and tooltips. Inert in
1954
- a book without formulas. */
1953
+ stylesheet, less the ones for its interactive menu and tooltips, plus one
1954
+ bound of ours on the hidden MathML copy (#469, below). Inert in a book
1955
+ without formulas. */
1955
1956
  mjx-container[jax="SVG"] { direction: ltr; }
1956
1957
  mjx-container[jax="SVG"] > svg { overflow: visible; min-height: 1px; min-width: 1px; }
1957
1958
  mjx-container[jax="SVG"] > svg a { fill: blue; stroke: blue; }
@@ -1968,6 +1969,15 @@ mjx-assistive-mml {
1968
1969
  border: 0px !important;
1969
1970
  display: block !important;
1970
1971
  width: auto !important;
1972
+ /* Ours (#469). The browser lays the copy out in whatever math font it has,
1973
+ so it can be wider than the formula's SVG; ending a line, it crossed the
1974
+ column edge and Chromium shrank the whole print to fit. MathJax makes the
1975
+ container position: relative with the copy at its top left, so 100% is
1976
+ the formula's width. Clip and overflow still hide the copy and its MathML
1977
+ still reaches screen readers; only its box changes. Not the 1px
1978
+ visually-hidden box MathJax 2 used: its docs note VoiceOver on iOS could
1979
+ not reach a formula by touch in it. */
1980
+ max-width: 100% !important;
1971
1981
  overflow: hidden !important;
1972
1982
  user-select: none;
1973
1983
  }
@@ -372,6 +372,40 @@ class LocalFolderSourceProvider {
372
372
  return Promise.reject(new Error(NO_VERSION_HISTORY));
373
373
  }
374
374
  }
375
+ var lastHistoryWalk = null;
376
+ async function startHistoryWalk(dir, cache, heads) {
377
+ const shallow = await gitFs.promises.readFile(path2.join(gitDirFor(dir), "shallow"), "utf8").catch(() => "");
378
+ const tips = await Promise.all(heads.map((oid) => git.readCommit({ fs: gitFs, dir, cache, oid })));
379
+ tips.sort((a, b) => a.commit.committer.timestamp - b.commit.committer.timestamp);
380
+ return {
381
+ tips,
382
+ queued: new Set(heads),
383
+ shallow: new Set(shallow.split(`
384
+ `)),
385
+ readAhead: new Map
386
+ };
387
+ }
388
+ async function nextInHistory(dir, walk, cache, needed) {
389
+ const commit = walk.tips.pop();
390
+ if (!commit || walk.shallow.has(commit.oid))
391
+ return commit;
392
+ for (const oid of commit.commit.parent) {
393
+ if (walk.queued.has(oid))
394
+ continue;
395
+ const oneLine = walk.tips.length === 0 && commit.commit.parent.length === 1;
396
+ if (needed > 1 && oneLine && !walk.readAhead.has(oid)) {
397
+ for (const c of await git.log({ fs: gitFs, dir, cache, ref: oid, depth: needed })) {
398
+ if (!walk.queued.has(c.oid))
399
+ walk.readAhead.set(c.oid, c);
400
+ }
401
+ }
402
+ walk.queued.add(oid);
403
+ walk.tips.push(walk.readAhead.get(oid) ?? await git.readCommit({ fs: gitFs, dir, cache, oid }));
404
+ walk.readAhead.delete(oid);
405
+ }
406
+ walk.tips.sort((a, b) => a.commit.committer.timestamp - b.commit.committer.timestamp);
407
+ return commit;
408
+ }
375
409
 
376
410
  class LocalGitSourceProvider {
377
411
  source;
@@ -409,40 +443,46 @@ class LocalGitSourceProvider {
409
443
  const dir = this.dir;
410
444
  const limit = Math.min(500, Math.max(1, options.limit ?? HISTORY_PAGE_LIMIT));
411
445
  const before = options.before;
412
- let commits;
446
+ const cache = {};
413
447
  try {
414
- commits = await git.log({
415
- fs: gitFs,
416
- dir,
417
- cache: {},
418
- depth: limit + 2,
419
- ...before ? { ref: before } : {}
420
- });
448
+ const head = await git.resolveRef({ fs: gitFs, dir, ref: "HEAD" });
449
+ let walk;
450
+ const saved = lastHistoryWalk;
451
+ if (before && saved?.dir === dir && saved.head === head && saved.last === before) {
452
+ walk = saved.walk;
453
+ lastHistoryWalk = null;
454
+ } else {
455
+ walk = await startHistoryWalk(dir, cache, [head]);
456
+ if (before) {
457
+ let c;
458
+ do {
459
+ c = await nextInHistory(dir, walk, cache, limit);
460
+ } while (c && c.oid !== before);
461
+ if (!c)
462
+ return { entries: [], hasMore: false };
463
+ }
464
+ }
465
+ const entries = [];
466
+ while (entries.length < limit) {
467
+ const c = await nextInHistory(dir, walk, cache, limit - entries.length);
468
+ if (!c)
469
+ break;
470
+ entries.push({
471
+ id: c.oid,
472
+ message: c.commit.message.trim(),
473
+ timestamp: c.commit.author.timestamp * 1000,
474
+ author: c.commit.author.name
475
+ });
476
+ }
477
+ const hasMore = walk.tips.length > 0;
478
+ lastHistoryWalk = hasMore ? { dir, head, last: entries.at(-1).id, walk } : null;
479
+ return { entries, hasMore };
421
480
  } catch (e) {
422
481
  if (before && e?.code === "NotFoundError") {
423
482
  return { entries: [], hasMore: false };
424
483
  }
425
484
  throw e;
426
485
  }
427
- const seen = new Set;
428
- let filtered = commits.filter((c) => {
429
- if (c.oid === before || seen.has(c.oid))
430
- return false;
431
- seen.add(c.oid);
432
- return true;
433
- });
434
- const hasMore = filtered.length > limit;
435
- if (hasMore)
436
- filtered = filtered.slice(0, limit);
437
- return {
438
- entries: filtered.map((c) => ({
439
- id: c.oid,
440
- message: c.commit.message.trim(),
441
- timestamp: c.commit.author.timestamp * 1000,
442
- author: c.commit.author.name
443
- })),
444
- hasMore
445
- };
446
486
  }
447
487
  restore(options) {
448
488
  return withRepoLock(this.dir, () => this.restoreUnlocked(options));
@@ -742,4 +782,4 @@ async function switchBranch(options) {
742
782
  });
743
783
  }
744
784
 
745
- export { gitScopeFor, HISTORY_PAGE_LIMIT, NO_VERSION_HISTORY, withRepoLock, whenGitIdle, __repoLockQueueSizeForTests, resolveGitAuthor, listWorkdirChanges, stageChanges, hasPendingChanges, hasUncommittedChanges, snapshotWorkingTreeUnlocked, isNoChangesError, countUnversionedChanges, providerFor, gitDirFor, snapshotStagingMarkerPath, RESTORE_BACKUP_MESSAGE, AUTO_SNAPSHOT_MESSAGE, restoreVersionWithBackup, SWITCH_BRANCH_SNAPSHOT_MESSAGE, readRevisionRefs, isContained, listRevisions, revisionNameFrom, assertFreeRevisionName, createRevision, switchBranch };
785
+ export { gitScopeFor, HISTORY_PAGE_LIMIT, NO_VERSION_HISTORY, withRepoLock, whenGitIdle, __repoLockQueueSizeForTests, resolveGitAuthor, listWorkdirChanges, stageChanges, hasPendingChanges, hasUncommittedChanges, startHistoryWalk, nextInHistory, snapshotWorkingTreeUnlocked, isNoChangesError, countUnversionedChanges, providerFor, gitDirFor, snapshotStagingMarkerPath, RESTORE_BACKUP_MESSAGE, AUTO_SNAPSHOT_MESSAGE, restoreVersionWithBackup, SWITCH_BRANCH_SNAPSHOT_MESSAGE, readRevisionRefs, isContained, listRevisions, revisionNameFrom, assertFreeRevisionName, createRevision, switchBranch };
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var package_default = {
3
3
  name: "gutterpress",
4
- version: "0.11.23-beta.1",
4
+ version: "0.11.23-beta.2",
5
5
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
6
6
  author: "itlackey",
7
7
  license: "MPL-2.0",