gutterpress 0.11.20-alpha.2 → 0.11.20-alpha.4

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 (43) hide show
  1. package/dist/api/index.js +3 -2
  2. package/dist/{build-qmjg9r2d.js → build-epdcrm2c.js} +5 -4
  3. package/dist/{cli-zacevwcy.js → cli-0ey902sx.js} +267 -455
  4. package/dist/{cli-j802s31j.js → cli-61vd4ews.js} +83 -41
  5. package/dist/{cli-7z29z4wz.js → cli-fmksazej.js} +1 -1
  6. package/dist/{cli-gh0ym80r.js → cli-h4tfmpg3.js} +121 -20
  7. package/dist/cli-sth219v1.js +418 -0
  8. package/dist/cli.js +12 -12
  9. package/dist/{doctor-fe7tcs4v.js → doctor-78es5scm.js} +2 -2
  10. package/dist/engine/compiler/build.d.ts +1 -1
  11. package/dist/engine/shared/margin-box-support.d.ts +2 -2
  12. package/dist/engine/shared/pin-edges.d.ts +127 -0
  13. package/dist/engine/shared/print-area.d.ts +68 -0
  14. package/dist/{engine-9xb1ky8d.js → engine-67f68bxs.js} +1 -1
  15. package/dist/{engine-yax79k9m.js → engine-7ha27n2b.js} +2 -2
  16. package/dist/{ext-ahwxt6gv.js → ext-9mc7sr0d.js} +5 -4
  17. package/dist/{gutterpress-agent-cazqstr1.js → gutterpress-agent-mrdnwktc.js} +78 -13
  18. package/dist/{gutterpress-viewer-retj6ym2.js → gutterpress-viewer-jr30yw8t.js} +155 -56
  19. package/dist/{index-6xwtb7cw.js → index-ad95fws8.js} +82 -40
  20. package/dist/{index-3nr5rzzp.js → index-gm5xbx5w.js} +3 -3
  21. package/dist/index-sth219v1.js +418 -0
  22. package/dist/{index-razpty47.js → index-t13p8y08.js} +493 -582
  23. package/dist/index.js +4 -3
  24. package/dist/lib/build-fingerprint.d.ts +8 -0
  25. package/dist/lib/extension-manifest.d.ts +13 -0
  26. package/dist/lib/ghostscript.d.ts +70 -16
  27. package/dist/lib/markdown/assemble.d.ts +12 -2
  28. package/dist/lib/markdown/gutterpress-css.d.ts +46 -30
  29. package/dist/lib/markdown/index.d.ts +5 -0
  30. package/dist/lib/pdf-inspect.d.ts +45 -0
  31. package/dist/{manifest.schema-74s1ykyv.json → manifest.schema-13dfax80.json} +16 -4
  32. package/dist/{new-ftd4fec4.js → new-s4f0tnya.js} +5 -4
  33. package/dist/pdf-inspect-zdmy7qca.js +35 -0
  34. package/dist/{preflight-8sqarcsr.js → preflight-7ck4rbsq.js} +5 -4
  35. package/dist/preview/file-watcher.d.ts +4 -10
  36. package/dist/{preview-q76xn95h.js → preview-j91sxadd.js} +5 -4
  37. package/dist/{publish-7gdy6gbf.js → publish-f12xaenm.js} +5 -4
  38. package/dist/render.js +57 -6
  39. package/dist/schema/manifest.types.d.ts +28 -8
  40. package/dist/test-helpers/testkit.d.ts +2 -2
  41. package/dist/{validate-e3ryegce.js → validate-pb5gysvr.js} +5 -4
  42. package/package.json +1 -1
  43. package/dist/engine/shared/flush.d.ts +0 -59
@@ -19,7 +19,7 @@ import {
19
19
  stringValueAt,
20
20
  toFolioPage,
21
21
  wantsRecto
22
- } from "./index-3nr5rzzp.js";
22
+ } from "./index-gm5xbx5w.js";
23
23
  import {
24
24
  launchChromium
25
25
  } from "./index-bt0qws5c.js";
@@ -29,14 +29,15 @@ import { readFile } from "node:fs/promises";
29
29
  import { resolve } from "node:path";
30
30
  import { pathToFileURL } from "node:url";
31
31
 
32
- // src/engine/shared/flush.ts
33
- var FLUSH_EDGES = ["top", "right", "bottom", "left"];
34
- function flushKey(edges) {
35
- return FLUSH_EDGES.filter((e) => edges.includes(e)).map((e) => e[0]).join("");
32
+ // src/engine/shared/pin-edges.ts
33
+ var PIN_EDGES = ["top", "right", "bottom", "left"];
34
+ var EDGE_PIN_SELECTOR = PIN_EDGES.map((edge) => `.gp-pin.gp-${edge}`).join(", ");
35
+ function pinKey(edges) {
36
+ return PIN_EDGES.filter((e) => edges.includes(e)).map((e) => e[0]).join("");
36
37
  }
37
- function flushPageName(authorPage, edges) {
38
+ function pinPageName(authorPage, edges) {
38
39
  const safe = authorPage ? authorPage.replace(/[^A-Za-z0-9_-]/g, "_") : "";
39
- return `gp--flush${safe ? `-${safe}` : ""}-${flushKey(edges)}`;
40
+ return `gp--pin${safe ? `-${safe}` : ""}-${pinKey(edges)}`;
40
41
  }
41
42
  function flushMargins(margin, edges) {
42
43
  return {
@@ -46,6 +47,23 @@ function flushMargins(margin, edges) {
46
47
  left: edges.includes("left") ? 0 : margin.left
47
48
  };
48
49
  }
50
+ function pinInsets(area, side, edges) {
51
+ const binding = side === "recto" ? "left" : "right";
52
+ const inset = (edge) => !area || !edges.includes(edge) ? 0 : edge === binding ? area.safe : area.bleed + area.artSafe;
53
+ return { top: inset("top"), right: inset("right"), bottom: inset("bottom"), left: inset("left") };
54
+ }
55
+ var NO_PADDING = { top: 0, right: 0, bottom: 0, left: 0 };
56
+ function pinRootStyle(margin, area, side, edges, padding = NO_PADDING) {
57
+ const px = (pt) => pt * 96 / 72;
58
+ const inset = pinInsets(area, side, edges);
59
+ const style = { "box-decoration-break": "clone" };
60
+ for (const edge of PIN_EDGES)
61
+ if (edges.includes(edge))
62
+ style[`padding-${edge}`] = `${px(margin[edge]) + padding[edge]}px`;
63
+ for (const edge of PIN_EDGES)
64
+ style[`--gp-pin-${edge}`] = `${px(inset[edge])}px`;
65
+ return style;
66
+ }
49
67
  function marginBoxesOnEdges(edges) {
50
68
  const owners = {
51
69
  "top-left-corner": ["top", "left"],
@@ -723,7 +741,7 @@ var BUILD_DIAGNOSTIC_CODES = [
723
741
  "engine.multicol.dead-column",
724
742
  "engine.content.overheight",
725
743
  "engine.image.low-dpi",
726
- "engine.flush.margin-box",
744
+ "engine.pin.margin-box",
727
745
  "engine.page-background.unreferenced",
728
746
  "engine.hyphenation.unsupported"
729
747
  ];
@@ -756,25 +774,31 @@ async function build(opts) {
756
774
  await page.send("Emulation.setDeviceMetricsOverride", sheetViewport);
757
775
  await page.send("Emulation.setEmulatedMedia", { media: "print" });
758
776
  log(`print media emulated for audits and measurement`);
759
- const flushRoots = await page.evaluate(`window.__gp.flushRoots()`);
760
- const flushGroups = new Map;
761
- for (const root of flushRoots) {
762
- if (flushGroups.has(root.key))
777
+ const pinRoots = await page.evaluate(`window.__gp.pinRoots()`);
778
+ const pinGroups = new Map;
779
+ for (const root of pinRoots) {
780
+ if (pinGroups.has(root.key))
763
781
  continue;
764
782
  const authorPage = root.page === "auto" ? undefined : root.page;
765
- flushGroups.set(root.key, {
783
+ pinGroups.set(root.key, {
766
784
  authorPage,
767
785
  edges: root.edges,
768
- genName: flushPageName(authorPage, root.edges),
786
+ padding: root.padding,
787
+ genName: pinPageName(authorPage, root.edges),
769
788
  key: root.key
770
789
  });
771
790
  }
772
- if (flushGroups.size) {
791
+ const printArea = pinRoots.length ? await page.evaluate(`window.__gp.printArea()`) : null;
792
+ const pinStyle = (group, pseudos) => pinRootStyle(resolvePage(model, { name: group.authorPage, pseudos }).geometry.margin, printArea, pseudos.includes("left") ? "verso" : "recto", group.edges, group.padding);
793
+ if (pinGroups.size) {
773
794
  const lines = [];
774
- for (const group of flushGroups.values()) {
795
+ const aliased = new Set;
796
+ for (const group of pinGroups.values()) {
797
+ const alias = !aliased.has(group.genName);
798
+ aliased.add(group.genName);
775
799
  const relocated = new Set(marginBoxesOnEdges(group.edges));
776
800
  const pseudoSets = [[]];
777
- if (group.authorPage) {
801
+ if (alias && group.authorPage) {
778
802
  for (const rule of model.pageRules) {
779
803
  if (rule.name !== group.authorPage)
780
804
  continue;
@@ -798,7 +822,7 @@ ${body.join(`
798
822
  }`);
799
823
  }
800
824
  }
801
- for (const pseudos of pseudoSets) {
825
+ for (const pseudos of alias ? pseudoSets : []) {
802
826
  const pseudo = pseudos.length ? `:${pseudos.join(":")}` : "";
803
827
  const body = [];
804
828
  for (const edge of group.edges)
@@ -810,14 +834,15 @@ ${body.join(`
810
834
  `)}
811
835
  }`);
812
836
  }
813
- lines.push(`:where(.page, .spread)[data-gp-flush="${group.key}"][data-gp-flush][data-gp-flush][data-gp-flush] { page: ${group.genName}; }`);
837
+ const recto = Object.entries(pinStyle(group, ["right"])).map(([prop, value]) => ` ${prop}: ${value};`).join("");
838
+ lines.push(`:where(.page, .spread)[data-gp-pin="${group.key}"][data-gp-pin][data-gp-pin][data-gp-pin] { page: ${group.genName};${recto} }`);
814
839
  }
815
- const flushCss = lines.join(`
840
+ const pinCss = lines.join(`
816
841
  `);
817
- await page.evaluate(`window.__gp.addCss("gp-flush-css", ${JSON.stringify(flushCss)})`);
842
+ await page.evaluate(`window.__gp.addCss("gp-pin-css", ${JSON.stringify(pinCss)})`);
818
843
  model = extract(`${cssText}
819
- ${flushCss}`);
820
- log(`.gp-flush: ${flushRoots.length} pinned root(s) -> ${flushGroups.size} generated page context(s)`);
844
+ ${pinCss}`);
845
+ log(`pinned art: ${pinRoots.length} page root(s) -> ${pinGroups.size} generated page context(s)`);
821
846
  }
822
847
  const { tier3Reasons } = classify(model);
823
848
  const pseudoContexts = pagePseudoContexts(model);
@@ -842,11 +867,16 @@ ${flushCss}`);
842
867
  return !!c && c !== "none" && c !== "normal";
843
868
  });
844
869
  };
845
- const furnitureRoots = flushRoots.filter((root) => {
846
- const group = flushGroups.get(root.key);
870
+ const furnitureRoots = pinRoots.filter((root) => {
871
+ const group = pinGroups.get(root.key);
847
872
  return pseudoContexts.some((pseudos) => relocationBoxes(group, pseudos).length > 0);
848
873
  });
849
- const flushDiagnosed = new Set;
874
+ const relocationDiagnosed = new Set;
875
+ const sideRoots = pinRoots.filter((root) => {
876
+ const group = pinGroups.get(root.key);
877
+ const recto = JSON.stringify(pinStyle(group, ["right"]));
878
+ return [["left"], ["right", "first"]].some((pseudos) => JSON.stringify(pinStyle(group, pseudos)) !== recto);
879
+ });
850
880
  const tier2 = synthesize({
851
881
  model,
852
882
  marks: opts.marks,
@@ -890,7 +920,7 @@ ${describe(widthOffenders.intrinsics)}`;
890
920
  log(`WARNING: ${msg}`);
891
921
  }
892
922
  const rectoDecls = model.breaks.filter(isRectoVersoBreak);
893
- const needsMeasure = tier3Reasons.length > 0 || consumedStrings(model).size > 0 || rectoDecls.length > 0 || model.counterResets.length > 0 || furnitureRoots.length > 0;
923
+ const needsMeasure = tier3Reasons.length > 0 || consumedStrings(model).size > 0 || rectoDecls.length > 0 || model.counterResets.length > 0 || furnitureRoots.length > 0 || sideRoots.length > 0;
894
924
  let tier = tier2.geometry.bleed > 0 || tier2.geometry.slug > 0 ? 2 : 1;
895
925
  let passes = 1;
896
926
  let pageMap = {};
@@ -918,7 +948,7 @@ ${describe(widthOffenders.intrinsics)}`;
918
948
  targets.add(s.id);
919
949
  for (const s of resetSites)
920
950
  targets.add(s.id);
921
- for (const r of furnitureRoots)
951
+ for (const r of [...furnitureRoots, ...sideRoots])
922
952
  targets.add(r.id);
923
953
  await page.evaluate(`window.__gp.instrument(${JSON.stringify([...targets])})`);
924
954
  const targetText = await page.evaluate(`window.__gp.targetTexts(${JSON.stringify([...targets])})`);
@@ -1004,8 +1034,12 @@ ${describe(widthOffenders.intrinsics)}`;
1004
1034
  /* Tier 3 */
1005
1035
  ${mapCss}`;
1006
1036
  }
1037
+ const pageAt = (id) => map[id] ?? predictedForResult?.pageMap[id];
1038
+ const pseudosAt = (physical) => [
1039
+ physical % 2 === 1 ? "right" : "left",
1040
+ ...physical === 1 ? ["first"] : []
1041
+ ];
1007
1042
  if (furnitureRoots.length) {
1008
- const pageAt = (id) => map[id] ?? predictedForResult?.pageMap[id];
1009
1043
  const byName = new Map;
1010
1044
  for (const s of sources) {
1011
1045
  const p = pageAt(s.id);
@@ -1023,10 +1057,8 @@ ${mapCss}`;
1023
1057
  const physical = pageAt(root.id);
1024
1058
  if (!physical)
1025
1059
  continue;
1026
- const group = flushGroups.get(root.key);
1027
- const pseudos = [physical % 2 === 1 ? "right" : "left"];
1028
- if (physical === 1)
1029
- pseudos.push("first");
1060
+ const group = pinGroups.get(root.key);
1061
+ const pseudos = pseudosAt(physical);
1030
1062
  const ctx = resolvePage(model, { name: group.authorPage, pseudos });
1031
1063
  const g = ctx.geometry;
1032
1064
  const eff = flushMargins(g.margin, group.edges);
@@ -1042,9 +1074,9 @@ ${mapCss}`;
1042
1074
  }
1043
1075
  });
1044
1076
  if (!text) {
1045
- if (!flushDiagnosed.has(name)) {
1046
- flushDiagnosed.add(name);
1047
- diagnose("engine.flush.margin-box", `@${name} sits on an edge a .gp-flush pin frees, and its content could not be ` + `re-homed into the page (unsupported content value: ${decls.content}). It will ` + `not print on that page — simplify the box's content, or drop .gp-flush there.`);
1077
+ if (!relocationDiagnosed.has(name)) {
1078
+ relocationDiagnosed.add(name);
1079
+ diagnose("engine.pin.margin-box", `@${name} sits on an edge a pinned image frees, and its content could not be ` + `re-homed into the page (unsupported content value: ${decls.content}). It will ` + `not print on that page — simplify the box's content, or pin the image to another edge.`);
1048
1080
  }
1049
1081
  continue;
1050
1082
  }
@@ -1069,7 +1101,17 @@ ${mapCss}`;
1069
1101
  items.push({ id: root.id, boxes });
1070
1102
  }
1071
1103
  if (items.length)
1072
- await page.evaluate(`window.__gp.setFlushFurniture(${JSON.stringify(items)})`);
1104
+ await page.evaluate(`window.__gp.setPinFurniture(${JSON.stringify(items)})`);
1105
+ }
1106
+ if (sideRoots.length) {
1107
+ const items = sideRoots.flatMap((root) => {
1108
+ const physical = pageAt(root.id);
1109
+ if (!physical)
1110
+ return [];
1111
+ return [{ id: root.id, style: pinStyle(pinGroups.get(root.key), pseudosAt(physical)) }];
1112
+ });
1113
+ if (items.length)
1114
+ await page.evaluate(`window.__gp.setPinGeometry(${JSON.stringify(items)})`);
1073
1115
  }
1074
1116
  };
1075
1117
  const predicted = await predictPageMap(browser, url, AGENT, VIEWER, {
@@ -1432,12 +1474,12 @@ function shrinkScale(boxes, limitPx) {
1432
1474
  async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreViewport) {
1433
1475
  const contexts = [
1434
1476
  resolvePage(model),
1435
- ...model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--flush-")).map((n) => resolvePage(model, { name: n }))
1477
+ ...model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--pin-")).map((n) => resolvePage(model, { name: n }))
1436
1478
  ];
1437
1479
  const contentPxOf = (c) => (c.geometry.width - c.geometry.margin.left - c.geometry.margin.right + bleedSlugExtensionPt) * 96 / 72;
1438
1480
  const defaultLimitPx = contentPxOf(contexts[0]);
1439
1481
  const namedLimits = {};
1440
- model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--flush-")).forEach((n) => {
1482
+ model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--pin-")).forEach((n) => {
1441
1483
  namedLimits[n] = contentPxOf(resolvePage(model, { name: n }));
1442
1484
  });
1443
1485
  const widths = [...new Set([defaultLimitPx, ...Object.values(namedLimits)])].sort((a, b) => a - b);
@@ -1624,7 +1666,7 @@ async function predictPageMap(browser, url, agentScript, viewerScript, args, she
1624
1666
  await page.waitForReady();
1625
1667
  await page.evaluate(`window.__GP_MANUAL__ = true;`);
1626
1668
  await page.evaluate(viewerScript);
1627
- await page.evaluate(`window.__gp.flushRoots()`);
1669
+ await page.evaluate(`window.__gp.pinRoots()`);
1628
1670
  await page.evaluate(`window.__gp.stringSources(${JSON.stringify(args.stringSets)})`);
1629
1671
  if (args.rectoDecls.length)
1630
1672
  await page.evaluate(`window.__gp.forcedBreakSites(${JSON.stringify(args.rectoDecls)})`);
@@ -148,7 +148,7 @@ import { fileURLToPath } from "node:url";
148
148
  var favicon_default = "./favicon-272ga6y0.ico";
149
149
 
150
150
  // src/assets/manifest.schema.json
151
- var manifest_schema_default = "./manifest.schema-74s1ykyv.json";
151
+ var manifest_schema_default = "./manifest.schema-13dfax80.json";
152
152
 
153
153
  // src/assets/preview/scripts/preview-interface.js
154
154
  var preview_interface_default = "./preview-interface-nnmrxyqm.js";
@@ -163,10 +163,10 @@ var preview_shell_default = "./preview-shell-wk9k3gcd.js";
163
163
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
164
164
 
165
165
  // src/assets/engine/gutterpress-viewer.js
166
- var gutterpress_viewer_default = "./gutterpress-viewer-retj6ym2.js";
166
+ var gutterpress_viewer_default = "./gutterpress-viewer-jr30yw8t.js";
167
167
 
168
168
  // src/assets/engine/gutterpress-agent.js
169
- var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
169
+ var gutterpress_agent_default = "./gutterpress-agent-mrdnwktc.js";
170
170
 
171
171
  // src/assets/templates/book/manifest.yaml
172
172
  var manifest_default = "./manifest-n1gh3qw5.yaml";
@@ -0,0 +1,418 @@
1
+ // src/lib/pdf-inspect.ts
2
+ import { stat, readFile } from "node:fs/promises";
3
+ import { getDocumentProxy } from "unpdf";
4
+ import * as pdfjs from "unpdf/pdfjs";
5
+ function getOps(mod) {
6
+ if ("OPS" in mod && mod.OPS && typeof mod.OPS === "object") {
7
+ return mod.OPS;
8
+ }
9
+ throw new Error("unpdf/pdfjs missing OPS export");
10
+ }
11
+ var OPS = getOps(pdfjs);
12
+ var docCache = new Map;
13
+ var DOC_CACHE_MAX = 8;
14
+ function destroyEntry(entry) {
15
+ entry.doc.then((d) => d.destroy()).catch(() => {});
16
+ }
17
+ var EVICT_DESTROY_GRACE_MS = 60000;
18
+ var gracePending = new Map;
19
+ function destroyEntryAfterGrace(entry) {
20
+ const t = setTimeout(() => {
21
+ gracePending.delete(t);
22
+ destroyEntry(entry);
23
+ }, EVICT_DESTROY_GRACE_MS);
24
+ t.unref?.();
25
+ gracePending.set(t, entry);
26
+ }
27
+ function evictLru() {
28
+ while (docCache.size > DOC_CACHE_MAX) {
29
+ const oldestKey = docCache.keys().next().value;
30
+ if (oldestKey === undefined)
31
+ break;
32
+ const evicted = docCache.get(oldestKey);
33
+ docCache.delete(oldestKey);
34
+ if (evicted)
35
+ destroyEntryAfterGrace(evicted);
36
+ }
37
+ }
38
+ async function loadPdf(path) {
39
+ let st;
40
+ try {
41
+ st = await stat(path);
42
+ } catch {
43
+ return null;
44
+ }
45
+ const hit = docCache.get(path);
46
+ if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
47
+ docCache.delete(path);
48
+ docCache.set(path, hit);
49
+ try {
50
+ return await hit.doc;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ if (hit)
56
+ destroyEntry(hit);
57
+ const docPromise = readFile(path).then((buf) => getDocumentProxy(new Uint8Array(buf)));
58
+ docCache.delete(path);
59
+ docCache.set(path, { mtimeMs: st.mtimeMs, size: st.size, doc: docPromise });
60
+ evictLru();
61
+ try {
62
+ return await docPromise;
63
+ } catch {
64
+ if (docCache.get(path)?.doc === docPromise)
65
+ docCache.delete(path);
66
+ return null;
67
+ }
68
+ }
69
+ var activeRetains = 0;
70
+ function retainPdfCache() {
71
+ activeRetains++;
72
+ let released = false;
73
+ return () => {
74
+ if (released)
75
+ return;
76
+ released = true;
77
+ activeRetains--;
78
+ if (activeRetains === 0)
79
+ clearPdfCache();
80
+ };
81
+ }
82
+ function clearPdfCache() {
83
+ for (const [t, entry] of gracePending) {
84
+ clearTimeout(t);
85
+ destroyEntry(entry);
86
+ }
87
+ gracePending.clear();
88
+ for (const entry of docCache.values())
89
+ destroyEntry(entry);
90
+ docCache.clear();
91
+ }
92
+ function pendingGraceDestroyCount() {
93
+ return gracePending.size;
94
+ }
95
+ function getPageSize(page) {
96
+ const vp = page.getViewport({ scale: 1 });
97
+ return { w: vp.width, h: vp.height };
98
+ }
99
+ async function getOutlineCount(doc) {
100
+ try {
101
+ const outline = await doc.getOutline();
102
+ return outline ? outline.length : 0;
103
+ } catch {
104
+ return 0;
105
+ }
106
+ }
107
+ async function getPageLabels(doc) {
108
+ try {
109
+ return await doc.getPageLabels();
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+ async function countLinkAnnotations(doc, stopAtFirst = false) {
115
+ let count = 0;
116
+ for (let i = 1;i <= doc.numPages; i++) {
117
+ try {
118
+ const page = await doc.getPage(i);
119
+ const annots = await page.getAnnotations();
120
+ for (const a of annots) {
121
+ if (a.subtype === "Link") {
122
+ count++;
123
+ if (stopAtFirst)
124
+ return count;
125
+ }
126
+ }
127
+ } catch {}
128
+ }
129
+ return count;
130
+ }
131
+ var textCache = new WeakMap;
132
+ function getTextPass(doc) {
133
+ let p = textCache.get(doc);
134
+ if (!p) {
135
+ p = (async () => {
136
+ const textByPage = [];
137
+ const positions = [];
138
+ const runsByPage = [];
139
+ for (let i = 1;i <= doc.numPages; i++) {
140
+ try {
141
+ const page = await doc.getPage(i);
142
+ const tc = await page.getTextContent();
143
+ let pageText = "";
144
+ const runs = [];
145
+ for (const item of tc.items) {
146
+ const it = item;
147
+ if (typeof it.str !== "string")
148
+ continue;
149
+ pageText += it.str;
150
+ if (it.transform && it.str.trim().length > 0) {
151
+ const x = it.transform[4];
152
+ const y = it.transform[5];
153
+ positions.push({ x, y });
154
+ runs.push({ s: it.str, x, y, w: it.width ?? 0, h: it.height ?? 0 });
155
+ }
156
+ }
157
+ textByPage.push(pageText);
158
+ runsByPage.push(runs);
159
+ } catch {
160
+ textByPage.push("");
161
+ runsByPage.push([]);
162
+ }
163
+ }
164
+ return { textByPage, positions, runsByPage };
165
+ })();
166
+ textCache.set(doc, p);
167
+ }
168
+ return p;
169
+ }
170
+ var opCache = new WeakMap;
171
+ function multiply(m1, m2) {
172
+ return [
173
+ m1[0] * m2[0] + m1[2] * m2[1],
174
+ m1[1] * m2[0] + m1[3] * m2[1],
175
+ m1[0] * m2[2] + m1[2] * m2[3],
176
+ m1[1] * m2[2] + m1[3] * m2[3],
177
+ m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
178
+ m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
179
+ ];
180
+ }
181
+ function fontIsEmbedded(f) {
182
+ if (f.isType3Font)
183
+ return true;
184
+ if (f.missingFile === true)
185
+ return false;
186
+ if (f.isStandardFont === true)
187
+ return false;
188
+ return true;
189
+ }
190
+ function getOpPass(doc) {
191
+ let p = opCache.get(doc);
192
+ if (!p) {
193
+ p = (async () => {
194
+ const imagesByPage = new Map;
195
+ const fonts = [];
196
+ const seenFonts = new Set;
197
+ for (let i = 1;i <= doc.numPages; i++) {
198
+ let page;
199
+ try {
200
+ page = await doc.getPage(i);
201
+ } catch {
202
+ continue;
203
+ }
204
+ let ops;
205
+ try {
206
+ ops = await page.getOperatorList();
207
+ } catch {
208
+ continue;
209
+ }
210
+ let ctm = [1, 0, 0, 1, 0, 0];
211
+ const stack = [];
212
+ const pageImages = [];
213
+ for (let j = 0;j < ops.fnArray.length; j++) {
214
+ const fn = ops.fnArray[j];
215
+ const args = ops.argsArray[j];
216
+ if (fn === OPS.save) {
217
+ stack.push(ctm.slice());
218
+ } else if (fn === OPS.restore) {
219
+ ctm = stack.pop() ?? ctm;
220
+ } else if (fn === OPS.transform) {
221
+ ctm = multiply(ctm, args);
222
+ } else if (fn === OPS.paintImageXObject || fn === OPS.paintImageXObjectRepeat || fn === OPS.paintInlineImageXObject) {
223
+ const name = typeof args[0] === "string" ? args[0] : "(inline)";
224
+ pageImages.push({
225
+ name,
226
+ placedW: Math.hypot(ctm[0], ctm[1]),
227
+ placedH: Math.hypot(ctm[2], ctm[3]),
228
+ x: ctm[4],
229
+ y: ctm[5],
230
+ page: i
231
+ });
232
+ } else if (fn === OPS.setFont) {
233
+ const ref = args[0];
234
+ if (typeof ref === "string" && !seenFonts.has(ref)) {
235
+ seenFonts.add(ref);
236
+ try {
237
+ if (page.commonObjs.has(ref)) {
238
+ const f = page.commonObjs.get(ref);
239
+ fonts.push({
240
+ name: f?.name ?? ref,
241
+ embedded: fontIsEmbedded(f ?? {})
242
+ });
243
+ }
244
+ } catch {}
245
+ }
246
+ }
247
+ }
248
+ if (pageImages.length > 0)
249
+ imagesByPage.set(i, pageImages);
250
+ }
251
+ return { imagesByPage, fonts };
252
+ })();
253
+ opCache.set(doc, p);
254
+ }
255
+ return p;
256
+ }
257
+ var opSet = (names) => new Set(names.map((name) => OPS[name]).filter((op) => op !== undefined));
258
+ var FILL_COLOR_OPS = opSet(["setFillGray", "setFillRGBColor", "setFillCMYKColor", "setFillColor"]);
259
+ var STROKE_COLOR_OPS = opSet(["setStrokeGray", "setStrokeRGBColor", "setStrokeCMYKColor", "setStrokeColor"]);
260
+ var SHOW_TEXT_OPS = opSet(["showText", "showSpacedText", "nextLineShowText", "nextLineSetSpacingShowText"]);
261
+ async function unsplittableTextReasons(pdfBytes, pages) {
262
+ const GSTATE_BIT = { ca: 1, CA: 2, BM: 4, SMask: 8 };
263
+ const COMPOSITING = 15;
264
+ const FILL_PATTERN = 16;
265
+ const STROKE_PATTERN = 32;
266
+ const CLIP_TEXT = 64;
267
+ const doc = await getDocumentProxy(new Uint8Array(pdfBytes));
268
+ const reasons = new Map;
269
+ try {
270
+ const scan = pages ?? Array.from({ length: doc.numPages }, (_, i) => i + 1);
271
+ for (const i of scan) {
272
+ const page = await doc.getPage(i);
273
+ const ops = await page.getOperatorList();
274
+ let state = 0;
275
+ const groups = [];
276
+ const stack = [];
277
+ let showsText = false;
278
+ for (let j = 0;j < ops.fnArray.length; j++) {
279
+ const fn = ops.fnArray[j];
280
+ if (fn === OPS.save || fn === OPS.paintFormXObjectBegin)
281
+ stack.push(state);
282
+ else if (fn === OPS.restore || fn === OPS.paintFormXObjectEnd)
283
+ state = stack.pop() ?? 0;
284
+ else if (fn === OPS.beginGroup) {
285
+ const group = ops.argsArray[j][0];
286
+ groups.push((state & COMPOSITING) !== 0 || !!(group.smask || group.knockout));
287
+ } else if (fn === OPS.endGroup)
288
+ groups.pop();
289
+ else if (fn === OPS.setFont) {
290
+ const ref = ops.argsArray[j][0];
291
+ const font = typeof ref === "string" && page.commonObjs.has(ref) ? page.commonObjs.get(ref) : undefined;
292
+ if (font?.isType3Font) {
293
+ reasons.set(i, "type3-font");
294
+ break;
295
+ }
296
+ } else if (fn === OPS.setFillColorN || fn === OPS.setStrokeColorN) {
297
+ const bit = fn === OPS.setFillColorN ? FILL_PATTERN : STROKE_PATTERN;
298
+ const kind = ops.argsArray[j][0];
299
+ state = kind === "TilingPattern" || kind === "Shading" ? state | bit : state & ~bit;
300
+ } else if (FILL_COLOR_OPS.has(fn))
301
+ state &= ~FILL_PATTERN;
302
+ else if (STROKE_COLOR_OPS.has(fn))
303
+ state &= ~STROKE_PATTERN;
304
+ else if (fn === OPS.setTextRenderingMode) {
305
+ state = ops.argsArray[j][0] >= 4 ? state | CLIP_TEXT : state & ~CLIP_TEXT;
306
+ } else if (fn === OPS.setGState) {
307
+ for (const [key, value] of ops.argsArray[j][0]) {
308
+ const bit = GSTATE_BIT[key];
309
+ if (!bit)
310
+ continue;
311
+ const on = key === "BM" ? value !== "source-over" : key === "SMask" ? !!value : value < 1;
312
+ state = on ? state | bit : state & ~bit;
313
+ }
314
+ } else if (SHOW_TEXT_OPS.has(fn)) {
315
+ showsText = true;
316
+ if (state || groups.includes(true)) {
317
+ const paint = state & (FILL_PATTERN | STROKE_PATTERN | CLIP_TEXT);
318
+ reasons.set(i, paint ? "text-pattern-or-clip" : "text-transparency");
319
+ break;
320
+ }
321
+ }
322
+ }
323
+ if (!showsText && !reasons.has(i))
324
+ reasons.set(i, "no-text");
325
+ }
326
+ } finally {
327
+ await doc.destroy();
328
+ }
329
+ return reasons;
330
+ }
331
+ function getImagePixelSize(page, name, timeoutMs = 2000) {
332
+ return new Promise((resolve) => {
333
+ let done = false;
334
+ const finish = (v) => {
335
+ if (!done) {
336
+ done = true;
337
+ resolve(v);
338
+ }
339
+ };
340
+ const t = setTimeout(() => finish(null), timeoutMs);
341
+ try {
342
+ page.objs.get(name, (obj) => {
343
+ clearTimeout(t);
344
+ finish(obj && obj.width && obj.height ? { width: obj.width, height: obj.height } : null);
345
+ });
346
+ } catch {
347
+ clearTimeout(t);
348
+ finish(null);
349
+ }
350
+ });
351
+ }
352
+ var PAINT_OPS = new Set([
353
+ "stroke",
354
+ "closeStroke",
355
+ "fill",
356
+ "eoFill",
357
+ "fillStroke",
358
+ "eoFillStroke",
359
+ "closeFillStroke",
360
+ "closeEOFillStroke",
361
+ "shadingFill",
362
+ "paintImageXObject",
363
+ "paintInlineImageXObject",
364
+ "paintImageXObjectRepeat",
365
+ "paintImageMaskXObject",
366
+ "paintImageMaskXObjectGroup",
367
+ "paintImageMaskXObjectRepeat",
368
+ "paintSolidColorImageMask",
369
+ "showText",
370
+ "showSpacedText",
371
+ "nextLineShowText",
372
+ "nextLineSetSpacingShowText"
373
+ ].map((name) => OPS[name]).filter((op) => op !== undefined));
374
+ async function pageHasMarks(doc, pageNum) {
375
+ const ops = await (await doc.getPage(pageNum)).getOperatorList();
376
+ return ops.fnArray.some((fn, i) => fn === OPS.constructPath ? PAINT_OPS.has(ops.argsArray[i][0]) : PAINT_OPS.has(fn));
377
+ }
378
+ async function getImageResolutions(doc) {
379
+ const { imagesByPage } = await getOpPass(doc);
380
+ const out = [];
381
+ for (const [pageNum, imgs] of imagesByPage) {
382
+ let page;
383
+ try {
384
+ page = await doc.getPage(pageNum);
385
+ } catch {
386
+ continue;
387
+ }
388
+ for (const img of imgs) {
389
+ if (img.name === "(inline)")
390
+ continue;
391
+ const px = await getImagePixelSize(page, img.name);
392
+ if (!px)
393
+ continue;
394
+ const placedWin = img.placedW / 72;
395
+ const placedHin = img.placedH / 72;
396
+ if (placedWin <= 0 || placedHin <= 0)
397
+ continue;
398
+ out.push({
399
+ page: pageNum,
400
+ xDpi: px.width / placedWin,
401
+ yDpi: px.height / placedHin
402
+ });
403
+ }
404
+ }
405
+ return out;
406
+ }
407
+ async function isLoadable(doc) {
408
+ try {
409
+ for (let i = 1;i <= doc.numPages; i++) {
410
+ await doc.getPage(i);
411
+ }
412
+ return true;
413
+ } catch {
414
+ return false;
415
+ }
416
+ }
417
+
418
+ export { DOC_CACHE_MAX, loadPdf, retainPdfCache, clearPdfCache, pendingGraceDestroyCount, getPageSize, getOutlineCount, getPageLabels, countLinkAnnotations, getTextPass, getOpPass, unsplittableTextReasons, pageHasMarks, getImageResolutions, isLoadable };