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
|
@@ -567,14 +567,15 @@ function specificity(r) {
|
|
|
567
567
|
return (r.name ? 2 : 0) + r.pseudos.length;
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
-
// src/engine/shared/
|
|
571
|
-
var
|
|
572
|
-
|
|
573
|
-
|
|
570
|
+
// src/engine/shared/pin-edges.ts
|
|
571
|
+
var PIN_EDGES = ["top", "right", "bottom", "left"];
|
|
572
|
+
var EDGE_PIN_SELECTOR = PIN_EDGES.map((edge) => `.gp-pin.gp-${edge}`).join(", ");
|
|
573
|
+
function pinKey(edges) {
|
|
574
|
+
return PIN_EDGES.filter((e) => edges.includes(e)).map((e) => e[0]).join("");
|
|
574
575
|
}
|
|
575
|
-
function
|
|
576
|
+
function pinPageName(authorPage, edges) {
|
|
576
577
|
const safe = authorPage ? authorPage.replace(/[^A-Za-z0-9_-]/g, "_") : "";
|
|
577
|
-
return `gp--
|
|
578
|
+
return `gp--pin${safe ? `-${safe}` : ""}-${pinKey(edges)}`;
|
|
578
579
|
}
|
|
579
580
|
function flushMargins(margin, edges) {
|
|
580
581
|
return {
|
|
@@ -584,6 +585,23 @@ function flushMargins(margin, edges) {
|
|
|
584
585
|
left: edges.includes("left") ? 0 : margin.left
|
|
585
586
|
};
|
|
586
587
|
}
|
|
588
|
+
function pinInsets(area, side, edges) {
|
|
589
|
+
const binding = side === "recto" ? "left" : "right";
|
|
590
|
+
const inset = (edge) => !area || !edges.includes(edge) ? 0 : edge === binding ? area.safe : area.bleed + area.artSafe;
|
|
591
|
+
return { top: inset("top"), right: inset("right"), bottom: inset("bottom"), left: inset("left") };
|
|
592
|
+
}
|
|
593
|
+
var NO_PADDING = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
594
|
+
function pinRootStyle(margin, area, side, edges, padding = NO_PADDING) {
|
|
595
|
+
const px = (pt) => pt * 96 / 72;
|
|
596
|
+
const inset = pinInsets(area, side, edges);
|
|
597
|
+
const style = { "box-decoration-break": "clone" };
|
|
598
|
+
for (const edge of PIN_EDGES)
|
|
599
|
+
if (edges.includes(edge))
|
|
600
|
+
style[`padding-${edge}`] = `${px(margin[edge]) + padding[edge]}px`;
|
|
601
|
+
for (const edge of PIN_EDGES)
|
|
602
|
+
style[`--gp-pin-${edge}`] = `${px(inset[edge])}px`;
|
|
603
|
+
return style;
|
|
604
|
+
}
|
|
587
605
|
function marginBoxesOnEdges(edges) {
|
|
588
606
|
const owners = {
|
|
589
607
|
"top-left-corner": ["top", "left"],
|
|
@@ -1116,7 +1134,7 @@ import { fileURLToPath } from "node:url";
|
|
|
1116
1134
|
var favicon_default = "./favicon-272ga6y0.ico";
|
|
1117
1135
|
|
|
1118
1136
|
// src/assets/manifest.schema.json
|
|
1119
|
-
var manifest_schema_default = "./manifest.schema-
|
|
1137
|
+
var manifest_schema_default = "./manifest.schema-13dfax80.json";
|
|
1120
1138
|
|
|
1121
1139
|
// src/assets/preview/scripts/preview-interface.js
|
|
1122
1140
|
var preview_interface_default = "./preview-interface-nnmrxyqm.js";
|
|
@@ -1131,10 +1149,10 @@ var preview_shell_default = "./preview-shell-wk9k3gcd.js";
|
|
|
1131
1149
|
var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
|
|
1132
1150
|
|
|
1133
1151
|
// src/assets/engine/gutterpress-viewer.js
|
|
1134
|
-
var gutterpress_viewer_default = "./gutterpress-viewer-
|
|
1152
|
+
var gutterpress_viewer_default = "./gutterpress-viewer-jr30yw8t.js";
|
|
1135
1153
|
|
|
1136
1154
|
// src/assets/engine/gutterpress-agent.js
|
|
1137
|
-
var gutterpress_agent_default = "./gutterpress-agent-
|
|
1155
|
+
var gutterpress_agent_default = "./gutterpress-agent-mrdnwktc.js";
|
|
1138
1156
|
|
|
1139
1157
|
// src/assets/templates/book/manifest.yaml
|
|
1140
1158
|
var manifest_default = "./manifest-n1gh3qw5.yaml";
|
|
@@ -1652,25 +1670,31 @@ async function build(opts) {
|
|
|
1652
1670
|
await page.send("Emulation.setDeviceMetricsOverride", sheetViewport);
|
|
1653
1671
|
await page.send("Emulation.setEmulatedMedia", { media: "print" });
|
|
1654
1672
|
log(`print media emulated for audits and measurement`);
|
|
1655
|
-
const
|
|
1656
|
-
const
|
|
1657
|
-
for (const root of
|
|
1658
|
-
if (
|
|
1673
|
+
const pinRoots = await page.evaluate(`window.__gp.pinRoots()`);
|
|
1674
|
+
const pinGroups = new Map;
|
|
1675
|
+
for (const root of pinRoots) {
|
|
1676
|
+
if (pinGroups.has(root.key))
|
|
1659
1677
|
continue;
|
|
1660
1678
|
const authorPage = root.page === "auto" ? undefined : root.page;
|
|
1661
|
-
|
|
1679
|
+
pinGroups.set(root.key, {
|
|
1662
1680
|
authorPage,
|
|
1663
1681
|
edges: root.edges,
|
|
1664
|
-
|
|
1682
|
+
padding: root.padding,
|
|
1683
|
+
genName: pinPageName(authorPage, root.edges),
|
|
1665
1684
|
key: root.key
|
|
1666
1685
|
});
|
|
1667
1686
|
}
|
|
1668
|
-
|
|
1687
|
+
const printArea = pinRoots.length ? await page.evaluate(`window.__gp.printArea()`) : null;
|
|
1688
|
+
const pinStyle = (group, pseudos) => pinRootStyle(resolvePage(model, { name: group.authorPage, pseudos }).geometry.margin, printArea, pseudos.includes("left") ? "verso" : "recto", group.edges, group.padding);
|
|
1689
|
+
if (pinGroups.size) {
|
|
1669
1690
|
const lines = [];
|
|
1670
|
-
|
|
1691
|
+
const aliased = new Set;
|
|
1692
|
+
for (const group of pinGroups.values()) {
|
|
1693
|
+
const alias = !aliased.has(group.genName);
|
|
1694
|
+
aliased.add(group.genName);
|
|
1671
1695
|
const relocated = new Set(marginBoxesOnEdges(group.edges));
|
|
1672
1696
|
const pseudoSets = [[]];
|
|
1673
|
-
if (group.authorPage) {
|
|
1697
|
+
if (alias && group.authorPage) {
|
|
1674
1698
|
for (const rule of model.pageRules) {
|
|
1675
1699
|
if (rule.name !== group.authorPage)
|
|
1676
1700
|
continue;
|
|
@@ -1694,7 +1718,7 @@ ${body.join(`
|
|
|
1694
1718
|
}`);
|
|
1695
1719
|
}
|
|
1696
1720
|
}
|
|
1697
|
-
for (const pseudos of pseudoSets) {
|
|
1721
|
+
for (const pseudos of alias ? pseudoSets : []) {
|
|
1698
1722
|
const pseudo = pseudos.length ? `:${pseudos.join(":")}` : "";
|
|
1699
1723
|
const body = [];
|
|
1700
1724
|
for (const edge of group.edges)
|
|
@@ -1706,14 +1730,15 @@ ${body.join(`
|
|
|
1706
1730
|
`)}
|
|
1707
1731
|
}`);
|
|
1708
1732
|
}
|
|
1709
|
-
|
|
1733
|
+
const recto = Object.entries(pinStyle(group, ["right"])).map(([prop, value]) => ` ${prop}: ${value};`).join("");
|
|
1734
|
+
lines.push(`:where(.page, .spread)[data-gp-pin="${group.key}"][data-gp-pin][data-gp-pin][data-gp-pin] { page: ${group.genName};${recto} }`);
|
|
1710
1735
|
}
|
|
1711
|
-
const
|
|
1736
|
+
const pinCss = lines.join(`
|
|
1712
1737
|
`);
|
|
1713
|
-
await page.evaluate(`window.__gp.addCss("gp-
|
|
1738
|
+
await page.evaluate(`window.__gp.addCss("gp-pin-css", ${JSON.stringify(pinCss)})`);
|
|
1714
1739
|
model = extract(`${cssText}
|
|
1715
|
-
${
|
|
1716
|
-
log(
|
|
1740
|
+
${pinCss}`);
|
|
1741
|
+
log(`pinned art: ${pinRoots.length} page root(s) -> ${pinGroups.size} generated page context(s)`);
|
|
1717
1742
|
}
|
|
1718
1743
|
const { tier3Reasons } = classify(model);
|
|
1719
1744
|
const pseudoContexts = pagePseudoContexts(model);
|
|
@@ -1738,11 +1763,16 @@ ${flushCss}`);
|
|
|
1738
1763
|
return !!c && c !== "none" && c !== "normal";
|
|
1739
1764
|
});
|
|
1740
1765
|
};
|
|
1741
|
-
const furnitureRoots =
|
|
1742
|
-
const group =
|
|
1766
|
+
const furnitureRoots = pinRoots.filter((root) => {
|
|
1767
|
+
const group = pinGroups.get(root.key);
|
|
1743
1768
|
return pseudoContexts.some((pseudos) => relocationBoxes(group, pseudos).length > 0);
|
|
1744
1769
|
});
|
|
1745
|
-
const
|
|
1770
|
+
const relocationDiagnosed = new Set;
|
|
1771
|
+
const sideRoots = pinRoots.filter((root) => {
|
|
1772
|
+
const group = pinGroups.get(root.key);
|
|
1773
|
+
const recto = JSON.stringify(pinStyle(group, ["right"]));
|
|
1774
|
+
return [["left"], ["right", "first"]].some((pseudos) => JSON.stringify(pinStyle(group, pseudos)) !== recto);
|
|
1775
|
+
});
|
|
1746
1776
|
const tier2 = synthesize({
|
|
1747
1777
|
model,
|
|
1748
1778
|
marks: opts.marks,
|
|
@@ -1786,7 +1816,7 @@ ${describe(widthOffenders.intrinsics)}`;
|
|
|
1786
1816
|
log(`WARNING: ${msg}`);
|
|
1787
1817
|
}
|
|
1788
1818
|
const rectoDecls = model.breaks.filter(isRectoVersoBreak);
|
|
1789
|
-
const needsMeasure = tier3Reasons.length > 0 || consumedStrings(model).size > 0 || rectoDecls.length > 0 || model.counterResets.length > 0 || furnitureRoots.length > 0;
|
|
1819
|
+
const needsMeasure = tier3Reasons.length > 0 || consumedStrings(model).size > 0 || rectoDecls.length > 0 || model.counterResets.length > 0 || furnitureRoots.length > 0 || sideRoots.length > 0;
|
|
1790
1820
|
let tier = tier2.geometry.bleed > 0 || tier2.geometry.slug > 0 ? 2 : 1;
|
|
1791
1821
|
let passes = 1;
|
|
1792
1822
|
let pageMap = {};
|
|
@@ -1814,7 +1844,7 @@ ${describe(widthOffenders.intrinsics)}`;
|
|
|
1814
1844
|
targets.add(s.id);
|
|
1815
1845
|
for (const s of resetSites)
|
|
1816
1846
|
targets.add(s.id);
|
|
1817
|
-
for (const r of furnitureRoots)
|
|
1847
|
+
for (const r of [...furnitureRoots, ...sideRoots])
|
|
1818
1848
|
targets.add(r.id);
|
|
1819
1849
|
await page.evaluate(`window.__gp.instrument(${JSON.stringify([...targets])})`);
|
|
1820
1850
|
const targetText = await page.evaluate(`window.__gp.targetTexts(${JSON.stringify([...targets])})`);
|
|
@@ -1900,8 +1930,12 @@ ${describe(widthOffenders.intrinsics)}`;
|
|
|
1900
1930
|
/* Tier 3 */
|
|
1901
1931
|
${mapCss}`;
|
|
1902
1932
|
}
|
|
1933
|
+
const pageAt = (id) => map[id] ?? predictedForResult?.pageMap[id];
|
|
1934
|
+
const pseudosAt = (physical) => [
|
|
1935
|
+
physical % 2 === 1 ? "right" : "left",
|
|
1936
|
+
...physical === 1 ? ["first"] : []
|
|
1937
|
+
];
|
|
1903
1938
|
if (furnitureRoots.length) {
|
|
1904
|
-
const pageAt = (id) => map[id] ?? predictedForResult?.pageMap[id];
|
|
1905
1939
|
const byName = new Map;
|
|
1906
1940
|
for (const s of sources) {
|
|
1907
1941
|
const p = pageAt(s.id);
|
|
@@ -1919,10 +1953,8 @@ ${mapCss}`;
|
|
|
1919
1953
|
const physical = pageAt(root.id);
|
|
1920
1954
|
if (!physical)
|
|
1921
1955
|
continue;
|
|
1922
|
-
const group =
|
|
1923
|
-
const pseudos =
|
|
1924
|
-
if (physical === 1)
|
|
1925
|
-
pseudos.push("first");
|
|
1956
|
+
const group = pinGroups.get(root.key);
|
|
1957
|
+
const pseudos = pseudosAt(physical);
|
|
1926
1958
|
const ctx = resolvePage(model, { name: group.authorPage, pseudos });
|
|
1927
1959
|
const g = ctx.geometry;
|
|
1928
1960
|
const eff = flushMargins(g.margin, group.edges);
|
|
@@ -1938,9 +1970,9 @@ ${mapCss}`;
|
|
|
1938
1970
|
}
|
|
1939
1971
|
});
|
|
1940
1972
|
if (!text) {
|
|
1941
|
-
if (!
|
|
1942
|
-
|
|
1943
|
-
diagnose("engine.
|
|
1973
|
+
if (!relocationDiagnosed.has(name)) {
|
|
1974
|
+
relocationDiagnosed.add(name);
|
|
1975
|
+
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.`);
|
|
1944
1976
|
}
|
|
1945
1977
|
continue;
|
|
1946
1978
|
}
|
|
@@ -1965,7 +1997,17 @@ ${mapCss}`;
|
|
|
1965
1997
|
items.push({ id: root.id, boxes });
|
|
1966
1998
|
}
|
|
1967
1999
|
if (items.length)
|
|
1968
|
-
await page.evaluate(`window.__gp.
|
|
2000
|
+
await page.evaluate(`window.__gp.setPinFurniture(${JSON.stringify(items)})`);
|
|
2001
|
+
}
|
|
2002
|
+
if (sideRoots.length) {
|
|
2003
|
+
const items = sideRoots.flatMap((root) => {
|
|
2004
|
+
const physical = pageAt(root.id);
|
|
2005
|
+
if (!physical)
|
|
2006
|
+
return [];
|
|
2007
|
+
return [{ id: root.id, style: pinStyle(pinGroups.get(root.key), pseudosAt(physical)) }];
|
|
2008
|
+
});
|
|
2009
|
+
if (items.length)
|
|
2010
|
+
await page.evaluate(`window.__gp.setPinGeometry(${JSON.stringify(items)})`);
|
|
1969
2011
|
}
|
|
1970
2012
|
};
|
|
1971
2013
|
const predicted = await predictPageMap(browser, url, AGENT, VIEWER, {
|
|
@@ -2328,12 +2370,12 @@ function shrinkScale(boxes, limitPx) {
|
|
|
2328
2370
|
async function findWidthOffenders(page, model, bleedSlugExtensionPt, restoreViewport) {
|
|
2329
2371
|
const contexts = [
|
|
2330
2372
|
resolvePage(model),
|
|
2331
|
-
...model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--
|
|
2373
|
+
...model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--pin-")).map((n) => resolvePage(model, { name: n }))
|
|
2332
2374
|
];
|
|
2333
2375
|
const contentPxOf = (c) => (c.geometry.width - c.geometry.margin.left - c.geometry.margin.right + bleedSlugExtensionPt) * 96 / 72;
|
|
2334
2376
|
const defaultLimitPx = contentPxOf(contexts[0]);
|
|
2335
2377
|
const namedLimits = {};
|
|
2336
|
-
model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--
|
|
2378
|
+
model.pageNames.filter((n) => !n.startsWith("gp-") || n.startsWith("gp--pin-")).forEach((n) => {
|
|
2337
2379
|
namedLimits[n] = contentPxOf(resolvePage(model, { name: n }));
|
|
2338
2380
|
});
|
|
2339
2381
|
const widths = [...new Set([defaultLimitPx, ...Object.values(namedLimits)])].sort((a, b) => a - b);
|
|
@@ -2520,7 +2562,7 @@ async function predictPageMap(browser, url, agentScript, viewerScript, args, she
|
|
|
2520
2562
|
await page.waitForReady();
|
|
2521
2563
|
await page.evaluate(`window.__GP_MANUAL__ = true;`);
|
|
2522
2564
|
await page.evaluate(viewerScript);
|
|
2523
|
-
await page.evaluate(`window.__gp.
|
|
2565
|
+
await page.evaluate(`window.__gp.pinRoots()`);
|
|
2524
2566
|
await page.evaluate(`window.__gp.stringSources(${JSON.stringify(args.stringSets)})`);
|
|
2525
2567
|
if (args.rectoDecls.length)
|
|
2526
2568
|
await page.evaluate(`window.__gp.forcedBreakSites(${JSON.stringify(args.rectoDecls)})`);
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
// package.json
|
|
5
5
|
var package_default = {
|
|
6
6
|
name: "gutterpress",
|
|
7
|
-
version: "0.11.20-alpha.
|
|
7
|
+
version: "0.11.20-alpha.4",
|
|
8
8
|
description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
|
|
9
9
|
author: "itlackey",
|
|
10
10
|
license: "MPL-2.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
package_default
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-fmksazej.js";
|
|
4
4
|
import {
|
|
5
5
|
enhancedPath,
|
|
6
6
|
execCapture,
|
|
@@ -132,8 +132,8 @@ async function resolveGhostscript(targetPlatform = process.platform, env = proce
|
|
|
132
132
|
}
|
|
133
133
|
return targetPlatform === "win32" ? findWindowsGhostscriptInstall(env) : undefined;
|
|
134
134
|
}
|
|
135
|
-
async function
|
|
136
|
-
const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } = await import("pdf-lib");
|
|
135
|
+
async function transparentPages(pdfBytes) {
|
|
136
|
+
const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFArray, PDFRef } = await import("pdf-lib");
|
|
137
137
|
let doc;
|
|
138
138
|
try {
|
|
139
139
|
doc = await PDFDocument.load(pdfBytes, {
|
|
@@ -141,36 +141,64 @@ async function hasLiveTransparency(pdfBytes) {
|
|
|
141
141
|
throwOnInvalidObject: false
|
|
142
142
|
});
|
|
143
143
|
} catch {
|
|
144
|
-
return
|
|
144
|
+
return [];
|
|
145
145
|
}
|
|
146
146
|
const ctx = doc.context;
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (!dict)
|
|
151
|
-
continue;
|
|
147
|
+
const SKIP = new Set(["Parent", "Font", "Annots"]);
|
|
148
|
+
const isSet = (v) => v !== undefined && String(v) !== "/None";
|
|
149
|
+
const dictHasTransparency = (dict) => {
|
|
152
150
|
const smask = dict.get(PDFName.of("SMask"));
|
|
153
151
|
const subtype = String(dict.get(PDFName.of("Subtype")) ?? "");
|
|
154
152
|
const type = String(dict.get(PDFName.of("Type")) ?? "");
|
|
155
|
-
if (subtype === "/Image" &&
|
|
153
|
+
if (subtype === "/Image" && isSet(smask))
|
|
156
154
|
return true;
|
|
157
|
-
if (type === "/ExtGState") {
|
|
158
|
-
if (
|
|
155
|
+
if (type === "/ExtGState" || dict.has(PDFName.of("ca")) || dict.has(PDFName.of("BM"))) {
|
|
156
|
+
if (isSet(smask))
|
|
159
157
|
return true;
|
|
160
158
|
for (const key of ["ca", "CA"]) {
|
|
161
|
-
const alpha = dict.get(PDFName.of(key));
|
|
159
|
+
const alpha = ctx.lookup(dict.get(PDFName.of(key)));
|
|
162
160
|
if (alpha instanceof PDFNumber && alpha.asNumber() < 1)
|
|
163
161
|
return true;
|
|
164
162
|
}
|
|
163
|
+
const bm = ctx.lookup(dict.get(PDFName.of("BM")));
|
|
164
|
+
const mode = bm instanceof PDFArray ? bm.get(0) : bm;
|
|
165
|
+
if (mode && !["/Normal", "/Compatible"].includes(String(mode)))
|
|
166
|
+
return true;
|
|
165
167
|
}
|
|
166
|
-
const group = dict.get(PDFName.of("Group"));
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
const group = ctx.lookup(dict.get(PDFName.of("Group")));
|
|
169
|
+
return group instanceof PDFDict && String(group.get(PDFName.of("S"))) === "/Transparency";
|
|
170
|
+
};
|
|
171
|
+
const reaches = (root) => {
|
|
172
|
+
const seen = new Set;
|
|
173
|
+
const stack = [root];
|
|
174
|
+
while (stack.length) {
|
|
175
|
+
const node = stack.pop();
|
|
176
|
+
const obj = node instanceof PDFRef ? ctx.lookup(node) : node;
|
|
177
|
+
if (!obj || seen.has(obj))
|
|
178
|
+
continue;
|
|
179
|
+
seen.add(obj);
|
|
180
|
+
if (obj instanceof PDFArray) {
|
|
181
|
+
stack.push(...obj.asArray());
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const dict = obj instanceof PDFRawStream ? obj.dict : obj instanceof PDFDict ? obj : undefined;
|
|
185
|
+
if (!dict)
|
|
186
|
+
continue;
|
|
187
|
+
if (dictHasTransparency(dict))
|
|
170
188
|
return true;
|
|
189
|
+
for (const [key, value] of dict.entries()) {
|
|
190
|
+
if (!SKIP.has(key.asString().slice(1)))
|
|
191
|
+
stack.push(value);
|
|
192
|
+
}
|
|
171
193
|
}
|
|
172
|
-
|
|
173
|
-
|
|
194
|
+
return false;
|
|
195
|
+
};
|
|
196
|
+
const pages = [];
|
|
197
|
+
doc.getPages().forEach((page, i) => {
|
|
198
|
+
if (reaches(page.node))
|
|
199
|
+
pages.push(i + 1);
|
|
200
|
+
});
|
|
201
|
+
return pages;
|
|
174
202
|
}
|
|
175
203
|
async function findPostScriptShadingPages(pdfBytes) {
|
|
176
204
|
const { PDFDocument, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } = await import("pdf-lib");
|
|
@@ -241,6 +269,75 @@ async function stripAnnotations(pdfPath, stagingDir) {
|
|
|
241
269
|
const out = await doc.save();
|
|
242
270
|
await writeFile(pdfPath, out);
|
|
243
271
|
}
|
|
272
|
+
function planTransparentPages(transparent, reasons, flattenPages, gradientPages) {
|
|
273
|
+
const split = [];
|
|
274
|
+
const artOnly = [];
|
|
275
|
+
const kept = new Map;
|
|
276
|
+
for (const n of transparent) {
|
|
277
|
+
const reason = reasons.get(n);
|
|
278
|
+
if (reason === "no-text")
|
|
279
|
+
artOnly.push(n);
|
|
280
|
+
else if (flattenPages.includes(n))
|
|
281
|
+
kept.set(n, "listed");
|
|
282
|
+
else if (reason)
|
|
283
|
+
kept.set(n, reason);
|
|
284
|
+
else if (gradientPages.includes(n))
|
|
285
|
+
kept.set(n, "gradient");
|
|
286
|
+
else
|
|
287
|
+
split.push(n);
|
|
288
|
+
}
|
|
289
|
+
return { split, artOnly, kept };
|
|
290
|
+
}
|
|
291
|
+
async function flattenTransparentPages(pdfPath, pageNumbers, stagingDir, dpi = 300) {
|
|
292
|
+
const pages = [...new Set(pageNumbers)].sort((a, b) => a - b);
|
|
293
|
+
if (pages.length === 0)
|
|
294
|
+
return;
|
|
295
|
+
const ghostscript = await resolveGhostscript();
|
|
296
|
+
if (!ghostscript) {
|
|
297
|
+
throw new Error("Ghostscript executable not found. Install Ghostscript or set GHOSTSCRIPT_PATH to its command-line executable.");
|
|
298
|
+
}
|
|
299
|
+
const art = join(stagingDir, "flatten-art.pdf");
|
|
300
|
+
const text = join(stagingDir, "flatten-text.pdf");
|
|
301
|
+
const pageList = `-sPageList=${pages.join(",")}`;
|
|
302
|
+
const gs = (...args) => run(ghostscript, [
|
|
303
|
+
"-q",
|
|
304
|
+
"-dBATCH",
|
|
305
|
+
"-dNOPAUSE",
|
|
306
|
+
"-dSAFER",
|
|
307
|
+
"-dPreserveAnnots=false",
|
|
308
|
+
"-dNO_PDFMARK_OUTLINES",
|
|
309
|
+
...args
|
|
310
|
+
]);
|
|
311
|
+
try {
|
|
312
|
+
await gs(pageList, "-sDEVICE=pdfimage24", "-dFILTERTEXT", `-r${dpi}`, `-sOutputFile=${art}`, pdfPath);
|
|
313
|
+
await gs(pageList, "-sDEVICE=pdfwrite", "-dNOTRANSPARENCY", "-dFILTERIMAGE", "-dFILTERVECTOR", "-dCompatibilityLevel=1.3", `-sOutputFile=${text}`, pdfPath);
|
|
314
|
+
const { PDFDocument, PDFName } = await import("pdf-lib");
|
|
315
|
+
const doc = await PDFDocument.load(await readFile(pdfPath), { updateMetadata: false });
|
|
316
|
+
const artDoc = await PDFDocument.load(await readFile(art));
|
|
317
|
+
const textDoc = await PDFDocument.load(await readFile(text));
|
|
318
|
+
if (artDoc.getPageCount() !== pages.length || textDoc.getPageCount() !== pages.length) {
|
|
319
|
+
throw new Error(`Ghostscript returned ${artDoc.getPageCount()} artwork and ${textDoc.getPageCount()} text pages for ${pages.length} transparent pages.`);
|
|
320
|
+
}
|
|
321
|
+
const artPages = await doc.embedPages(artDoc.getPages());
|
|
322
|
+
const withText = textDoc.getPages().filter((p) => p.node.Contents());
|
|
323
|
+
const embeddedText = await doc.embedPages(withText);
|
|
324
|
+
const textPages = textDoc.getPages().map((p) => embeddedText[withText.indexOf(p)]);
|
|
325
|
+
pages.forEach((pageNumber, i) => {
|
|
326
|
+
const page = doc.getPage(pageNumber - 1);
|
|
327
|
+
const { x, y, width, height } = page.getMediaBox();
|
|
328
|
+
page.node.set(PDFName.of("Contents"), doc.context.obj([]));
|
|
329
|
+
page.node.set(PDFName.of("Resources"), doc.context.obj({}));
|
|
330
|
+
page.node.delete(PDFName.of("Group"));
|
|
331
|
+
page.drawPage(artPages[i], { x, y, width, height });
|
|
332
|
+
const textPage = textPages[i];
|
|
333
|
+
if (textPage)
|
|
334
|
+
page.drawPage(textPage, { x, y, width, height });
|
|
335
|
+
});
|
|
336
|
+
await writeFile(pdfPath, await doc.save());
|
|
337
|
+
} finally {
|
|
338
|
+
await Promise.all([art, text].map((f) => unlink(f).catch(() => {})));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
244
341
|
function makePdfxDefPs(params) {
|
|
245
342
|
const pdfxVersion = params.pdfx === "x3" ? "PDF/X-3:2002" : "PDF/X-1:2001";
|
|
246
343
|
const pdfxConformance = params.pdfx === "x1a" ? "PDF/X-1a:2001" : null;
|
|
@@ -332,6 +429,10 @@ async function convertToPdfxCmyk(inputPdf, outPdf, config) {
|
|
|
332
429
|
"-dDownsampleColorImages=false",
|
|
333
430
|
"-dDownsampleGrayImages=false",
|
|
334
431
|
"-dDownsampleMonoImages=false",
|
|
432
|
+
"-dAutoFilterColorImages=false",
|
|
433
|
+
"-dColorImageFilter=/FlateEncode",
|
|
434
|
+
"-dAutoFilterGrayImages=false",
|
|
435
|
+
"-dGrayImageFilter=/FlateEncode",
|
|
335
436
|
...config.rasterDpi ? [`-r${config.rasterDpi}`] : [],
|
|
336
437
|
"-sProcessColorModel=DeviceCMYK",
|
|
337
438
|
"-sColorConversionStrategy=CMYK",
|
|
@@ -584,4 +685,4 @@ async function getSystemDiagnostics() {
|
|
|
584
685
|
};
|
|
585
686
|
}
|
|
586
687
|
|
|
587
|
-
export { setLogLevel, debug, info, warn, error, log, resolveGhostscript,
|
|
688
|
+
export { setLogLevel, debug, info, warn, error, log, PACKAGE_META, PACKAGE_VERSION, resolveGhostscript, transparentPages, findPostScriptShadingPages, stripAnnotations, planTransparentPages, flattenTransparentPages, stampCreator, convertToPdfxCmyk, defaultConfigDir, FileTokenStore, getSystemDiagnostics };
|