@segnavia/render 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/dist/color/binary.js +27 -0
- package/dist/color/black-point.js +121 -0
- package/dist/color/engine.js +18 -0
- package/dist/color/interpolation.js +74 -0
- package/dist/color/profile.js +98 -0
- package/dist/color/spaces.js +138 -0
- package/dist/color/tables.js +255 -0
- package/dist/color/types.js +21 -0
- package/dist/deck-pdf.js +152 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +9 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +322 -0
- package/dist/page-paper.js +94 -0
- package/dist/pages-imposition.js +65 -0
- package/dist/pages-pdf.js +66 -0
- package/dist/pdf-compose.js +35 -0
- package/dist/pdf-geometry.js +27 -0
- package/dist/press/adapter.js +145 -0
- package/dist/press/color-policy.js +31 -0
- package/dist/press/content.js +269 -0
- package/dist/press/pdf-objects.js +88 -0
- package/dist/press/policy.js +120 -0
- package/dist/press/report.js +79 -0
- package/dist/press/types.d.ts +16 -0
- package/dist/press/visitor.js +140 -0
- package/package.json +32 -4
- package/README.md +0 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { RenderRefusedError } from "../errors.js";
|
|
2
|
+
import { groupAction, shadingAction } from "./policy.js";
|
|
3
|
+
import { inspectContent } from "./content.js";
|
|
4
|
+
import { decodeContent, nameOf, numberOf, resolvedColorSpace } from "./pdf-objects.js";
|
|
5
|
+
import { walkPressGraph } from "./visitor.js";
|
|
6
|
+
import { PDFDict, PDFDocument, PDFName, PDFRef } from "pdf-lib";
|
|
7
|
+
//#region src/press/report.ts
|
|
8
|
+
const key = (name) => PDFName.of(name);
|
|
9
|
+
const refuse = (found, supported) => {
|
|
10
|
+
throw new RenderRefusedError(`The PDF has unsupported ${found}; supported: ${supported}.`);
|
|
11
|
+
};
|
|
12
|
+
const resolved = (state, object) => object instanceof PDFRef ? state.document.context.lookup(object) : object;
|
|
13
|
+
const requiredDimension = (value, name) => value ?? refuse(`RGB image ${name} missing`, `a ${name} number`);
|
|
14
|
+
function inspectImage(state, image) {
|
|
15
|
+
if (state.images.has(image)) return;
|
|
16
|
+
state.images.add(image);
|
|
17
|
+
const space = resolvedColorSpace(state.document, resolved(state, image.dict.get(key("ColorSpace"))));
|
|
18
|
+
if (space.kind !== "rgb") return;
|
|
19
|
+
const width = requiredDimension(numberOf(image.dict, "Width"), "Width");
|
|
20
|
+
const height = requiredDimension(numberOf(image.dict, "Height"), "Height");
|
|
21
|
+
state.report.images.push({
|
|
22
|
+
width,
|
|
23
|
+
height,
|
|
24
|
+
colorSpace: space.label
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function inspectFunction(state, fn, policy) {
|
|
28
|
+
if (policy.action === "refuse") refuse(policy.found, policy.supported);
|
|
29
|
+
if (fn && numberOf(fn, "FunctionType") === 2) state.report.gradientStops += 2;
|
|
30
|
+
}
|
|
31
|
+
function inspectShading(state, shading) {
|
|
32
|
+
if (state.shadings.has(shading)) return false;
|
|
33
|
+
state.shadings.add(shading);
|
|
34
|
+
const type = numberOf(shading, "ShadingType");
|
|
35
|
+
const space = resolvedColorSpace(state.document, resolved(state, shading.get(key("ColorSpace"))));
|
|
36
|
+
const policy = shadingAction(space.kind, type);
|
|
37
|
+
if (policy.action === "refuse" || policy.action === "convert") refuse(`ShadingType ${type ?? "missing"} color space ${space.label}`, policy.supported);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
function inspectGroup(state, owner, maskSource) {
|
|
41
|
+
const group = owner.lookupMaybe(key("Group"), PDFDict);
|
|
42
|
+
if (!group || nameOf(group.get(key("S"))) !== "Transparency" || state.groups.has(group)) return;
|
|
43
|
+
state.groups.add(group);
|
|
44
|
+
const space = resolvedColorSpace(state.document, resolved(state, group.get(key("CS"))));
|
|
45
|
+
const policy = groupAction(space.kind, space.label, maskSource);
|
|
46
|
+
if (policy.action !== "keep") refuse(`transparency group color space ${space.label}`, policy.supported);
|
|
47
|
+
if (space.kind === "cmyk") state.report.groups++;
|
|
48
|
+
}
|
|
49
|
+
async function inspectPressPdf(bytes) {
|
|
50
|
+
const document = await PDFDocument.load(bytes, { updateMetadata: false });
|
|
51
|
+
const state = {
|
|
52
|
+
budget: { decodedBytes: 0 },
|
|
53
|
+
document,
|
|
54
|
+
groups: /* @__PURE__ */ new WeakSet(),
|
|
55
|
+
images: /* @__PURE__ */ new WeakSet(),
|
|
56
|
+
report: {
|
|
57
|
+
images: [],
|
|
58
|
+
fills: 0,
|
|
59
|
+
strokes: 0,
|
|
60
|
+
gradientStops: 0,
|
|
61
|
+
groups: 0
|
|
62
|
+
},
|
|
63
|
+
shadings: /* @__PURE__ */ new WeakSet()
|
|
64
|
+
};
|
|
65
|
+
walkPressGraph(document, {
|
|
66
|
+
content: (_object, stream) => {
|
|
67
|
+
const counts = inspectContent(decodeContent(stream, state.budget));
|
|
68
|
+
state.report.fills += counts.fills;
|
|
69
|
+
state.report.strokes += counts.strokes;
|
|
70
|
+
},
|
|
71
|
+
function: (fn, decision) => inspectFunction(state, fn, decision),
|
|
72
|
+
group: (owner, _stream, maskSource) => inspectGroup(state, owner, maskSource),
|
|
73
|
+
image: (image) => inspectImage(state, image),
|
|
74
|
+
shading: (shading) => inspectShading(state, shading)
|
|
75
|
+
});
|
|
76
|
+
return state.report;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { inspectPressPdf };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/press/types.d.ts
|
|
2
|
+
export interface PressImage {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
colorSpace: 'DeviceRGB' | 'CalRGB' | 'ICCBased' | 'Indexed';
|
|
6
|
+
}
|
|
7
|
+
export interface PressReport {
|
|
8
|
+
/** Authored raster images left in RGB, read back from the finished PDF. */
|
|
9
|
+
images: PressImage[];
|
|
10
|
+
/** CMYK painting and group counts read back from the finished PDF. */
|
|
11
|
+
fills: number;
|
|
12
|
+
strokes: number;
|
|
13
|
+
gradientStops: number;
|
|
14
|
+
groups: number;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { shadingFunctionAction } from "./policy.js";
|
|
2
|
+
import { dictionaryOf, nameOf, numberOf, rawStreamOf } from "./pdf-objects.js";
|
|
3
|
+
import { PDFArray, PDFDict, PDFName, PDFRef } from "pdf-lib";
|
|
4
|
+
//#region src/press/visitor.ts
|
|
5
|
+
const key = (name) => PDFName.of(name);
|
|
6
|
+
const resolved = (state, object) => object instanceof PDFRef ? state.document.context.lookup(object) : object;
|
|
7
|
+
const dictionary = (state, object) => dictionaryOf(resolved(state, object));
|
|
8
|
+
const stream = (state, object) => rawStreamOf(resolved(state, object));
|
|
9
|
+
function visitContent(state, object, replace) {
|
|
10
|
+
if (!object) return;
|
|
11
|
+
if (object instanceof PDFRef) {
|
|
12
|
+
const label = object.toString();
|
|
13
|
+
if (state.references.has(label)) return;
|
|
14
|
+
state.references.add(label);
|
|
15
|
+
}
|
|
16
|
+
const found = stream(state, object);
|
|
17
|
+
if (!found || state.streams.has(found)) return;
|
|
18
|
+
state.streams.add(found);
|
|
19
|
+
state.actions?.group(found.dict, found, state.maskSources.has(found));
|
|
20
|
+
state.actions?.content(object, found, replace);
|
|
21
|
+
visitResources(state, found.dict.lookupMaybe(key("Resources"), PDFDict));
|
|
22
|
+
}
|
|
23
|
+
function visitSoftMask(state, object) {
|
|
24
|
+
const mask = dictionary(state, dictionary(state, object)?.get(key("SMask")));
|
|
25
|
+
const sourceObject = mask?.get(key("G"));
|
|
26
|
+
const source = stream(state, sourceObject);
|
|
27
|
+
if (!source || !sourceObject) return;
|
|
28
|
+
state.maskSources.add(source);
|
|
29
|
+
visitContent(state, sourceObject, (replacement) => mask?.set(key("G"), replacement));
|
|
30
|
+
}
|
|
31
|
+
function visitAppearances(state, page) {
|
|
32
|
+
const annotations = resolved(state, page.get(key("Annots")));
|
|
33
|
+
if (!(annotations instanceof PDFArray)) return;
|
|
34
|
+
for (let index = 0; index < annotations.size(); index++) {
|
|
35
|
+
const appearance = dictionary(state, dictionary(state, annotations.get(index))?.get(key("AP")));
|
|
36
|
+
for (const name of [
|
|
37
|
+
"N",
|
|
38
|
+
"R",
|
|
39
|
+
"D"
|
|
40
|
+
]) {
|
|
41
|
+
const object = appearance?.get(key(name));
|
|
42
|
+
if (!object) continue;
|
|
43
|
+
if (stream(state, object)) visitContent(state, object, (replacement) => appearance?.set(key(name), replacement));
|
|
44
|
+
else {
|
|
45
|
+
const states = dictionary(state, object);
|
|
46
|
+
for (const [stateName, stateObject] of states?.entries() ?? []) visitContent(state, stateObject, (replacement) => states?.set(stateName, replacement));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function visitFunction(state, object) {
|
|
52
|
+
const fn = dictionary(state, object);
|
|
53
|
+
if (fn && state.functions.has(fn)) return;
|
|
54
|
+
if (fn) state.functions.add(fn);
|
|
55
|
+
const type = fn ? numberOf(fn, "FunctionType") : void 0;
|
|
56
|
+
const functions = type === 3 ? fn?.lookupMaybe(key("Functions"), PDFArray) : void 0;
|
|
57
|
+
const decision = shadingFunctionAction(type, Boolean(fn), type !== 3 || Boolean(functions));
|
|
58
|
+
state.actions?.function(fn, decision);
|
|
59
|
+
for (let index = 0; index < (functions?.size() ?? 0); index++) visitFunction(state, functions?.get(index));
|
|
60
|
+
}
|
|
61
|
+
function visitShading(state, shading) {
|
|
62
|
+
if (state.actions?.shading(shading)) visitFunction(state, shading.get(key("Function")));
|
|
63
|
+
}
|
|
64
|
+
function visitShadings(state, resources) {
|
|
65
|
+
const shadings = resources.lookupMaybe(key("Shading"), PDFDict);
|
|
66
|
+
for (const [, object] of shadings?.entries() ?? []) {
|
|
67
|
+
const found = dictionary(state, object);
|
|
68
|
+
if (found) visitShading(state, found);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function visitPatterns(state, resources) {
|
|
72
|
+
const patterns = resources.lookupMaybe(key("Pattern"), PDFDict);
|
|
73
|
+
for (const [name, object] of patterns?.entries() ?? []) {
|
|
74
|
+
const found = dictionary(state, object);
|
|
75
|
+
if (!found) continue;
|
|
76
|
+
if (numberOf(found, "PatternType") === 2) {
|
|
77
|
+
const shading = dictionary(state, found.get(key("Shading")));
|
|
78
|
+
if (shading) visitShading(state, shading);
|
|
79
|
+
} else visitContent(state, object, (replacement) => patterns?.set(name, replacement));
|
|
80
|
+
visitResources(state, found.lookupMaybe(key("Resources"), PDFDict));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function visitGraphicsStates(state, resources) {
|
|
84
|
+
const graphicsStates = resources.lookupMaybe(key("ExtGState"), PDFDict);
|
|
85
|
+
for (const [, object] of graphicsStates?.entries() ?? []) visitSoftMask(state, object);
|
|
86
|
+
}
|
|
87
|
+
function visitXObjects(state, resources) {
|
|
88
|
+
const xObjects = resources.lookupMaybe(key("XObject"), PDFDict);
|
|
89
|
+
for (const [name, object] of xObjects?.entries() ?? []) {
|
|
90
|
+
const found = stream(state, object);
|
|
91
|
+
if (!found) continue;
|
|
92
|
+
if (nameOf(found.dict.get(key("Subtype"))) === "Image") state.actions?.image(found);
|
|
93
|
+
else visitContent(state, object, (replacement) => xObjects?.set(name, replacement));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function visitFonts(state, resources) {
|
|
97
|
+
const fonts = resources.lookupMaybe(key("Font"), PDFDict);
|
|
98
|
+
for (const [, object] of fonts?.entries() ?? []) {
|
|
99
|
+
const font = dictionary(state, object);
|
|
100
|
+
if (!font || nameOf(font.get(key("Subtype"))) !== "Type3") continue;
|
|
101
|
+
const charProcs = font.lookupMaybe(key("CharProcs"), PDFDict);
|
|
102
|
+
for (const [name, glyph] of charProcs?.entries() ?? []) visitContent(state, glyph, (replacement) => charProcs?.set(name, replacement));
|
|
103
|
+
visitResources(state, font.lookupMaybe(key("Resources"), PDFDict));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function visitResources(state, resources) {
|
|
107
|
+
if (!resources || state.resources.has(resources)) return;
|
|
108
|
+
state.resources.add(resources);
|
|
109
|
+
visitShadings(state, resources);
|
|
110
|
+
visitPatterns(state, resources);
|
|
111
|
+
visitGraphicsStates(state, resources);
|
|
112
|
+
visitXObjects(state, resources);
|
|
113
|
+
visitFonts(state, resources);
|
|
114
|
+
}
|
|
115
|
+
function traverse(document, maskSources, actions) {
|
|
116
|
+
const state = {
|
|
117
|
+
document,
|
|
118
|
+
maskSources,
|
|
119
|
+
actions,
|
|
120
|
+
functions: /* @__PURE__ */ new WeakSet(),
|
|
121
|
+
references: /* @__PURE__ */ new Set(),
|
|
122
|
+
resources: /* @__PURE__ */ new WeakSet(),
|
|
123
|
+
streams: /* @__PURE__ */ new WeakSet()
|
|
124
|
+
};
|
|
125
|
+
for (const page of document.getPages()) {
|
|
126
|
+
actions?.group(page.node, void 0, false);
|
|
127
|
+
const contents = page.node.Contents();
|
|
128
|
+
if (contents instanceof PDFArray) for (let index = 0; index < contents.size(); index++) visitContent(state, contents.get(index), (replacement) => contents.set(index, replacement));
|
|
129
|
+
else visitContent(state, contents, (replacement) => page.node.set(key("Contents"), replacement));
|
|
130
|
+
visitResources(state, page.node.Resources());
|
|
131
|
+
visitAppearances(state, page.node);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function walkPressGraph(document, actions) {
|
|
135
|
+
const maskSources = /* @__PURE__ */ new WeakSet();
|
|
136
|
+
traverse(document, maskSources);
|
|
137
|
+
traverse(document, maskSources, actions);
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
export { walkPressGraph };
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@segnavia/render",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/modstudio/segnavia.git",
|
|
9
|
+
"directory": "packages/render"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"@segnavia/source": "./src/index.ts",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@segnavia/format": "0.2.0",
|
|
30
|
+
"@segnavia/frame": "0.2.0",
|
|
31
|
+
"playwright-core": "1.55.1",
|
|
32
|
+
"pdf-lib": "^1.17.1"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/README.md
DELETED