@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 { RenderRefusedError } from "../errors.js";
2
+ import { cachedPressColor, pressColor } from "./color-policy.js";
3
+ import { groupAction, shadingAction } from "./policy.js";
4
+ import { rewriteContent } from "./content.js";
5
+ import { arrayNumbers, decodeContent, dictionaryOf, nameOf, numberOf, replaceArray, resolvedColorSpace } from "./pdf-objects.js";
6
+ import { walkPressGraph } from "./visitor.js";
7
+ import "../effects/types.js";
8
+ import { replaceType1Function, sampleDomainForUses, shadingDeviceMatrix, shadingDomainOf, shadingSampleGridForUses } from "./sample-shading.js";
9
+ import { readProfile } from "../color/profile.js";
10
+ import { createPaintedBoundsTracker, recordPatternPaintedBounds } from "./pattern-bounds.js";
11
+ import { inspectPressPdf } from "./report.js";
12
+ import { PDFDict, PDFDocument, PDFHexString, PDFName, PDFRef, PDFString } from "pdf-lib";
13
+ import { deflateSync } from "node:zlib";
14
+ //#region src/press/adapter.ts
15
+ const key = (name) => PDFName.of(name);
16
+ const refuse = (found, supported) => {
17
+ throw new RenderRefusedError(`The PDF has unsupported ${found}; supported: ${supported}.`);
18
+ };
19
+ const resolved = (state, object) => object instanceof PDFRef ? state.document.context.lookup(object) : object;
20
+ function convertFunction(state, fn, policy) {
21
+ if (policy.action === "refuse") refuse(policy.found, policy.supported);
22
+ if (!fn) return;
23
+ if (numberOf(fn, "FunctionType") === 2) {
24
+ for (const name of ["C0", "C1"]) replaceArray(fn, name, state.convert(arrayNumbers(fn, name, 3)));
25
+ if (fn.has(key("Range"))) replaceArray(fn, "Range", [
26
+ 0,
27
+ 1,
28
+ 0,
29
+ 1,
30
+ 0,
31
+ 1,
32
+ 0,
33
+ 1
34
+ ]);
35
+ state.totals.gradientStops += 2;
36
+ return;
37
+ }
38
+ if (fn.has(key("Range"))) replaceArray(fn, "Range", [
39
+ 0,
40
+ 1,
41
+ 0,
42
+ 1,
43
+ 0,
44
+ 1,
45
+ 0,
46
+ 1
47
+ ]);
48
+ }
49
+ function functionTypeOf(state, shading) {
50
+ const fn = dictionaryOf(resolved(state, shading.get(key("Function"))));
51
+ return fn ? numberOf(fn, "FunctionType") : void 0;
52
+ }
53
+ function indexShadingPattern(state, shading, pattern) {
54
+ if (!pattern) return false;
55
+ const list = state.shadingPatterns.get(shading) ?? [];
56
+ if (!list.includes(pattern)) list.push(pattern);
57
+ state.shadingPatterns.set(shading, list);
58
+ return false;
59
+ }
60
+ function shadingUses(state, shading, pattern) {
61
+ const patterns = [...state.shadingPatterns.get(shading) ?? []];
62
+ if (pattern && !patterns.includes(pattern)) patterns.push(pattern);
63
+ const uses = patterns.map((found) => ({
64
+ matrix: shadingDeviceMatrix(shading, found),
65
+ painted: state.painted.get(found),
66
+ pattern: found
67
+ }));
68
+ const shPaint = state.shadingPainted.get(shading);
69
+ if (shPaint !== void 0) uses.push({
70
+ matrix: shadingDeviceMatrix(shading, void 0),
71
+ painted: shPaint
72
+ });
73
+ return uses.filter((use) => use.painted !== void 0);
74
+ }
75
+ function convertType1Shading(state, shading, pattern) {
76
+ const uses = shadingUses(state, shading, pattern);
77
+ const domain = sampleDomainForUses(shadingDomainOf(shading), uses);
78
+ const matrices = uses.length > 0 ? uses.map((use) => use.matrix) : [shadingDeviceMatrix(shading, pattern)];
79
+ const size = shadingSampleGridForUses(domain, matrices);
80
+ replaceType1Function(state.document, shading, pattern, (rgb) => pressColor(state.profile, rgb), state.budget, void 0, domain, size);
81
+ }
82
+ function convertShading(state, shading, pattern) {
83
+ if (state.seenShadings.has(shading)) return false;
84
+ state.seenShadings.add(shading);
85
+ const type = numberOf(shading, "ShadingType");
86
+ const functionType = functionTypeOf(state, shading);
87
+ const space = resolvedColorSpace(state.document, resolved(state, shading.get(key("ColorSpace"))));
88
+ const policy = shadingAction(space.kind, type, functionType);
89
+ if (policy.action === "refuse") {
90
+ if (type === 1) refuse(`ShadingType 1 FunctionType ${functionType ?? "missing"}`, policy.supported);
91
+ refuse(`ShadingType ${type ?? "missing"} color space ${space.label}`, policy.supported);
92
+ }
93
+ if (policy.action === "keep") return false;
94
+ if (shading.has(key("Background"))) replaceArray(shading, "Background", state.convert(arrayNumbers(shading, "Background", 3)));
95
+ shading.set(key("ColorSpace"), key("DeviceCMYK"));
96
+ if (type === 1) {
97
+ convertType1Shading(state, shading, pattern);
98
+ return false;
99
+ }
100
+ return true;
101
+ }
102
+ function convertGroup(state, owner, maskSource) {
103
+ const group = owner.lookupMaybe(key("Group"), PDFDict);
104
+ if (!group || nameOf(group.get(key("S"))) !== "Transparency" || state.seenGroups.has(group)) return;
105
+ state.seenGroups.add(group);
106
+ const space = resolvedColorSpace(state.document, resolved(state, group.get(key("CS"))));
107
+ const policy = groupAction(space.kind, space.label, maskSource);
108
+ if (policy.action === "refuse") refuse(`transparency group color space ${space.label}`, policy.supported);
109
+ if (policy.action === "convert") {
110
+ group.set(key("CS"), key("DeviceCMYK"));
111
+ state.totals.groups++;
112
+ }
113
+ }
114
+ function convertRuleCImage(state, image, width, height, dictionary) {
115
+ const source = decodeContent(image, state.budget);
116
+ if (source.length !== width * height * 3) refuse("effect image sample length", "three 8-bit RGB samples per pixel");
117
+ const converted = new Uint8Array(width * height * 4);
118
+ for (let pixel = 0; pixel < width * height; pixel++) {
119
+ const color = state.convert([
120
+ (source[pixel * 3] ?? 0) / 255,
121
+ (source[pixel * 3 + 1] ?? 0) / 255,
122
+ (source[pixel * 3 + 2] ?? 0) / 255
123
+ ]);
124
+ for (let channel = 0; channel < 4; channel++) converted[pixel * 4 + channel] = Math.round(Math.max(0, Math.min(1, color[channel] ?? 0)) * 255);
125
+ }
126
+ dictionary.ColorSpace = key("DeviceCMYK");
127
+ const replacement = state.document.context.stream(deflateSync(converted), dictionary);
128
+ const reference = state.document.context.getObjectRef(image);
129
+ if (reference) state.document.context.assign(reference, replacement);
130
+ }
131
+ function convertEffectImage(state, image) {
132
+ if (!image.dict.has(key("SegnaviaGeneratedEffect")) || resolvedColorSpace(state.document, resolved(state, image.dict.get(key("ColorSpace")))).kind !== "rgb") return;
133
+ const width = numberOf(image.dict, "Width") ?? refuse("effect image Width missing", "a positive Width number");
134
+ const height = numberOf(image.dict, "Height") ?? refuse("effect image Height missing", "a positive Height number");
135
+ if (width <= 0 || height <= 0) refuse("effect image dimensions", "positive Width and Height numbers");
136
+ const dictionary = streamDictionary(image);
137
+ if (!image.dict.has(key("SegnaviaEffectColor"))) {
138
+ convertRuleCImage(state, image, width, height, dictionary);
139
+ return;
140
+ }
141
+ const source = arrayNumbers(image.dict, "SegnaviaEffectColor", 3);
142
+ const color = state.convert(source);
143
+ const palette = color.map((value) => Math.round(Math.max(0, Math.min(1, value)) * 255));
144
+ dictionary.ColorSpace = state.document.context.obj([
145
+ "Indexed",
146
+ "DeviceCMYK",
147
+ 0,
148
+ PDFHexString.of(Buffer.from(palette).toString("hex"))
149
+ ]);
150
+ dictionary.SegnaviaEffectColor = state.document.context.obj(color);
151
+ const replacement = state.document.context.stream(image.contents, dictionary);
152
+ const reference = state.document.context.getObjectRef(image);
153
+ if (reference) state.document.context.assign(reference, replacement);
154
+ }
155
+ function streamDictionary(stream) {
156
+ const dictionary = { Filter: key("FlateDecode") };
157
+ for (const [name, value] of stream.dict.entries()) {
158
+ const label = name.toString().slice(1);
159
+ if (![
160
+ "Filter",
161
+ "DecodeParms",
162
+ "Length"
163
+ ].includes(label)) dictionary[label] = value;
164
+ }
165
+ return dictionary;
166
+ }
167
+ function resolveShadingPattern(state, resources, name) {
168
+ const patterns = resources?.lookupMaybe(key("Pattern"), PDFDict);
169
+ const found = dictionaryOf(resolved(state, patterns?.get(PDFName.of(name))));
170
+ if (found && numberOf(found, "PatternType") === 2) return found;
171
+ }
172
+ function resolveGraphicsState(state, resources, name) {
173
+ const states = resources?.lookupMaybe(key("ExtGState"), PDFDict);
174
+ return dictionaryOf(resolved(state, states?.get(PDFName.of(name))));
175
+ }
176
+ function resolveShadingResource(state, resources, name) {
177
+ const shadings = resources?.lookupMaybe(key("Shading"), PDFDict);
178
+ return dictionaryOf(resolved(state, shadings?.get(PDFName.of(name))));
179
+ }
180
+ function recordContentBounds(state, stream, resources, budget) {
181
+ const bytes = decodeContent(stream, budget);
182
+ const resolve = (name) => resolveShadingPattern(state, resources, name);
183
+ const resolveGs = (name) => resolveGraphicsState(state, resources, name);
184
+ const resolveShading = (name) => resolveShadingResource(state, resources, name);
185
+ if (state.sequence) state.sequence.feed(bytes, resolve, resolveGs, resolveShading);
186
+ else recordPatternPaintedBounds(bytes, resolve, state.painted, resolveGs, resolveShading, state.shadingPainted);
187
+ }
188
+ function convertContent(state, object, stream, setDirect) {
189
+ const bytes = decodeContent(stream, state.budget);
190
+ const rewritten = rewriteContent(bytes, state.convert);
191
+ state.totals.fills += rewritten.counts.fills;
192
+ state.totals.strokes += rewritten.counts.strokes;
193
+ const replacement = state.document.context.stream(deflateSync(rewritten.bytes), streamDictionary(stream));
194
+ const reference = object instanceof PDFRef ? object : state.document.context.getObjectRef(object);
195
+ if (reference) state.document.context.assign(reference, replacement);
196
+ else setDirect(replacement);
197
+ }
198
+ function newState(document, profile) {
199
+ return {
200
+ budget: {
201
+ calculatorOperations: 0,
202
+ decodedBytes: 0,
203
+ shadingSamples: 0
204
+ },
205
+ convert: cachedPressColor(profile),
206
+ document,
207
+ painted: /* @__PURE__ */ new WeakMap(),
208
+ profile,
209
+ seenGroups: /* @__PURE__ */ new WeakSet(),
210
+ seenShadings: /* @__PURE__ */ new WeakSet(),
211
+ shadingPainted: /* @__PURE__ */ new WeakMap(),
212
+ shadingPatterns: /* @__PURE__ */ new WeakMap(),
213
+ totals: {
214
+ fills: 0,
215
+ strokes: 0,
216
+ gradientStops: 0,
217
+ groups: 0
218
+ }
219
+ };
220
+ }
221
+ function rewritePressDocument(document, profile) {
222
+ const state = newState(document, profile);
223
+ const boundsBudget = { decodedBytes: 0 };
224
+ walkPressGraph(document, {
225
+ beginContentsSequence: () => {
226
+ state.sequence = createPaintedBoundsTracker(state.painted, state.shadingPainted);
227
+ },
228
+ content: (_object, stream, _replace, resources) => recordContentBounds(state, stream, resources, boundsBudget),
229
+ endContentsSequence: () => {
230
+ state.sequence = void 0;
231
+ },
232
+ function: () => void 0,
233
+ group: () => void 0,
234
+ image: () => void 0,
235
+ replayPageContents: true,
236
+ shading: (shading, pattern) => indexShadingPattern(state, shading, pattern)
237
+ });
238
+ walkPressGraph(document, {
239
+ content: (object, stream, replace) => convertContent(state, object, stream, replace),
240
+ function: (fn, decision) => convertFunction(state, fn, decision),
241
+ group: (owner, _stream, maskSource) => convertGroup(state, owner, maskSource),
242
+ image: (image) => convertEffectImage(state, image),
243
+ shading: (shading, pattern) => convertShading(state, shading, pattern)
244
+ });
245
+ return state.totals;
246
+ }
247
+ function addOutputIntent(document, profile) {
248
+ const profileRef = document.context.register(document.context.stream(profile, { N: 4 }));
249
+ const intentRef = document.context.register(document.context.obj({
250
+ Type: "OutputIntent",
251
+ S: "GTS_PDFX",
252
+ OutputConditionIdentifier: PDFString.of("Custom"),
253
+ DestOutputProfile: profileRef
254
+ }));
255
+ document.catalog.set(key("OutputIntents"), document.context.obj([intentRef]));
256
+ }
257
+ async function rewritePressPdf(bytes, profileBytes) {
258
+ try {
259
+ const profile = readProfile(profileBytes);
260
+ if (profile.colorSpace !== "CMYK") refuse(`press profile color space ${profile.colorSpace}`, "CMYK");
261
+ const document = await PDFDocument.load(bytes, { updateMetadata: false });
262
+ rewritePressDocument(document, profile);
263
+ addOutputIntent(document, profileBytes);
264
+ const output = await document.save({ useObjectStreams: false });
265
+ return {
266
+ bytes: output,
267
+ report: await inspectPressPdf(output)
268
+ };
269
+ } catch (error) {
270
+ if (error instanceof RenderRefusedError) throw error;
271
+ throw new RenderRefusedError("The PDF has unsupported malformed structure; supported: a valid PDF produced by the renderer.");
272
+ }
273
+ }
274
+ //#endregion
275
+ export { rewritePressDocument, rewritePressPdf };
@@ -0,0 +1,60 @@
1
+ //#region src/press/affine.ts
2
+ const IDENTITY_MATRIX = [
3
+ 1,
4
+ 0,
5
+ 0,
6
+ 1,
7
+ 0,
8
+ 0
9
+ ];
10
+ function affineComponent(values, index) {
11
+ return values[index] ?? 0;
12
+ }
13
+ function multiplyAffine(left, right) {
14
+ const a0 = affineComponent(left, 0);
15
+ const b0 = affineComponent(left, 1);
16
+ const c0 = affineComponent(left, 2);
17
+ const d0 = affineComponent(left, 3);
18
+ const e0 = affineComponent(left, 4);
19
+ const f0 = affineComponent(left, 5);
20
+ const a1 = affineComponent(right, 0);
21
+ const b1 = affineComponent(right, 1);
22
+ const c1 = affineComponent(right, 2);
23
+ const d1 = affineComponent(right, 3);
24
+ const e1 = affineComponent(right, 4);
25
+ const f1 = affineComponent(right, 5);
26
+ return [
27
+ a0 * a1 + c0 * b1,
28
+ b0 * a1 + d0 * b1,
29
+ a0 * c1 + c0 * d1,
30
+ b0 * c1 + d0 * d1,
31
+ a0 * e1 + c0 * f1 + e0,
32
+ b0 * e1 + d0 * f1 + f0
33
+ ];
34
+ }
35
+ function invertAffine(matrix) {
36
+ const a = affineComponent(matrix, 0);
37
+ const b = affineComponent(matrix, 1);
38
+ const c = affineComponent(matrix, 2);
39
+ const d = affineComponent(matrix, 3);
40
+ const e = affineComponent(matrix, 4);
41
+ const f = affineComponent(matrix, 5);
42
+ const det = a * d - b * c;
43
+ if (det === 0 || !Number.isFinite(det)) return void 0;
44
+ return [
45
+ d / det,
46
+ -b / det,
47
+ -c / det,
48
+ a / det,
49
+ (c * f - d * e) / det,
50
+ (b * e - a * f) / det
51
+ ];
52
+ }
53
+ function applyAffine(matrix, x, y) {
54
+ return {
55
+ x: affineComponent(matrix, 0) * x + affineComponent(matrix, 2) * y + affineComponent(matrix, 4),
56
+ y: affineComponent(matrix, 1) * x + affineComponent(matrix, 3) * y + affineComponent(matrix, 5)
57
+ };
58
+ }
59
+ //#endregion
60
+ export { IDENTITY_MATRIX, applyAffine, invertAffine, multiplyAffine };
@@ -0,0 +1,277 @@
1
+ import { RenderRefusedError } from "../errors.js";
2
+ function calculatorRefuse(found, supported) {
3
+ throw new RenderRefusedError(`The PDF has unsupported ${found}; supported: ${supported}.`);
4
+ }
5
+ function pop(state, operator) {
6
+ const value = state.stack.pop();
7
+ if (!value) return calculatorRefuse(`calculator ${operator} stack underflow`, "enough operands");
8
+ return value;
9
+ }
10
+ function classifiedOperatorName(name) {
11
+ return /^[A-Za-z][A-Za-z0-9]{0,15}$/.test(name) ? name : "other";
12
+ }
13
+ function pushStack(state, value) {
14
+ state.stack.push(value);
15
+ if (state.stack.length > 32) calculatorRefuse(`calculator stack depth ${state.stack.length}`, `at most 32 values`);
16
+ }
17
+ function popNumber(state, operator) {
18
+ const value = pop(state, operator);
19
+ if (value.kind !== "n") return calculatorRefuse(`calculator ${operator} operand type`, "a number");
20
+ return value;
21
+ }
22
+ function popInt(state, operator) {
23
+ const value = popNumber(state, operator);
24
+ if (!value.int || !Number.isInteger(value.value)) return calculatorRefuse(`calculator ${operator} operand type`, "an integer");
25
+ return value.value;
26
+ }
27
+ function popBool(state, operator) {
28
+ const value = pop(state, operator);
29
+ if (value.kind !== "b") return calculatorRefuse(`calculator ${operator} operand type`, "a boolean");
30
+ return value.value;
31
+ }
32
+ function popProc(state, operator) {
33
+ const value = pop(state, operator);
34
+ if (value.kind !== "p") return calculatorRefuse(`calculator ${operator} operand type`, "a procedure");
35
+ return value.body;
36
+ }
37
+ function pushNumber(state, value, int) {
38
+ if (!Number.isFinite(value)) calculatorRefuse("calculator numeric result", "a finite number");
39
+ pushStack(state, {
40
+ kind: "n",
41
+ value,
42
+ int: int && Number.isInteger(value)
43
+ });
44
+ }
45
+ function pushBool(state, value) {
46
+ pushStack(state, {
47
+ kind: "b",
48
+ value
49
+ });
50
+ }
51
+ function unary(operator, apply) {
52
+ return (state) => {
53
+ const operand = popNumber(state, operator);
54
+ const result = apply(operand.value, operand.int);
55
+ pushNumber(state, result.value, result.int);
56
+ };
57
+ }
58
+ function binary(operator, apply) {
59
+ return (state) => {
60
+ const right = popNumber(state, operator);
61
+ const left = popNumber(state, operator);
62
+ const result = apply(left.value, right.value, left.int && right.int);
63
+ pushNumber(state, result.value, result.int);
64
+ };
65
+ }
66
+ function compare(operator, apply) {
67
+ return (state) => {
68
+ const right = popNumber(state, operator);
69
+ pushBool(state, apply(popNumber(state, operator).value, right.value));
70
+ };
71
+ }
72
+ function equal(state, operator, negate) {
73
+ const right = pop(state, operator);
74
+ const left = pop(state, operator);
75
+ let same = false;
76
+ if (left.kind === "n" && right.kind === "n") same = left.value === right.value;
77
+ else if (left.kind === "b" && right.kind === "b") same = left.value === right.value;
78
+ else calculatorRefuse(`calculator ${operator} operand type`, "two numbers or two booleans");
79
+ pushBool(state, negate ? !same : same);
80
+ }
81
+ function psRound(value) {
82
+ return value < 0 ? -Math.round(-value) : Math.round(value);
83
+ }
84
+ function logicalOrBitwise(state, operator, bools, ints) {
85
+ const right = pop(state, operator);
86
+ const left = pop(state, operator);
87
+ if (left.kind === "b" && right.kind === "b") {
88
+ pushBool(state, bools(left.value, right.value));
89
+ return;
90
+ }
91
+ if (left.kind === "n" && right.kind === "n" && left.int && right.int) {
92
+ pushNumber(state, ints(left.value, right.value) | 0, true);
93
+ return;
94
+ }
95
+ calculatorRefuse(`calculator ${operator} operand type`, "two booleans or two integers");
96
+ }
97
+ const operators = {
98
+ abs: unary("abs", (value, int) => ({
99
+ value: Math.abs(value),
100
+ int
101
+ })),
102
+ add: binary("add", (left, right, ints) => ({
103
+ value: left + right,
104
+ int: ints
105
+ })),
106
+ atan: (state) => {
107
+ const den = popNumber(state, "atan");
108
+ const num = popNumber(state, "atan");
109
+ if (num.value === 0 && den.value === 0) {
110
+ pushNumber(state, 0, false);
111
+ return;
112
+ }
113
+ let angle = Math.atan2(num.value, den.value) * 180 / Math.PI;
114
+ if (angle < 0) angle += 360;
115
+ pushNumber(state, angle, false);
116
+ },
117
+ ceiling: unary("ceiling", (value, int) => ({
118
+ value: Math.ceil(value),
119
+ int
120
+ })),
121
+ cos: unary("cos", (value) => ({
122
+ value: Math.cos(value * Math.PI / 180),
123
+ int: false
124
+ })),
125
+ cvi: unary("cvi", (value) => ({
126
+ value: Math.trunc(value),
127
+ int: true
128
+ })),
129
+ cvr: unary("cvr", (value) => ({
130
+ value,
131
+ int: false
132
+ })),
133
+ div: binary("div", (left, right) => ({
134
+ value: left / right,
135
+ int: false
136
+ })),
137
+ exp: binary("exp", (left, right) => ({
138
+ value: left ** right,
139
+ int: false
140
+ })),
141
+ floor: unary("floor", (value, int) => ({
142
+ value: Math.floor(value),
143
+ int
144
+ })),
145
+ idiv: (state) => {
146
+ const right = popInt(state, "idiv");
147
+ const left = popInt(state, "idiv");
148
+ if (right === 0) calculatorRefuse("calculator idiv divisor 0", "a non-zero divisor");
149
+ pushNumber(state, Math.trunc(left / right), true);
150
+ },
151
+ ln: unary("ln", (value) => ({
152
+ value: Math.log(value),
153
+ int: false
154
+ })),
155
+ log: unary("log", (value) => ({
156
+ value: Math.log10(value),
157
+ int: false
158
+ })),
159
+ mod: (state) => {
160
+ const right = popInt(state, "mod");
161
+ const left = popInt(state, "mod");
162
+ if (right === 0) calculatorRefuse("calculator mod divisor 0", "a non-zero divisor");
163
+ pushNumber(state, left % right, true);
164
+ },
165
+ mul: binary("mul", (left, right, ints) => ({
166
+ value: left * right,
167
+ int: ints
168
+ })),
169
+ neg: unary("neg", (value, int) => ({
170
+ value: -value,
171
+ int
172
+ })),
173
+ round: unary("round", (value, int) => ({
174
+ value: psRound(value),
175
+ int
176
+ })),
177
+ sin: unary("sin", (value) => ({
178
+ value: Math.sin(value * Math.PI / 180),
179
+ int: false
180
+ })),
181
+ sqrt: unary("sqrt", (value) => ({
182
+ value: Math.sqrt(value),
183
+ int: false
184
+ })),
185
+ sub: binary("sub", (left, right, ints) => ({
186
+ value: left - right,
187
+ int: ints
188
+ })),
189
+ truncate: unary("truncate", (value, int) => ({
190
+ value: Math.trunc(value),
191
+ int
192
+ })),
193
+ and: (state) => logicalOrBitwise(state, "and", (left, right) => left && right, (left, right) => left & right),
194
+ bitshift: (state) => {
195
+ const shift = popInt(state, "bitshift");
196
+ const bits = popInt(state, "bitshift") | 0;
197
+ if (shift >= 32 || shift <= -32) {
198
+ pushNumber(state, 0, true);
199
+ return;
200
+ }
201
+ pushNumber(state, shift >= 0 ? bits << shift | 0 : bits >>> -shift | 0, true);
202
+ },
203
+ eq: (state) => equal(state, "eq", false),
204
+ false: (state) => pushBool(state, false),
205
+ ge: compare("ge", (left, right) => left >= right),
206
+ gt: compare("gt", (left, right) => left > right),
207
+ le: compare("le", (left, right) => left <= right),
208
+ lt: compare("lt", (left, right) => left < right),
209
+ ne: (state) => equal(state, "ne", true),
210
+ not: (state) => {
211
+ const value = pop(state, "not");
212
+ if (value.kind === "b") {
213
+ pushBool(state, !value.value);
214
+ return;
215
+ }
216
+ if (value.kind === "n" && value.int) {
217
+ pushNumber(state, ~value.value | 0, true);
218
+ return;
219
+ }
220
+ calculatorRefuse("calculator not operand type", "a boolean or an integer");
221
+ },
222
+ or: (state) => logicalOrBitwise(state, "or", (left, right) => left || right, (left, right) => left | right),
223
+ true: (state) => pushBool(state, true),
224
+ xor: (state) => logicalOrBitwise(state, "xor", (left, right) => left !== right, (left, right) => left ^ right),
225
+ if: (state, exec) => {
226
+ const body = popProc(state, "if");
227
+ if (popBool(state, "if")) exec(body);
228
+ },
229
+ ifelse: (state, exec) => {
230
+ const otherwise = popProc(state, "ifelse");
231
+ const thenBody = popProc(state, "ifelse");
232
+ exec(popBool(state, "ifelse") ? thenBody : otherwise);
233
+ },
234
+ copy: (state) => {
235
+ const count = popInt(state, "copy");
236
+ if (count < 0 || state.stack.length < count) calculatorRefuse("calculator copy count", "a non-negative integer within the stack");
237
+ const copies = state.stack.slice(state.stack.length - count);
238
+ for (const value of copies) pushStack(state, value);
239
+ },
240
+ dup: (state) => {
241
+ const value = pop(state, "dup");
242
+ pushStack(state, value);
243
+ pushStack(state, value);
244
+ },
245
+ exch: (state) => {
246
+ const top = pop(state, "exch");
247
+ const below = pop(state, "exch");
248
+ pushStack(state, top);
249
+ pushStack(state, below);
250
+ },
251
+ index: (state) => {
252
+ const n = popInt(state, "index");
253
+ if (n < 0 || n >= state.stack.length) calculatorRefuse("calculator index", "a non-negative integer within the stack");
254
+ const value = state.stack[state.stack.length - 1 - n];
255
+ if (value) pushStack(state, value);
256
+ },
257
+ pop: (state) => {
258
+ pop(state, "pop");
259
+ },
260
+ roll: (state) => {
261
+ const j = popInt(state, "roll");
262
+ const n = popInt(state, "roll");
263
+ if (n < 0 || state.stack.length < n) calculatorRefuse("calculator roll count", "a non-negative integer within the stack");
264
+ if (n === 0) return;
265
+ const shift = (j % n + n) % n;
266
+ const start = state.stack.length - n;
267
+ const slice = state.stack.splice(start, n);
268
+ state.stack.push(...slice.slice(n - shift), ...slice.slice(0, n - shift));
269
+ }
270
+ };
271
+ function applyOperator(name, state, exec) {
272
+ const handler = operators[name];
273
+ if (!handler) calculatorRefuse(`calculator operator ${classifiedOperatorName(name)}`, "the PDF calculator operators");
274
+ handler(state, exec);
275
+ }
276
+ //#endregion
277
+ export { applyOperator, calculatorRefuse, classifiedOperatorName, pushStack };