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.
- package/dist/api/index.js +3 -2
- package/dist/{build-qmjg9r2d.js → build-epdcrm2c.js} +5 -4
- package/dist/{cli-zacevwcy.js → cli-0ey902sx.js} +267 -455
- package/dist/{cli-j802s31j.js → cli-61vd4ews.js} +83 -41
- package/dist/{cli-7z29z4wz.js → cli-fmksazej.js} +1 -1
- package/dist/{cli-gh0ym80r.js → cli-h4tfmpg3.js} +121 -20
- package/dist/cli-sth219v1.js +418 -0
- package/dist/cli.js +12 -12
- package/dist/{doctor-fe7tcs4v.js → doctor-78es5scm.js} +2 -2
- package/dist/engine/compiler/build.d.ts +1 -1
- package/dist/engine/shared/margin-box-support.d.ts +2 -2
- package/dist/engine/shared/pin-edges.d.ts +127 -0
- package/dist/engine/shared/print-area.d.ts +68 -0
- package/dist/{engine-9xb1ky8d.js → engine-67f68bxs.js} +1 -1
- package/dist/{engine-yax79k9m.js → engine-7ha27n2b.js} +2 -2
- package/dist/{ext-ahwxt6gv.js → ext-9mc7sr0d.js} +5 -4
- package/dist/{gutterpress-agent-cazqstr1.js → gutterpress-agent-mrdnwktc.js} +78 -13
- package/dist/{gutterpress-viewer-retj6ym2.js → gutterpress-viewer-jr30yw8t.js} +155 -56
- package/dist/{index-6xwtb7cw.js → index-ad95fws8.js} +82 -40
- package/dist/{index-3nr5rzzp.js → index-gm5xbx5w.js} +3 -3
- package/dist/index-sth219v1.js +418 -0
- package/dist/{index-razpty47.js → index-t13p8y08.js} +493 -582
- package/dist/index.js +4 -3
- package/dist/lib/build-fingerprint.d.ts +8 -0
- package/dist/lib/extension-manifest.d.ts +13 -0
- package/dist/lib/ghostscript.d.ts +70 -16
- package/dist/lib/markdown/assemble.d.ts +12 -2
- package/dist/lib/markdown/gutterpress-css.d.ts +46 -30
- package/dist/lib/markdown/index.d.ts +5 -0
- package/dist/lib/pdf-inspect.d.ts +45 -0
- package/dist/{manifest.schema-74s1ykyv.json → manifest.schema-13dfax80.json} +16 -4
- package/dist/{new-ftd4fec4.js → new-s4f0tnya.js} +5 -4
- package/dist/pdf-inspect-zdmy7qca.js +35 -0
- package/dist/{preflight-8sqarcsr.js → preflight-7ck4rbsq.js} +5 -4
- package/dist/preview/file-watcher.d.ts +4 -10
- package/dist/{preview-q76xn95h.js → preview-j91sxadd.js} +5 -4
- package/dist/{publish-7gdy6gbf.js → publish-f12xaenm.js} +5 -4
- package/dist/render.js +57 -6
- package/dist/schema/manifest.types.d.ts +28 -8
- package/dist/test-helpers/testkit.d.ts +2 -2
- package/dist/{validate-e3ryegce.js → validate-pb5gysvr.js} +5 -4
- package/package.json +1 -1
- 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-
|
|
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/
|
|
33
|
-
var
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
38
|
+
function pinPageName(authorPage, edges) {
|
|
38
39
|
const safe = authorPage ? authorPage.replace(/[^A-Za-z0-9_-]/g, "_") : "";
|
|
39
|
-
return `gp--
|
|
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.
|
|
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
|
|
760
|
-
const
|
|
761
|
-
for (const root of
|
|
762
|
-
if (
|
|
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
|
-
|
|
783
|
+
pinGroups.set(root.key, {
|
|
766
784
|
authorPage,
|
|
767
785
|
edges: root.edges,
|
|
768
|
-
|
|
786
|
+
padding: root.padding,
|
|
787
|
+
genName: pinPageName(authorPage, root.edges),
|
|
769
788
|
key: root.key
|
|
770
789
|
});
|
|
771
790
|
}
|
|
772
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
840
|
+
const pinCss = lines.join(`
|
|
816
841
|
`);
|
|
817
|
-
await page.evaluate(`window.__gp.addCss("gp-
|
|
842
|
+
await page.evaluate(`window.__gp.addCss("gp-pin-css", ${JSON.stringify(pinCss)})`);
|
|
818
843
|
model = extract(`${cssText}
|
|
819
|
-
${
|
|
820
|
-
log(
|
|
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 =
|
|
846
|
-
const group =
|
|
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
|
|
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 =
|
|
1027
|
-
const pseudos =
|
|
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 (!
|
|
1046
|
-
|
|
1047
|
-
diagnose("engine.
|
|
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.
|
|
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--
|
|
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--
|
|
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.
|
|
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-
|
|
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-
|
|
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-
|
|
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 };
|