@segnavia/render 0.1.0 → 0.3.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.
Files changed (42) hide show
  1. package/dist/color/binary.js +27 -0
  2. package/dist/color/black-point.js +121 -0
  3. package/dist/color/engine.js +18 -0
  4. package/dist/color/interpolation.js +74 -0
  5. package/dist/color/profile.js +98 -0
  6. package/dist/color/spaces.js +138 -0
  7. package/dist/color/tables.js +255 -0
  8. package/dist/color/types.js +21 -0
  9. package/dist/deck-pdf.js +152 -0
  10. package/dist/effects/adapter.js +336 -0
  11. package/dist/effects/content-replacement.js +35 -0
  12. package/dist/effects/function-evaluator.js +72 -0
  13. package/dist/effects/policy.js +10 -0
  14. package/dist/effects/rule-1.js +40 -0
  15. package/dist/effects/rule-c.js +354 -0
  16. package/dist/effects/structure.js +86 -0
  17. package/dist/effects/types.d.ts +15 -0
  18. package/dist/effects/types.js +4 -0
  19. package/dist/errors.d.ts +8 -0
  20. package/dist/errors.js +9 -0
  21. package/dist/index.d.ts +18 -8
  22. package/dist/index.js +96 -76
  23. package/dist/page-paper.js +94 -0
  24. package/dist/pages-imposition.js +65 -0
  25. package/dist/pages-pdf.js +67 -0
  26. package/dist/pdf-compose.js +35 -0
  27. package/dist/pdf-geometry.js +27 -0
  28. package/dist/press/adapter.js +275 -0
  29. package/dist/press/affine.js +60 -0
  30. package/dist/press/calculator-ops.js +277 -0
  31. package/dist/press/calculator.js +130 -0
  32. package/dist/press/color-policy.js +31 -0
  33. package/dist/press/content.js +292 -0
  34. package/dist/press/pattern-bounds.js +275 -0
  35. package/dist/press/pattern-path.js +102 -0
  36. package/dist/press/pdf-objects.js +88 -0
  37. package/dist/press/policy.js +126 -0
  38. package/dist/press/report.js +89 -0
  39. package/dist/press/sample-shading.js +266 -0
  40. package/dist/press/types.d.ts +18 -0
  41. package/dist/press/visitor.js +163 -0
  42. package/package.json +3 -3
@@ -0,0 +1,275 @@
1
+ import { IDENTITY_MATRIX, multiplyAffine } from "./affine.js";
2
+ import { visitContentTokens } from "./content.js";
3
+ import { numberOf } from "./pdf-objects.js";
4
+ import { clearPath, closePath, curveTo, curveV, curveY, deviceBox, intersectRect, lineTo, moveTo, rectTo } from "./pattern-path.js";
5
+ const cloneGraphics = (graphics) => ({
6
+ clip: graphics.clip ? { ...graphics.clip } : void 0,
7
+ ctm: graphics.ctm.slice(),
8
+ fill: graphics.fill,
9
+ lineJoin: graphics.lineJoin,
10
+ lineWidth: graphics.lineWidth,
11
+ miterLimit: graphics.miterLimit,
12
+ stroke: graphics.stroke
13
+ });
14
+ function numbers(operands, count) {
15
+ if (operands.length !== count || operands.some((operand) => operand.kind !== "number")) return void 0;
16
+ return operands.map((operand) => Number(operand.raw));
17
+ }
18
+ function nameOf(operands) {
19
+ if (operands.length !== 1 || operands[0]?.kind !== "name") return void 0;
20
+ return operands[0].raw.slice(1);
21
+ }
22
+ function unionRect(left, right) {
23
+ return {
24
+ x0: Math.min(left.x0, right.x0),
25
+ y0: Math.min(left.y0, right.y0),
26
+ x1: Math.max(left.x1, right.x1),
27
+ y1: Math.max(left.y1, right.y1)
28
+ };
29
+ }
30
+ function addPaint(bounds, target, paint) {
31
+ if (!target) return;
32
+ const existing = bounds.get(target);
33
+ if (existing === "unbounded" || paint === "unbounded") {
34
+ bounds.set(target, "unbounded");
35
+ return;
36
+ }
37
+ bounds.set(target, existing ? unionRect(existing, paint) : paint);
38
+ }
39
+ function strokeMargin(graphics) {
40
+ const half = graphics.lineWidth / 2;
41
+ if (!Number.isFinite(half) || half < 0) return void 0;
42
+ if (graphics.lineJoin === 1 || graphics.lineJoin === 2) return half;
43
+ if (graphics.lineJoin !== 0 || !Number.isFinite(graphics.miterLimit) || graphics.miterLimit < 1) return void 0;
44
+ return graphics.miterLimit * half;
45
+ }
46
+ function paintFill(state) {
47
+ if (state.pastSaveCap) {
48
+ addPaint(state.bounds, state.current.fill, "unbounded");
49
+ return;
50
+ }
51
+ if (!state.path.box) return;
52
+ const box = deviceBox(state.path.box, state.current.ctm);
53
+ if (box) addPaint(state.bounds, state.current.fill, box);
54
+ }
55
+ function paintStroke(state) {
56
+ if (state.pastSaveCap) {
57
+ addPaint(state.bounds, state.current.stroke, "unbounded");
58
+ return;
59
+ }
60
+ if (!state.path.box) return;
61
+ const margin = strokeMargin(state.current);
62
+ if (margin === void 0) {
63
+ addPaint(state.bounds, state.current.stroke, "unbounded");
64
+ return;
65
+ }
66
+ const grown = {
67
+ x0: state.path.box.x0 - margin,
68
+ y0: state.path.box.y0 - margin,
69
+ x1: state.path.box.x1 + margin,
70
+ y1: state.path.box.y1 + margin
71
+ };
72
+ const box = deviceBox(grown, state.current.ctm);
73
+ if (box) addPaint(state.bounds, state.current.stroke, box);
74
+ }
75
+ function applyMove(state, operands) {
76
+ const values = numbers(operands, 2);
77
+ if (values) moveTo(state.path, values[0] ?? 0, values[1] ?? 0);
78
+ }
79
+ function applyLine(state, operands) {
80
+ const values = numbers(operands, 2);
81
+ if (values) lineTo(state.path, values[0] ?? 0, values[1] ?? 0);
82
+ }
83
+ function applyCurve(state, operands) {
84
+ const values = numbers(operands, 6);
85
+ if (values) curveTo(state.path, values);
86
+ }
87
+ function applyCurveV(state, operands) {
88
+ const values = numbers(operands, 4);
89
+ if (values) curveV(state.path, values);
90
+ }
91
+ function applyCurveY(state, operands) {
92
+ const values = numbers(operands, 4);
93
+ if (values) curveY(state.path, values);
94
+ }
95
+ function applyRect(state, operands) {
96
+ const values = numbers(operands, 4);
97
+ if (values) rectTo(state.path, values[0] ?? 0, values[1] ?? 0, values[2] ?? 0, values[3] ?? 0);
98
+ }
99
+ function applyCm(state, operands) {
100
+ const values = numbers(operands, 6);
101
+ if (!values) {
102
+ addPaint(state.bounds, state.current.fill, "unbounded");
103
+ addPaint(state.bounds, state.current.stroke, "unbounded");
104
+ return;
105
+ }
106
+ state.current.ctm = multiplyAffine(state.current.ctm, values);
107
+ }
108
+ function clearColor(state, fill) {
109
+ if (fill) state.current.fill = void 0;
110
+ else state.current.stroke = void 0;
111
+ }
112
+ function applyColor(state, operator, operands) {
113
+ const fill = operator === operator.toLowerCase();
114
+ const patternName = nameOf(operands);
115
+ if ((operator === "scn" || operator === "SCN") && patternName) {
116
+ const pattern = state.resolve(patternName);
117
+ if (fill) state.current.fill = pattern;
118
+ else state.current.stroke = pattern;
119
+ return;
120
+ }
121
+ if ((operator === "cs" || operator === "CS") && nameOf(operands) === "Pattern") return;
122
+ clearColor(state, fill);
123
+ }
124
+ const colorHandler = (operator) => (state, operands) => applyColor(state, operator, operands);
125
+ function markUnbounded(state) {
126
+ addPaint(state.bounds, state.current.fill, "unbounded");
127
+ addPaint(state.bounds, state.current.stroke, "unbounded");
128
+ }
129
+ function strokeAndClear(state, close) {
130
+ if (close) closePath(state.path);
131
+ paintStroke(state);
132
+ clearPath(state.path);
133
+ }
134
+ function fillAndClear(state) {
135
+ paintFill(state);
136
+ clearPath(state.path);
137
+ }
138
+ function fillStrokeAndClear(state, close) {
139
+ if (close) closePath(state.path);
140
+ paintFill(state);
141
+ paintStroke(state);
142
+ clearPath(state.path);
143
+ }
144
+ function setLineWidth(state, width) {
145
+ if (!Number.isFinite(width) || width < 0) return false;
146
+ state.current.lineWidth = width;
147
+ return true;
148
+ }
149
+ function setLineJoin(state, join) {
150
+ if (join !== 0 && join !== 1 && join !== 2) return false;
151
+ state.current.lineJoin = join;
152
+ return true;
153
+ }
154
+ function setMiterLimit(state, miter) {
155
+ if (!Number.isFinite(miter) || miter < 1) return false;
156
+ state.current.miterLimit = miter;
157
+ return true;
158
+ }
159
+ function applyStrokeNumber(state, operands, assign) {
160
+ const value = numbers(operands, 1)?.[0];
161
+ if (value === void 0 || !assign(state, value)) markUnbounded(state);
162
+ }
163
+ function applyStyleNumber(dict, key, assign) {
164
+ const value = numberOf(dict, key);
165
+ return value === void 0 || assign(value);
166
+ }
167
+ function applyGraphicsState(state, operands) {
168
+ const name = nameOf(operands);
169
+ const dict = name ? state.resolveGraphicsState(name) : void 0;
170
+ if (!dict) {
171
+ markUnbounded(state);
172
+ return;
173
+ }
174
+ if (!(applyStyleNumber(dict, "LW", (width) => setLineWidth(state, width)) && applyStyleNumber(dict, "LJ", (join) => setLineJoin(state, join)) && applyStyleNumber(dict, "ML", (miter) => setMiterLimit(state, miter)))) markUnbounded(state);
175
+ }
176
+ function saveGraphics(state) {
177
+ if (state.pastSaveCap || state.graphics.length >= 64) {
178
+ state.pastSaveCap = true;
179
+ markUnbounded(state);
180
+ return;
181
+ }
182
+ state.graphics.push(cloneGraphics(state.current));
183
+ }
184
+ function applyClip(state) {
185
+ if (!state.path.box) return;
186
+ const box = deviceBox(state.path.box, state.current.ctm);
187
+ if (!box) return;
188
+ state.current.clip = state.current.clip ? intersectRect(state.current.clip, box) : box;
189
+ }
190
+ function applyShading(state, operands) {
191
+ const name = nameOf(operands);
192
+ const shading = name ? state.resolveShading(name) : void 0;
193
+ if (state.pastSaveCap || !state.current.clip) {
194
+ addPaint(state.shadingBounds, shading, "unbounded");
195
+ return;
196
+ }
197
+ addPaint(state.shadingBounds, shading, state.current.clip);
198
+ }
199
+ const handlers = {
200
+ q: saveGraphics,
201
+ Q: (state) => {
202
+ const restored = state.graphics.pop();
203
+ if (restored) state.current = restored;
204
+ },
205
+ cm: applyCm,
206
+ m: applyMove,
207
+ l: applyLine,
208
+ c: applyCurve,
209
+ v: applyCurveV,
210
+ y: applyCurveY,
211
+ h: (state) => closePath(state.path),
212
+ re: applyRect,
213
+ n: (state) => clearPath(state.path),
214
+ W: applyClip,
215
+ "W*": applyClip,
216
+ sh: applyShading,
217
+ Do: markUnbounded,
218
+ S: (state) => strokeAndClear(state, false),
219
+ s: (state) => strokeAndClear(state, true),
220
+ f: fillAndClear,
221
+ F: fillAndClear,
222
+ "f*": fillAndClear,
223
+ B: (state) => fillStrokeAndClear(state, false),
224
+ "B*": (state) => fillStrokeAndClear(state, false),
225
+ b: (state) => fillStrokeAndClear(state, true),
226
+ "b*": (state) => fillStrokeAndClear(state, true),
227
+ w: (state, operands) => applyStrokeNumber(state, operands, setLineWidth),
228
+ j: (state, operands) => applyStrokeNumber(state, operands, setLineJoin),
229
+ M: (state, operands) => applyStrokeNumber(state, operands, setMiterLimit),
230
+ gs: applyGraphicsState,
231
+ cs: colorHandler("cs"),
232
+ CS: colorHandler("CS"),
233
+ sc: colorHandler("sc"),
234
+ SC: colorHandler("SC"),
235
+ scn: colorHandler("scn"),
236
+ SCN: colorHandler("SCN"),
237
+ rg: colorHandler("rg"),
238
+ RG: colorHandler("RG"),
239
+ g: colorHandler("g"),
240
+ G: colorHandler("G"),
241
+ k: colorHandler("k"),
242
+ K: colorHandler("K")
243
+ };
244
+ function emptyGraphics() {
245
+ return {
246
+ ctm: IDENTITY_MATRIX.slice(),
247
+ lineJoin: 0,
248
+ lineWidth: 1,
249
+ miterLimit: 10
250
+ };
251
+ }
252
+ function createPaintedBoundsTracker(bounds, shadingBounds = /* @__PURE__ */ new WeakMap()) {
253
+ const state = {
254
+ bounds,
255
+ current: emptyGraphics(),
256
+ graphics: [],
257
+ pastSaveCap: false,
258
+ path: {},
259
+ resolve: () => void 0,
260
+ resolveGraphicsState: () => void 0,
261
+ resolveShading: () => void 0,
262
+ shadingBounds
263
+ };
264
+ return { feed(bytes, resolve, resolveGraphicsState = () => void 0, resolveShading = () => void 0) {
265
+ state.resolve = resolve;
266
+ state.resolveGraphicsState = resolveGraphicsState;
267
+ state.resolveShading = resolveShading;
268
+ visitContentTokens(bytes, (operator, operands) => handlers[operator]?.(state, operands));
269
+ } };
270
+ }
271
+ function recordPatternPaintedBounds(bytes, resolve, bounds, resolveGraphicsState = () => void 0, resolveShading = () => void 0, shadingBounds = /* @__PURE__ */ new WeakMap()) {
272
+ createPaintedBoundsTracker(bounds, shadingBounds).feed(bytes, resolve, resolveGraphicsState, resolveShading);
273
+ }
274
+ //#endregion
275
+ export { createPaintedBoundsTracker, recordPatternPaintedBounds, unionRect };
@@ -0,0 +1,102 @@
1
+ import { applyAffine } from "./affine.js";
2
+ //#region src/press/pattern-path.ts
3
+ function includePoint(box, x, y) {
4
+ if (!box) return {
5
+ x0: x,
6
+ y0: y,
7
+ x1: x,
8
+ y1: y
9
+ };
10
+ return {
11
+ x0: Math.min(box.x0, x),
12
+ y0: Math.min(box.y0, y),
13
+ x1: Math.max(box.x1, x),
14
+ y1: Math.max(box.y1, y)
15
+ };
16
+ }
17
+ function addPoint(path, x, y) {
18
+ const point = {
19
+ x,
20
+ y
21
+ };
22
+ path.box = includePoint(path.box, x, y);
23
+ path.current = point;
24
+ return point;
25
+ }
26
+ function deviceBox(box, ctm) {
27
+ const corners = [
28
+ applyAffine(ctm, box.x0, box.y0),
29
+ applyAffine(ctm, box.x1, box.y0),
30
+ applyAffine(ctm, box.x0, box.y1),
31
+ applyAffine(ctm, box.x1, box.y1)
32
+ ];
33
+ const xs = corners.map((point) => point.x);
34
+ const ys = corners.map((point) => point.y);
35
+ const x0 = Math.min(...xs);
36
+ const y0 = Math.min(...ys);
37
+ const x1 = Math.max(...xs);
38
+ const y1 = Math.max(...ys);
39
+ if (!Number.isFinite(x0) || !Number.isFinite(y0) || !Number.isFinite(x1) || !Number.isFinite(y1)) return void 0;
40
+ return {
41
+ x0,
42
+ y0,
43
+ x1,
44
+ y1
45
+ };
46
+ }
47
+ function closePath(path) {
48
+ if (path.start) addPoint(path, path.start.x, path.start.y);
49
+ }
50
+ function clearPath(path) {
51
+ path.box = void 0;
52
+ path.current = void 0;
53
+ path.start = void 0;
54
+ }
55
+ function moveTo(path, x, y) {
56
+ path.start = addPoint(path, x, y);
57
+ }
58
+ function lineTo(path, x, y) {
59
+ addPoint(path, x, y);
60
+ }
61
+ function curveTo(path, values) {
62
+ addPoint(path, values[0] ?? 0, values[1] ?? 0);
63
+ addPoint(path, values[2] ?? 0, values[3] ?? 0);
64
+ addPoint(path, values[4] ?? 0, values[5] ?? 0);
65
+ }
66
+ function curveV(path, values) {
67
+ if (!path.current) return;
68
+ addPoint(path, path.current.x, path.current.y);
69
+ addPoint(path, values[0] ?? 0, values[1] ?? 0);
70
+ addPoint(path, values[2] ?? 0, values[3] ?? 0);
71
+ }
72
+ function curveY(path, values) {
73
+ addPoint(path, values[0] ?? 0, values[1] ?? 0);
74
+ addPoint(path, values[2] ?? 0, values[3] ?? 0);
75
+ addPoint(path, values[2] ?? 0, values[3] ?? 0);
76
+ }
77
+ function rectTo(path, x, y, width, height) {
78
+ addPoint(path, x, y);
79
+ path.start = {
80
+ x,
81
+ y
82
+ };
83
+ addPoint(path, x + width, y);
84
+ addPoint(path, x + width, y + height);
85
+ addPoint(path, x, y + height);
86
+ addPoint(path, x, y);
87
+ }
88
+ function intersectRect(left, right) {
89
+ const x0 = Math.max(left.x0, right.x0);
90
+ const y0 = Math.max(left.y0, right.y0);
91
+ const x1 = Math.min(left.x1, right.x1);
92
+ const y1 = Math.min(left.y1, right.y1);
93
+ if (!(x0 < x1) || !(y0 < y1)) return void 0;
94
+ return {
95
+ x0,
96
+ y0,
97
+ x1,
98
+ y1
99
+ };
100
+ }
101
+ //#endregion
102
+ export { addPoint, clearPath, closePath, curveTo, curveV, curveY, deviceBox, includePoint, intersectRect, lineTo, moveTo, rectTo };
@@ -0,0 +1,88 @@
1
+ import { RenderRefusedError } from "../errors.js";
2
+ import { colorSpaceKind, colorSpaceLabel, contentFilter } from "./policy.js";
3
+ import { PDFArray, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef, PDFStream } from "pdf-lib";
4
+ import { inflateSync } from "node:zlib";
5
+ //#region src/press/pdf-objects.ts
6
+ const MAX_CONTENT_BYTES = 33554432;
7
+ const MAX_DOCUMENT_CONTENT_BYTES = 134217728;
8
+ const nameOf = (object) => object instanceof PDFName ? object.toString().slice(1) : void 0;
9
+ function dictionaryOf(object) {
10
+ if (object instanceof PDFDict) return object;
11
+ if (object instanceof PDFStream) return object.dict;
12
+ }
13
+ function numberOf(dictionary, key) {
14
+ return dictionary.lookupMaybe(PDFName.of(key), PDFNumber)?.asNumber();
15
+ }
16
+ function arrayNumbers(dictionary, key, count) {
17
+ const array = dictionary.lookupMaybe(PDFName.of(key), PDFArray);
18
+ if (!array || array.size() !== count) throw new Error(`PDF ${key} has unsupported length ${array?.size() ?? "missing"}; supported: ${count} numbers`);
19
+ return Array.from({ length: count }, (_, index) => {
20
+ const value = array.lookupMaybe(index, PDFNumber)?.asNumber();
21
+ if (value === void 0) throw new Error(`PDF ${key} has unsupported non-number at index ${index}; supported: ${count} numbers`);
22
+ return value;
23
+ });
24
+ }
25
+ function replaceArray(dictionary, key, values) {
26
+ const array = dictionary.context.obj(values);
27
+ dictionary.set(PDFName.of(key), array);
28
+ }
29
+ function rawStreamOf(object) {
30
+ return object instanceof PDFRawStream ? object : void 0;
31
+ }
32
+ const resolved = (document, object) => object instanceof PDFRef ? document.context.lookup(object) : object;
33
+ function colorSpaceValue(document, object) {
34
+ const value = resolved(document, object);
35
+ const name = nameOf(value);
36
+ if (name) return { name };
37
+ if (!(value instanceof PDFArray)) return void 0;
38
+ const family = nameOf(resolved(document, value.get(0)));
39
+ if (family === "ICCBased") return {
40
+ family,
41
+ channels: rawStreamOf(resolved(document, value.get(1)))?.dict.lookupMaybe(PDFName.of("N"), PDFNumber)?.asNumber()
42
+ };
43
+ if (family === "Indexed") return {
44
+ family,
45
+ base: colorSpaceValue(document, value.get(1))
46
+ };
47
+ return { family };
48
+ }
49
+ function resolvedColorSpace(document, object) {
50
+ const value = colorSpaceValue(document, object);
51
+ return {
52
+ kind: colorSpaceKind(value),
53
+ label: object ? colorSpaceLabel(value) : "missing"
54
+ };
55
+ }
56
+ function filterNames(filter) {
57
+ if (!filter) return void 0;
58
+ const direct = nameOf(filter);
59
+ if (direct) return [direct];
60
+ if (!(filter instanceof PDFArray)) return [];
61
+ const names = [];
62
+ for (let index = 0; index < filter.size(); index++) {
63
+ const name = nameOf(filter.get(index));
64
+ if (!name) return [];
65
+ names.push(name);
66
+ }
67
+ return names;
68
+ }
69
+ function decodeContent(stream, budget) {
70
+ const filter = stream.dict.get(PDFName.of("Filter"));
71
+ const decision = contentFilter(filterNames(filter));
72
+ if (decision.action === "refuse") throw new RenderRefusedError(`The PDF has unsupported content stream filter other; supported: ${decision.supported}.`);
73
+ let decoded;
74
+ try {
75
+ decoded = decision.encoding === "compressed" ? inflateSync(stream.contents, { maxOutputLength: MAX_CONTENT_BYTES }) : stream.contents;
76
+ } catch (error) {
77
+ if (error.code === "ERR_BUFFER_TOO_LARGE") throw new RenderRefusedError(`The PDF has unsupported decoded content stream longer than ${MAX_CONTENT_BYTES} bytes; supported: at most ${MAX_CONTENT_BYTES} bytes.`);
78
+ throw new RenderRefusedError(`The PDF has unsupported content stream decode failure; supported: a valid FlateDecode or unfiltered stream.`);
79
+ }
80
+ if (decoded.byteLength > 33554432) throw new RenderRefusedError(`The PDF has unsupported decoded content stream length ${decoded.byteLength}; supported: at most ${MAX_CONTENT_BYTES} bytes.`);
81
+ if (budget) {
82
+ budget.decodedBytes += decoded.byteLength;
83
+ if (budget.decodedBytes > 134217728) throw new RenderRefusedError(`The PDF has unsupported total decoded content length ${budget.decodedBytes}; supported: at most ${MAX_DOCUMENT_CONTENT_BYTES} bytes.`);
84
+ }
85
+ return decoded;
86
+ }
87
+ //#endregion
88
+ export { MAX_CONTENT_BYTES, MAX_DOCUMENT_CONTENT_BYTES, arrayNumbers, decodeContent, dictionaryOf, nameOf, numberOf, rawStreamOf, replaceArray, resolvedColorSpace };
@@ -0,0 +1,126 @@
1
+ //#region src/press/policy.ts
2
+ function colorSpaceKind(value) {
3
+ if (value?.name === "DeviceRGB" || value?.name === "CalRGB") return "rgb";
4
+ if (value?.name === "DeviceGray") return "gray";
5
+ if (value?.name === "DeviceCMYK") return "cmyk";
6
+ if (value?.family === "ICCBased") return value.channels === 3 ? "rgb" : value.channels === 1 ? "gray" : value.channels === 4 ? "cmyk" : "other";
7
+ if (value?.family === "Indexed") return colorSpaceKind(value.base);
8
+ return "other";
9
+ }
10
+ function colorSpaceLabel(value) {
11
+ const label = value?.name ?? value?.family;
12
+ if (!label) return "missing";
13
+ return [
14
+ "DeviceRGB",
15
+ "DeviceGray",
16
+ "DeviceCMYK",
17
+ "CalRGB",
18
+ "ICCBased",
19
+ "Indexed",
20
+ "Pattern"
21
+ ].includes(label) ? label : "other";
22
+ }
23
+ const decision = (action, supported) => ({
24
+ action,
25
+ supported
26
+ });
27
+ function contentFilter(names) {
28
+ const supported = `FlateDecode or no filter`;
29
+ if (!names) return {
30
+ ...decision("keep", supported),
31
+ encoding: "none"
32
+ };
33
+ if (names.length === 1 && names[0] === "FlateDecode") return {
34
+ ...decision("keep", supported),
35
+ encoding: "compressed"
36
+ };
37
+ return {
38
+ ...decision("refuse", supported),
39
+ encoding: "unsupported"
40
+ };
41
+ }
42
+ function groupAction(kind, label, maskSource) {
43
+ const supported = "DeviceCMYK, DeviceGray, DeviceRGB on a mask source, or no declared space";
44
+ if (kind === "gray" || kind === "cmyk" || maskSource && (label === "DeviceRGB" || label === "missing")) return decision("keep", supported);
45
+ if (label === "missing" || label === "DeviceRGB") return decision("convert", supported);
46
+ return decision("refuse", supported);
47
+ }
48
+ function shadingAction(kind, type, functionType) {
49
+ if (type === 1) {
50
+ const supported = "ShadingType 1 in an RGB space with FunctionType 4, or DeviceCMYK";
51
+ if (kind === "cmyk") return decision("keep", supported);
52
+ if (kind === "rgb" && functionType === 4) return decision("convert", supported);
53
+ return decision("refuse", supported);
54
+ }
55
+ const supported = "ShadingType 2 or 3 in an RGB or DeviceCMYK color space";
56
+ if (type !== 2 && type !== 3) return decision("refuse", supported);
57
+ if (kind === "rgb") return decision("convert", supported);
58
+ if (kind === "cmyk") return decision("keep", supported);
59
+ return decision("refuse", supported);
60
+ }
61
+ function shadingFunctionAction(type, dictionary, functions) {
62
+ if (!dictionary) return {
63
+ ...decision("refuse", "FunctionType 2 or 3"),
64
+ found: "shading function other"
65
+ };
66
+ if (type !== 2 && type !== 3) return {
67
+ ...decision("refuse", "FunctionType 2 or 3"),
68
+ found: `shading FunctionType ${type ?? "missing"}`
69
+ };
70
+ if (type === 3 && !functions) return {
71
+ ...decision("refuse", "a Functions array"),
72
+ found: "FunctionType 3 Functions missing"
73
+ };
74
+ return {
75
+ ...decision("keep", "FunctionType 2 or 3"),
76
+ found: `shading FunctionType ${type}`
77
+ };
78
+ }
79
+ const inlineNames = {
80
+ G: { name: "DeviceGray" },
81
+ DeviceGray: { name: "DeviceGray" },
82
+ RGB: { name: "DeviceRGB" },
83
+ DeviceRGB: { name: "DeviceRGB" },
84
+ CMYK: { name: "DeviceCMYK" },
85
+ DeviceCMYK: { name: "DeviceCMYK" },
86
+ I: { family: "Indexed" },
87
+ Indexed: { family: "Indexed" },
88
+ CalRGB: { name: "CalRGB" }
89
+ };
90
+ function inlineImageAction(name, imageMask) {
91
+ const value = name ? inlineNames[name] ?? { name } : void 0;
92
+ const kind = colorSpaceKind(value);
93
+ return {
94
+ ...decision(imageMask || kind === "gray" || kind === "cmyk" ? "keep" : "refuse", "an image mask, DeviceGray, or DeviceCMYK"),
95
+ kind,
96
+ label: colorSpaceLabel(value)
97
+ };
98
+ }
99
+ function contentColorSpaceAction(name) {
100
+ return decision(name === "Pattern" || name === "DeviceCMYK" ? "keep" : "refuse", "Pattern or DeviceCMYK");
101
+ }
102
+ const colorOperators = [
103
+ "rg",
104
+ "RG",
105
+ "g",
106
+ "G",
107
+ "k",
108
+ "K",
109
+ "sc",
110
+ "SC",
111
+ "scn",
112
+ "SCN",
113
+ "cs",
114
+ "CS"
115
+ ];
116
+ function isContentColorOperator(operator) {
117
+ return colorOperators.includes(operator);
118
+ }
119
+ function contentOperatorAction(operator, inspect, selectedSpace, patternName = false) {
120
+ if (operator === "rg" || operator === "RG" || operator === "g" || operator === "G") return decision(inspect ? "refuse" : "convert", inspect ? "CMYK operators" : "RGB or DeviceGray color operands");
121
+ if (operator === "k" || operator === "K") return decision("keep", "four CMYK operands");
122
+ if (operator === "cs" || operator === "CS") return contentColorSpaceAction(selectedSpace);
123
+ return decision((operator === "scn" || operator === "SCN") && patternName ? "keep" : "refuse", "a pattern name with scn or SCN");
124
+ }
125
+ //#endregion
126
+ export { colorSpaceKind, colorSpaceLabel, contentColorSpaceAction, contentFilter, contentOperatorAction, groupAction, inlineImageAction, isContentColorOperator, shadingAction, shadingFunctionAction };
@@ -0,0 +1,89 @@
1
+ import { RenderRefusedError } from "../errors.js";
2
+ import { groupAction, shadingAction } from "./policy.js";
3
+ import { inspectContent } from "./content.js";
4
+ import { decodeContent, dictionaryOf, 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 fn = dictionaryOf(resolved(state, shading.get(key("Function"))));
36
+ const functionType = fn ? numberOf(fn, "FunctionType") : void 0;
37
+ const space = resolvedColorSpace(state.document, resolved(state, shading.get(key("ColorSpace"))));
38
+ const policy = shadingAction(space.kind, type, functionType);
39
+ if (policy.action !== "keep") {
40
+ if (type === 1) refuse(`ShadingType 1 FunctionType ${functionType ?? "missing"} color space ${space.label}`, policy.supported);
41
+ refuse(`ShadingType ${type ?? "missing"} color space ${space.label}`, policy.supported);
42
+ }
43
+ if (type === 1) {
44
+ state.report.sampledShadings++;
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+ function inspectGroup(state, owner, maskSource) {
50
+ const group = owner.lookupMaybe(key("Group"), PDFDict);
51
+ if (!group || nameOf(group.get(key("S"))) !== "Transparency" || state.groups.has(group)) return;
52
+ state.groups.add(group);
53
+ const space = resolvedColorSpace(state.document, resolved(state, group.get(key("CS"))));
54
+ const policy = groupAction(space.kind, space.label, maskSource);
55
+ if (policy.action !== "keep") refuse(`transparency group color space ${space.label}`, policy.supported);
56
+ if (space.kind === "cmyk") state.report.groups++;
57
+ }
58
+ async function inspectPressPdf(bytes) {
59
+ const document = await PDFDocument.load(bytes, { updateMetadata: false });
60
+ const state = {
61
+ budget: { decodedBytes: 0 },
62
+ document,
63
+ groups: /* @__PURE__ */ new WeakSet(),
64
+ images: /* @__PURE__ */ new WeakSet(),
65
+ report: {
66
+ images: [],
67
+ fills: 0,
68
+ strokes: 0,
69
+ gradientStops: 0,
70
+ sampledShadings: 0,
71
+ groups: 0
72
+ },
73
+ shadings: /* @__PURE__ */ new WeakSet()
74
+ };
75
+ walkPressGraph(document, {
76
+ content: (_object, stream) => {
77
+ const counts = inspectContent(decodeContent(stream, state.budget));
78
+ state.report.fills += counts.fills;
79
+ state.report.strokes += counts.strokes;
80
+ },
81
+ function: (fn, decision) => inspectFunction(state, fn, decision),
82
+ group: (owner, _stream, maskSource) => inspectGroup(state, owner, maskSource),
83
+ image: (image) => inspectImage(state, image),
84
+ shading: (shading) => inspectShading(state, shading)
85
+ });
86
+ return state.report;
87
+ }
88
+ //#endregion
89
+ export { inspectPressPdf };