@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,354 @@
1
+ import { IDENTITY_MATRIX, applyAffine } from "../press/affine.js";
2
+ import { visitContentOperations } from "../press/content.js";
3
+ import { decodeContent, dictionaryOf, nameOf, numberOf, resolvedColorSpace } from "../press/pdf-objects.js";
4
+ import { GENERATED_EFFECT_IMAGE_KEY } from "./types.js";
5
+ import { SHADING_SAMPLE_SPACING_PT, chargeShadingSamples } from "../press/sample-shading.js";
6
+ import { functionEvaluator } from "./function-evaluator.js";
7
+ import { EMPTY_EFFECT_GRAPHICS_STATE, advanceEffectGraphicsState, effectFormStructure, effectGraphicsStateOpacity, effectGraphicsStateStructure, effectGraphicsStateValue } from "./structure.js";
8
+ import { PDFArray, PDFDict, PDFName, PDFNumber, PDFRawStream, PDFRef } from "pdf-lib";
9
+ import { deflateSync } from "node:zlib";
10
+ //#region src/effects/rule-c.ts
11
+ const key = (name) => PDFName.of(name);
12
+ const resolved = (document, object) => object instanceof PDFRef ? document.context.lookup(object) : object;
13
+ const MAX_EFFECT_COVERAGE_LAYERS = 4096;
14
+ function arrayNumbers(dictionary, label) {
15
+ const array = dictionary.lookupMaybe(key(label), PDFArray);
16
+ if (!array) return void 0;
17
+ const values = [];
18
+ for (let index = 0; index < array.size(); index++) {
19
+ const value = array.lookupMaybe(index, PDFNumber)?.asNumber();
20
+ if (value === void 0 || !Number.isFinite(value)) return void 0;
21
+ values.push(value);
22
+ }
23
+ return values;
24
+ }
25
+ function clamp(value) {
26
+ return Math.max(0, Math.min(1, value));
27
+ }
28
+ function axialLayer(document, pattern) {
29
+ if (numberOf(pattern, "PatternType") !== 2) return void 0;
30
+ const shading = dictionaryOf(resolved(document, pattern.get(key("Shading"))));
31
+ if (!shading || numberOf(shading, "ShadingType") !== 2) return void 0;
32
+ const space = resolvedColorSpace(document, resolved(document, shading.get(key("ColorSpace")))).kind;
33
+ const channels = space === "rgb" ? 3 : space === "gray" ? 1 : 0;
34
+ const coords = arrayNumbers(shading, "Coords");
35
+ const matrix = arrayNumbers(pattern, "Matrix") ?? IDENTITY_MATRIX;
36
+ const domain = arrayNumbers(shading, "Domain") ?? [0, 1];
37
+ const evaluate = functionEvaluator(document, shading.get(key("Function")), channels);
38
+ if (!channels || coords?.length !== 4 || matrix.length !== 6 || domain.length !== 2 || !evaluate) return void 0;
39
+ const start = applyAffine(matrix, coords[0] ?? 0, coords[1] ?? 0);
40
+ const end = applyAffine(matrix, coords[2] ?? 0, coords[3] ?? 0);
41
+ const dx = end.x - start.x;
42
+ const dy = end.y - start.y;
43
+ if (!(dx * dx + dy * dy > 0)) return void 0;
44
+ return {
45
+ axis: {
46
+ start,
47
+ end
48
+ },
49
+ channels,
50
+ cost: evaluate.cost,
51
+ evaluate: (position) => evaluate((domain[0] ?? 0) + position * ((domain[1] ?? 1) - (domain[0] ?? 0))).map(clamp)
52
+ };
53
+ }
54
+ function operations(stream, budget) {
55
+ const found = [];
56
+ visitContentOperations(decodeContent(stream, budget), (operation) => found.push(operation));
57
+ return found;
58
+ }
59
+ function simplePaintStream(stream, budget) {
60
+ const source = new TextDecoder("latin1").decode(decodeContent(stream, budget));
61
+ return !/[()]/.test(source) && !/(?:^|\s)(?:BT|ET|Tj|TJ)(?:\s|$)/.test(source);
62
+ }
63
+ function paints(document, stream, inherited, budget) {
64
+ const resources = stream.dict.lookupMaybe(key("Resources"), PDFDict) ?? inherited;
65
+ const states = resources?.lookupMaybe(key("ExtGState"), PDFDict);
66
+ const patterns = resources?.lookupMaybe(key("Pattern"), PDFDict);
67
+ const xObjects = resources?.lookupMaybe(key("XObject"), PDFDict);
68
+ const stack = [];
69
+ const graphicsStack = [];
70
+ let graphicsState = EMPTY_EFFECT_GRAPHICS_STATE;
71
+ let pattern;
72
+ const result = [];
73
+ for (const operation of operations(stream, budget)) {
74
+ const name = operation.operands.at(-1)?.kind === "name" ? operation.operands.at(-1)?.raw.slice(1) : void 0;
75
+ graphicsState = advanceEffectGraphicsState(document, operation, states, graphicsState, graphicsStack);
76
+ if (operation.operator === "q") stack.push(pattern);
77
+ else if (operation.operator === "Q") pattern = stack.pop();
78
+ else if (["scn", "SCN"].includes(operation.operator) && name) pattern = resolved(document, patterns?.get(key(name)));
79
+ else if (operation.operator === "Do" && name) result.push({
80
+ graphicsState,
81
+ operation,
82
+ xObject: resolved(document, xObjects?.get(key(name)))
83
+ });
84
+ else if ([
85
+ "f",
86
+ "f*",
87
+ "F"
88
+ ].includes(operation.operator)) result.push({
89
+ graphicsState,
90
+ operation,
91
+ pattern
92
+ });
93
+ }
94
+ return result;
95
+ }
96
+ function inspectForm(context, stream) {
97
+ if (nameOf(stream.dict.get(key("Subtype"))) === "Form" && effectFormStructure(stream.dict) === "other") context.structure = "other";
98
+ }
99
+ function inspectGraphicsState(context, graphicsState) {
100
+ if (graphicsState.structure === "other") context.structure = "other";
101
+ }
102
+ function singleLayer(document, stream, budget, context, depth = 0) {
103
+ inspectForm(context, stream);
104
+ if (depth > 12 || !simplePaintStream(stream, budget)) return void 0;
105
+ const items = paints(document, stream, void 0, budget);
106
+ for (const item of items) inspectGraphicsState(context, item.graphicsState);
107
+ if (items.length !== 1 || effectGraphicsStateValue(items[0]?.graphicsState ?? EMPTY_EFFECT_GRAPHICS_STATE, "SMask")) return void 0;
108
+ const item = items[0];
109
+ if (!item) return void 0;
110
+ if (effectGraphicsStateOpacity(item.graphicsState) !== 1) return void 0;
111
+ if (item.pattern instanceof PDFDict) {
112
+ const axial = axialLayer(document, item.pattern);
113
+ if (axial) return axial;
114
+ if (item.pattern instanceof PDFRawStream) return singleLayer(document, item.pattern, budget, context, depth + 1);
115
+ }
116
+ return item.xObject instanceof PDFRawStream ? singleLayer(document, item.xObject, budget, context, depth + 1) : void 0;
117
+ }
118
+ function transfer(document, mask) {
119
+ const object = resolved(document, mask.get(key("TR")));
120
+ if (!object || nameOf(object) === "Identity") return "identity";
121
+ if (!(object instanceof PDFRawStream) || numberOf(object.dict, "FunctionType") !== 4) return void 0;
122
+ return new TextDecoder("latin1").decode(object.contents).replace(/\s+/g, " ").trim() === "{1 exch sub}" ? "invert" : void 0;
123
+ }
124
+ function mappedCoverage(coverage, mapped) {
125
+ if (mapped === "identity") return coverage;
126
+ return coverage.kind === "constant" ? {
127
+ kind: "constant",
128
+ value: 1 - coverage.value
129
+ } : {
130
+ ...coverage,
131
+ transfer: coverage.transfer === "identity" ? "invert" : "identity"
132
+ };
133
+ }
134
+ function scaledCoverage(coverage, opacity) {
135
+ return coverage.kind === "constant" ? {
136
+ kind: "constant",
137
+ value: coverage.value * opacity
138
+ } : {
139
+ ...coverage,
140
+ opacity: coverage.opacity * opacity
141
+ };
142
+ }
143
+ function maskCoverage(document, mask, budget, masks, context, depth) {
144
+ masks.push(mask);
145
+ const source = resolved(document, mask.get(key("G")));
146
+ if (!(source instanceof PDFRawStream)) return void 0;
147
+ const mode = nameOf(mask.get(key("S")));
148
+ const mapped = transfer(document, mask);
149
+ if (!mapped) return void 0;
150
+ if (mode === "Alpha") {
151
+ const coverage = alphaCoverage(document, source, budget, masks, context, depth + 1);
152
+ return coverage ? mappedCoverage(coverage, mapped) : void 0;
153
+ }
154
+ if (mode !== "Luminosity") return void 0;
155
+ const layer = singleLayer(document, source, budget, context, depth + 1);
156
+ const gray = layer?.channels === 1 || layer?.channels === 3 && [
157
+ 0,
158
+ .5,
159
+ 1
160
+ ].every((position) => {
161
+ const sample = layer.evaluate(position);
162
+ return sample.every((value) => Math.abs(value - (sample[0] ?? 0)) < .001);
163
+ });
164
+ return layer && gray ? {
165
+ kind: "layer",
166
+ layer,
167
+ opacity: 1,
168
+ transfer: mapped
169
+ } : void 0;
170
+ }
171
+ function alphaCoverage(document, stream, budget, masks, context, depth = 0) {
172
+ inspectForm(context, stream);
173
+ if (depth > 12 || !simplePaintStream(stream, budget)) return void 0;
174
+ const items = paints(document, stream, void 0, budget);
175
+ for (const found of items) inspectGraphicsState(context, found.graphicsState);
176
+ if (items.length !== 1) return void 0;
177
+ const item = items[0];
178
+ if (!item) return void 0;
179
+ const opacity = effectGraphicsStateOpacity(item.graphicsState);
180
+ if (opacity === void 0) return void 0;
181
+ const mask = dictionaryOf(resolved(document, effectGraphicsStateValue(item.graphicsState, "SMask")));
182
+ if (mask) {
183
+ const found = maskCoverage(document, mask, budget, masks, context, depth);
184
+ return found ? scaledCoverage(found, opacity) : void 0;
185
+ }
186
+ if (item.xObject instanceof PDFRawStream) {
187
+ const found = alphaCoverage(document, item.xObject, budget, masks, context, depth + 1);
188
+ return found ? scaledCoverage(found, opacity) : void 0;
189
+ }
190
+ if (item.pattern instanceof PDFRawStream) {
191
+ const found = alphaCoverage(document, item.pattern, budget, masks, context, depth + 1);
192
+ return found ? scaledCoverage(found, opacity) : void 0;
193
+ }
194
+ return item.pattern instanceof PDFDict || [
195
+ "f",
196
+ "f*",
197
+ "F"
198
+ ].includes(item.operation.operator) ? {
199
+ kind: "constant",
200
+ value: opacity
201
+ } : void 0;
202
+ }
203
+ function sameAxis(left, right) {
204
+ const lx = left.end.x - left.start.x;
205
+ const ly = left.end.y - left.start.y;
206
+ const rx = right.end.x - right.start.x;
207
+ const ry = right.end.y - right.start.y;
208
+ const scale = Math.hypot(lx, ly) * Math.hypot(rx, ry);
209
+ if (!(scale > 0) || Math.abs(lx * ry - ly * rx) > scale * 1e-4) return false;
210
+ return Math.abs((right.start.x - left.start.x) * ly - (right.start.y - left.start.y) * lx) <= scale * 1e-4;
211
+ }
212
+ function compositeCoverage(document, items, budget, context) {
213
+ if (items.length > 4096) return void 0;
214
+ const masks = [];
215
+ const coverage = [];
216
+ for (const item of items) {
217
+ inspectGraphicsState(context, item.graphicsState);
218
+ const opacity = effectGraphicsStateOpacity(item.graphicsState);
219
+ const mask = dictionaryOf(resolved(document, effectGraphicsStateValue(item.graphicsState, "SMask")));
220
+ const source = resolved(document, mask?.get(key("G")));
221
+ if (opacity === void 0 || !mask || !(source instanceof PDFRawStream) || nameOf(mask.get(key("S"))) !== "Alpha") return void 0;
222
+ masks.push(mask);
223
+ const found = alphaCoverage(document, source, budget, masks, context);
224
+ const mapped = transfer(document, mask);
225
+ if (!found || !mapped) return void 0;
226
+ coverage.push(scaledCoverage(mappedCoverage(found, mapped), opacity));
227
+ }
228
+ return {
229
+ coverage,
230
+ masks
231
+ };
232
+ }
233
+ function candidate(context, nested, oneAxis, plan) {
234
+ return {
235
+ nested,
236
+ oneAxis,
237
+ plan,
238
+ structure: context.structure
239
+ };
240
+ }
241
+ function ruleCPlan(document, form, outerGraphicsState, budget) {
242
+ const context = { structure: "allowed" };
243
+ if (effectGraphicsStateStructure(outerGraphicsState) === "other") context.structure = "other";
244
+ inspectForm(context, form);
245
+ if (!simplePaintStream(form, budget)) return candidate(context, false, "non-shading");
246
+ const bbox = arrayNumbers(form.dict, "BBox");
247
+ const items = paints(document, form, void 0, budget);
248
+ const nested = items.some((item) => dictionaryOf(resolved(document, effectGraphicsStateValue(item.graphicsState, "SMask"))) !== void 0);
249
+ if (bbox?.length !== 4 || items.length < 1 || items.some((item) => !(item.xObject instanceof PDFRawStream))) return candidate(context, nested, "non-shading");
250
+ const colorObject = items[0]?.xObject;
251
+ if (!(colorObject instanceof PDFRawStream) || nameOf(colorObject.dict.get(key("Subtype"))) !== "Form") return candidate(context, nested, "non-shading");
252
+ const color = singleLayer(document, colorObject, budget, context);
253
+ if (color?.channels !== 3 || items.some((item) => item.xObject !== colorObject)) return candidate(context, nested, "non-shading");
254
+ const combined = compositeCoverage(document, items, budget, context);
255
+ if (!combined) return candidate(context, nested, "non-shading");
256
+ const { coverage, masks } = combined;
257
+ const layers = coverage.filter((value) => value.kind === "layer");
258
+ if (layers.length === 0) return candidate(context, nested, "non-shading");
259
+ if (layers.some(({ layer }) => !sameAxis(color.axis, layer.axis))) return candidate(context, nested, "different");
260
+ return candidate(context, nested, "shared", {
261
+ bbox,
262
+ color,
263
+ coverage,
264
+ masks
265
+ });
266
+ }
267
+ function positionOnAxis(axis, x, y) {
268
+ const dx = axis.end.x - axis.start.x;
269
+ const dy = axis.end.y - axis.start.y;
270
+ return ((x - axis.start.x) * dx + (y - axis.start.y) * dy) / (dx * dx + dy * dy);
271
+ }
272
+ function buildRuleCImage(document, plan, opacity, budget, printMatrix) {
273
+ const dx = plan.color.axis.end.x - plan.color.axis.start.x;
274
+ const dy = plan.color.axis.end.y - plan.color.axis.start.y;
275
+ const length = Math.hypot(dx, dy);
276
+ if (!(length > 0)) return void 0;
277
+ const ux = dx / length;
278
+ const uy = dy / length;
279
+ const vx = -uy;
280
+ const vy = ux;
281
+ const [left = 0, bottom = 0, right = 0, top = 0] = plan.bbox;
282
+ const corners = [
283
+ [left, bottom],
284
+ [left, top],
285
+ [right, bottom],
286
+ [right, top]
287
+ ];
288
+ const along = corners.map(([x = 0, y = 0]) => x * ux + y * uy);
289
+ const across = corners.map(([x = 0, y = 0]) => x * vx + y * vy);
290
+ const amin = Math.min(...along);
291
+ const amax = Math.max(...along);
292
+ const vmin = Math.min(...across);
293
+ const vmax = Math.max(...across);
294
+ const printStart = applyAffine(printMatrix, plan.color.axis.start.x, plan.color.axis.start.y);
295
+ const printEnd = applyAffine(printMatrix, plan.color.axis.end.x, plan.color.axis.end.y);
296
+ const printScale = Math.hypot(printEnd.x - printStart.x, printEnd.y - printStart.y) / length;
297
+ const rows = Math.ceil((amax - amin) * printScale / SHADING_SAMPLE_SPACING_PT) + 1;
298
+ if (rows > 1048576) return void 0;
299
+ const operations = rows * (plan.color.cost + plan.coverage.reduce((sum, coverage) => sum + (coverage.kind === "layer" ? coverage.layer.cost : 0), 0));
300
+ if ((budget.calculatorOperations ?? 0) + operations > 436207616) return void 0;
301
+ chargeShadingSamples(budget, rows, operations);
302
+ const rgb = new Uint8Array(rows * 3);
303
+ const gray = new Uint8Array(rows);
304
+ for (let row = 0; row < rows; row++) {
305
+ const distance = amin + (amax - amin) * row / Math.max(1, rows - 1);
306
+ const x = ux * distance + vx * ((vmin + vmax) / 2);
307
+ const y = uy * distance + vy * ((vmin + vmax) / 2);
308
+ const colorPosition = positionOnAxis(plan.color.axis, x, y);
309
+ const color = plan.color.evaluate(colorPosition);
310
+ rgb.set(color.map((value) => Math.round(clamp(value) * 255)), (rows - row - 1) * 3);
311
+ let alpha = 0;
312
+ for (const coverage of plan.coverage) {
313
+ let value = coverage.kind === "constant" ? coverage.value : coverage.layer.evaluate(positionOnAxis(coverage.layer.axis, x, y))[0] ?? 0;
314
+ if (coverage.kind === "layer" && coverage.transfer === "invert") value = 1 - value;
315
+ if (coverage.kind === "layer") value *= coverage.opacity;
316
+ alpha = alpha + clamp(value) * (1 - alpha);
317
+ }
318
+ gray[rows - row - 1] = Math.round(clamp(alpha * opacity) * 255);
319
+ }
320
+ const mask = document.context.register(document.context.stream(deflateSync(gray), {
321
+ Type: "XObject",
322
+ Subtype: "Image",
323
+ Width: 1,
324
+ Height: rows,
325
+ ColorSpace: "DeviceGray",
326
+ BitsPerComponent: 8,
327
+ Interpolate: true,
328
+ Filter: "FlateDecode"
329
+ }));
330
+ return {
331
+ image: document.context.register(document.context.stream(deflateSync(rgb), {
332
+ Type: "XObject",
333
+ Subtype: "Image",
334
+ Width: 1,
335
+ Height: rows,
336
+ ColorSpace: "DeviceRGB",
337
+ BitsPerComponent: 8,
338
+ Interpolate: true,
339
+ Filter: "FlateDecode",
340
+ SMask: mask,
341
+ [GENERATED_EFFECT_IMAGE_KEY]: true
342
+ })),
343
+ matrix: [
344
+ vx * (vmax - vmin),
345
+ vy * (vmax - vmin),
346
+ ux * (amax - amin),
347
+ uy * (amax - amin),
348
+ vx * vmin + ux * amin,
349
+ vy * vmin + uy * amin
350
+ ]
351
+ };
352
+ }
353
+ //#endregion
354
+ export { MAX_EFFECT_COVERAGE_LAYERS, buildRuleCImage, ruleCPlan };
@@ -0,0 +1,86 @@
1
+ import { dictionaryOf, nameOf } from "../press/pdf-objects.js";
2
+ import { PDFArray, PDFName, PDFNumber, PDFRef } from "pdf-lib";
3
+ //#region src/effects/structure.ts
4
+ const key = (name) => PDFName.of(name);
5
+ const resolved = (document, object) => object instanceof PDFRef ? document.context.lookup(object) : object;
6
+ const EMPTY_EFFECT_GRAPHICS_STATE = {
7
+ dictionaries: [],
8
+ structure: "allowed"
9
+ };
10
+ function finiteNumber(dictionary, label) {
11
+ const object = dictionary.get(key(label));
12
+ const value = object instanceof PDFRef ? dictionary.context.lookup(object) : object;
13
+ const number = value instanceof PDFNumber ? value.asNumber() : void 0;
14
+ return number !== void 0 && Number.isFinite(number) ? number : void 0;
15
+ }
16
+ function effectOpacity(graphicsState) {
17
+ if (!graphicsState?.has(key("ca"))) return 1;
18
+ return finiteNumber(graphicsState, "ca");
19
+ }
20
+ function effectGraphicsStateStructure(graphicsState) {
21
+ if (!graphicsState) return "allowed";
22
+ for (const [name] of graphicsState.entries()) {
23
+ const label = name.toString().slice(1);
24
+ if (label === "Type" || label === "SMask") continue;
25
+ if (label === "ca" && finiteNumber(graphicsState, label) !== void 0) continue;
26
+ if (label === "CA" && finiteNumber(graphicsState, label) === 1) continue;
27
+ if (label === "BM" && nameOf(graphicsState.lookup(key(label))) === "Normal") continue;
28
+ return "other";
29
+ }
30
+ return "allowed";
31
+ }
32
+ function advanceEffectGraphicsState(document, operation, states, current, stack) {
33
+ if (operation.operator === "q") {
34
+ stack.push(current);
35
+ return current;
36
+ }
37
+ if (operation.operator === "Q") return stack.pop() ?? EMPTY_EFFECT_GRAPHICS_STATE;
38
+ const operand = operation.operands[0];
39
+ if (operation.operator !== "gs") return current;
40
+ if (operation.operands.length !== 1 || operand?.kind !== "name") return {
41
+ ...current,
42
+ structure: "other"
43
+ };
44
+ const dictionary = dictionaryOf(resolved(document, states?.get(key(operand.raw.slice(1)))));
45
+ if (!dictionary) return {
46
+ ...current,
47
+ structure: "other"
48
+ };
49
+ return {
50
+ dictionaries: [...current.dictionaries, dictionary],
51
+ structure: current.structure === "other" || effectGraphicsStateStructure(dictionary) === "other" ? "other" : "allowed"
52
+ };
53
+ }
54
+ function effectGraphicsStateValue(state, label) {
55
+ for (let index = state.dictionaries.length - 1; index >= 0; index--) {
56
+ const dictionary = state.dictionaries[index];
57
+ if (dictionary?.has(key(label))) return dictionary.get(key(label));
58
+ }
59
+ }
60
+ function effectGraphicsStateOpacity(state) {
61
+ for (let index = state.dictionaries.length - 1; index >= 0; index--) {
62
+ const dictionary = state.dictionaries[index];
63
+ if (dictionary?.has(key("ca"))) return effectOpacity(dictionary);
64
+ }
65
+ return 1;
66
+ }
67
+ function effectFormStructure(form) {
68
+ if (!form.has(key("Matrix"))) return "allowed";
69
+ const matrix = form.lookupMaybe(key("Matrix"), PDFArray);
70
+ if (matrix?.size() !== 6) return "other";
71
+ const identity = [
72
+ 1,
73
+ 0,
74
+ 0,
75
+ 1,
76
+ 0,
77
+ 0
78
+ ];
79
+ for (let index = 0; index < identity.length; index++) {
80
+ const value = matrix.lookupMaybe(index, PDFNumber)?.asNumber();
81
+ if (value === void 0 || !Number.isFinite(value) || Math.abs(value - (identity[index] ?? 0)) > 1e-9) return "other";
82
+ }
83
+ return "allowed";
84
+ }
85
+ //#endregion
86
+ export { EMPTY_EFFECT_GRAPHICS_STATE, advanceEffectGraphicsState, effectFormStructure, effectGraphicsStateOpacity, effectGraphicsStateStructure, effectGraphicsStateValue, effectOpacity };
@@ -0,0 +1,15 @@
1
+ //#region src/effects/types.d.ts
2
+ /**
3
+ * Counts the implemented rewrites. Rule 2 is unbuilt, Rule 3 is left out
4
+ * because measurements show it is ineffective, and Rule 4 is unbuilt and
5
+ * will be opt-in and off by default when implemented.
6
+ */
7
+ export interface EffectsReport {
8
+ /** Luminosity masks rewritten from a grayscale image group to a direct image alpha mask. */
9
+ rule1: number;
10
+ /** Nested one-axis shading composites rewritten as an interpolated image and direct alpha mask. */
11
+ ruleC: number;
12
+ /** Soft masks left in the form Chromium wrote. */
13
+ kept: number;
14
+ }
15
+ //#endregion
@@ -0,0 +1,4 @@
1
+ //#region src/effects/types.ts
2
+ const GENERATED_EFFECT_IMAGE_KEY = "SegnaviaGeneratedEffect";
3
+ //#endregion
4
+ export { GENERATED_EFFECT_IMAGE_KEY };
@@ -0,0 +1,8 @@
1
+ //#region src/errors.d.ts
2
+ export declare class RendererUnavailableError extends Error {
3
+ name: string;
4
+ }
5
+ export declare class RenderRefusedError extends Error {
6
+ name: string;
7
+ }
8
+ //#endregion
package/dist/errors.js ADDED
@@ -0,0 +1,9 @@
1
+ //#region src/errors.ts
2
+ var RendererUnavailableError = class extends Error {
3
+ name = "RendererUnavailableError";
4
+ };
5
+ var RenderRefusedError = class extends Error {
6
+ name = "RenderRefusedError";
7
+ };
8
+ //#endregion
9
+ export { RenderRefusedError, RendererUnavailableError };
package/dist/index.d.ts CHANGED
@@ -1,19 +1,16 @@
1
- import { Deck, PageInput } from "@segnavia/format";
1
+ import { EffectsReport } from "./effects/types.js";
2
+ import { PressImage, PressReport } from "./press/types.js";
3
+ import { RenderRefusedError, RendererUnavailableError } from "./errors.js";
4
+ import { Deck, ImpositionInput, PageInput } from "@segnavia/format";
2
5
  import { PrintOptions } from "@segnavia/frame/print-document";
3
6
  import { Page as Page$1 } from "playwright-core";
4
7
  //#region src/index.d.ts
5
8
  export declare const RENDER_BOUND_MS = 60000;
6
9
  export declare const PAGE_READY_BOUND_MS = 10000;
7
10
  export declare const CONTAINER_ARGS: string[];
8
- export declare class RendererUnavailableError extends Error {
9
- name: string;
10
- }
11
11
  export declare class RenderTimeoutError extends Error {
12
12
  name: string;
13
13
  }
14
- export declare class RenderRefusedError extends Error {
15
- name: string;
16
- }
17
14
  export interface Font {
18
15
  family: string;
19
16
  data: Uint8Array;
@@ -33,6 +30,15 @@ export interface RenderOptions {
33
30
  waitForPageReady?: boolean;
34
31
  preferCssPageSize?: boolean;
35
32
  beforeOutput?: (tab: Page$1) => Promise<void>;
33
+ effects?: false | {
34
+ report?: (report: EffectsReport) => void;
35
+ };
36
+ press?: {
37
+ /** The ICC profile of the press, as bytes. The library ships none. */
38
+ profile: Uint8Array;
39
+ /** Called with authored RGB images left unchanged and counts read back from the output. */
40
+ report?: (report: PressReport) => void;
41
+ };
36
42
  }
37
43
  export interface ImageOptions extends RenderOptions {
38
44
  format?: 'png' | 'jpeg' | 'webp';
@@ -48,6 +54,9 @@ export interface RendererOptions {
48
54
  }
49
55
  export interface Renderer {
50
56
  pagePdf(page: PageInput, options?: RenderOptions): Promise<Uint8Array>;
57
+ pagesPdf(pages: PageInput[], options?: RenderOptions & {
58
+ imposition?: ImpositionInput;
59
+ }): Promise<Uint8Array>;
51
60
  pageImage(page: PageInput, options?: ImageOptions): Promise<Uint8Array>;
52
61
  deckPdf(deck: Deck, options?: RenderOptions & PrintOptions): Promise<Uint8Array>;
53
62
  slideImage(deck: Deck, index: number, options?: ImageOptions & Pick<PrintOptions, 'inline'>): Promise<Uint8Array>;
@@ -56,4 +65,5 @@ export interface Renderer {
56
65
  export declare function fontFaces(fonts?: Font[]): string;
57
66
  export declare function captureImage(tab: Page$1, options: Pick<ImageOptions, 'format' | 'quality' | 'limits'>, fullPage: boolean): Promise<Uint8Array>;
58
67
  export declare function openRenderer(options: RendererOptions): Promise<Renderer>;
59
- //#endregion
68
+ //#endregion
69
+ export { type EffectsReport, type PressImage, type PressReport, RenderRefusedError, RendererUnavailableError };