@segnavia/render 0.4.0 → 0.6.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/dist/book-destinations.js +101 -0
- package/dist/book-margins.js +20 -0
- package/dist/book-pdf.js +233 -0
- package/dist/book-render.js +48 -0
- package/dist/browser-jpeg.js +42 -0
- package/dist/color/black-point.js +11 -8
- package/dist/color/engine.js +9 -2
- package/dist/color/interpolation.js +11 -1
- package/dist/color/profile.js +17 -7
- package/dist/color/tables.js +47 -13
- package/dist/color/types.js +4 -1
- package/dist/index.d.ts +5 -38
- package/dist/index.js +29 -5
- package/dist/options.d.ts +39 -0
- package/dist/press/adapter.js +117 -50
- package/dist/press/color-policy.js +27 -8
- package/dist/press/content.js +28 -20
- package/dist/press/image.js +327 -0
- package/dist/press/policy.js +27 -10
- package/dist/press/report.js +26 -14
- package/dist/press/sample-shading.js +9 -19
- package/dist/press/types.d.ts +9 -2
- package/dist/press/visitor.js +32 -30
- package/package.json +4 -3
package/dist/color/tables.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { REVERSE_LUT, checked, clamp, refuse } from "./types.js";
|
|
2
2
|
import { invert3, matrix3 } from "./spaces.js";
|
|
3
3
|
import { align4, fixed, need, signature, u16, u32 } from "./binary.js";
|
|
4
|
-
import { interpolate, matrixInput, multilinear, tetrahedral4 } from "./interpolation.js";
|
|
4
|
+
import { interpolate, linearGrid, matrixInput, multilinear, tetrahedral4 } from "./interpolation.js";
|
|
5
5
|
//#region src/color/tables.ts
|
|
6
6
|
function packed(bytes, view, offset, length, width, bounds) {
|
|
7
7
|
need(offset, length * width, bounds);
|
|
@@ -73,6 +73,16 @@ function parseCurve(bytes, view, offset, bounds) {
|
|
|
73
73
|
size: 12 + count * 4
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
+
function invertCurve(curve, target) {
|
|
77
|
+
let low = 0, high = 1;
|
|
78
|
+
const descending = curve(0) > curve(1);
|
|
79
|
+
for (let index = 0; index < 24; index++) {
|
|
80
|
+
const middle = (low + high) / 2;
|
|
81
|
+
if (descending ? curve(middle) > target : curve(middle) < target) low = middle;
|
|
82
|
+
else high = middle;
|
|
83
|
+
}
|
|
84
|
+
return (low + high) / 2;
|
|
85
|
+
}
|
|
76
86
|
function curvesAt(bytes, view, offset, count, bounds) {
|
|
77
87
|
const curves = [];
|
|
78
88
|
let cursor = offset;
|
|
@@ -132,7 +142,7 @@ function parseMft(bytes, view, record, type, inputs, outputs) {
|
|
|
132
142
|
];
|
|
133
143
|
current = matrix3(fixedMatrix, matrixInput(current));
|
|
134
144
|
}
|
|
135
|
-
current = inputs === 3 ? multilinear(grid, points, outputs, current) : tetrahedral4(grid, points, outputs, current);
|
|
145
|
+
current = inputs === 1 ? linearGrid(grid, points, outputs, current) : inputs === 3 ? multilinear(grid, points, outputs, current) : tetrahedral4(grid, points, outputs, current);
|
|
136
146
|
return current.map((value, channel) => interpolate(checked(outputTables, channel, `${type} output tables`), value));
|
|
137
147
|
}
|
|
138
148
|
};
|
|
@@ -179,7 +189,7 @@ function parseMab(bytes, view, record, type, inputs, outputs) {
|
|
|
179
189
|
2
|
|
180
190
|
].map((row) => checked(matrix, row * 3, `${type} matrix`) * x + checked(matrix, row * 3 + 1, `${type} matrix`) * y + checked(matrix, row * 3 + 2, `${type} matrix`) * z + checked(matrix, 9 + row, `${type} matrix`));
|
|
181
191
|
};
|
|
182
|
-
const clut = (values) => !grid ? [...values] : inputs === 3 ? multilinear(grid, points, outputs, values) : tetrahedral4(grid, points, outputs, values);
|
|
192
|
+
const clut = (values) => !grid ? [...values] : inputs === 1 ? linearGrid(grid, points, outputs, values) : inputs === 3 ? multilinear(grid, points, outputs, values) : tetrahedral4(grid, points, outputs, values);
|
|
183
193
|
return {
|
|
184
194
|
inputChannels: inputs,
|
|
185
195
|
outputChannels: outputs,
|
|
@@ -227,15 +237,6 @@ function matrixTables(bytes, view, records) {
|
|
|
227
237
|
checked(checked(columns, 2, "colorants"), 2, "blue")
|
|
228
238
|
];
|
|
229
239
|
const inverse = invert3(matrix);
|
|
230
|
-
const invertCurve = (curve, target) => {
|
|
231
|
-
let low = 0, high = 1;
|
|
232
|
-
for (let index = 0; index < 24; index++) {
|
|
233
|
-
const middle = (low + high) / 2;
|
|
234
|
-
if (curve(middle) < target) low = middle;
|
|
235
|
-
else high = middle;
|
|
236
|
-
}
|
|
237
|
-
return (low + high) / 2;
|
|
238
|
-
};
|
|
239
240
|
return {
|
|
240
241
|
a2b: {
|
|
241
242
|
inputChannels: 3,
|
|
@@ -251,5 +252,38 @@ function matrixTables(bytes, view, records) {
|
|
|
251
252
|
}
|
|
252
253
|
};
|
|
253
254
|
}
|
|
255
|
+
function grayTables(bytes, view, record, pcs) {
|
|
256
|
+
const curve = parseCurve(bytes, view, record.offset, record).curve;
|
|
257
|
+
const scale = 32768 / 65535;
|
|
258
|
+
return {
|
|
259
|
+
a2b: {
|
|
260
|
+
inputChannels: 1,
|
|
261
|
+
outputChannels: 3,
|
|
262
|
+
encoding: "mAB ",
|
|
263
|
+
run(input) {
|
|
264
|
+
const luminance = curve(checked(input, 0, "gray input"));
|
|
265
|
+
if (pcs === "XYZ ") return [
|
|
266
|
+
luminance * .9642 * scale,
|
|
267
|
+
luminance * scale,
|
|
268
|
+
luminance * .8249 * scale
|
|
269
|
+
];
|
|
270
|
+
return [
|
|
271
|
+
luminance,
|
|
272
|
+
128 / 255,
|
|
273
|
+
128 / 255
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
b2a: {
|
|
278
|
+
inputChannels: 3,
|
|
279
|
+
outputChannels: 1,
|
|
280
|
+
encoding: REVERSE_LUT,
|
|
281
|
+
run(input) {
|
|
282
|
+
const luminance = pcs === "XYZ " ? checked(input, 1, "gray PCS input") / scale : checked(input, 0, "gray PCS input");
|
|
283
|
+
return [invertCurve(curve, luminance)];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
254
288
|
//#endregion
|
|
255
|
-
export { matrixTables, parseCurve, parseMab, parseMft };
|
|
289
|
+
export { grayTables, invertCurve, matrixTables, parseCurve, parseMab, parseMft };
|
package/dist/color/types.js
CHANGED
|
@@ -8,6 +8,9 @@ function checked(values, index, structure) {
|
|
|
8
8
|
if (value === void 0) throw new Error(`${structure} index ${index} is outside length ${values.length}`);
|
|
9
9
|
return value;
|
|
10
10
|
}
|
|
11
|
+
function color1(values, structure) {
|
|
12
|
+
return [checked(values, 0, structure)];
|
|
13
|
+
}
|
|
11
14
|
function color4(values, structure) {
|
|
12
15
|
return [
|
|
13
16
|
checked(values, 0, structure),
|
|
@@ -18,4 +21,4 @@ function color4(values, structure) {
|
|
|
18
21
|
}
|
|
19
22
|
const clamp = (value) => Math.max(0, Math.min(1, value));
|
|
20
23
|
//#endregion
|
|
21
|
-
export { REVERSE_LUT, checked, clamp, color4, refuse };
|
|
24
|
+
export { REVERSE_LUT, checked, clamp, color1, color4, refuse };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { EffectsReport } from "./effects/types.js";
|
|
2
|
-
import { PressImage, PressReport } from "./press/types.js";
|
|
2
|
+
import { PressDestination, PressImage, PressReport } from "./press/types.js";
|
|
3
|
+
import { Font, ImageOptions, RenderLimits, RenderOptions } from "./options.js";
|
|
3
4
|
import { RenderRefusedError, RendererUnavailableError } from "./errors.js";
|
|
4
|
-
import { Deck, ImpositionInput, PageInput } from "@segnavia/format";
|
|
5
|
+
import { BookInput, Deck, ImpositionInput, PageInput } from "@segnavia/format";
|
|
5
6
|
import { PrintOptions } from "@segnavia/frame/print-document";
|
|
6
7
|
import { Page as Page$1 } from "playwright-core";
|
|
7
8
|
//#region src/index.d.ts
|
|
@@ -11,41 +12,6 @@ export declare const CONTAINER_ARGS: string[];
|
|
|
11
12
|
export declare class RenderTimeoutError extends Error {
|
|
12
13
|
name: string;
|
|
13
14
|
}
|
|
14
|
-
export interface Font {
|
|
15
|
-
family: string;
|
|
16
|
-
data: Uint8Array;
|
|
17
|
-
format: 'woff2' | 'woff' | 'truetype' | 'opentype';
|
|
18
|
-
weight?: number | string;
|
|
19
|
-
style?: 'normal' | 'italic';
|
|
20
|
-
}
|
|
21
|
-
export interface RenderLimits {
|
|
22
|
-
maxSheets?: number;
|
|
23
|
-
maxBytes?: number;
|
|
24
|
-
}
|
|
25
|
-
export interface RenderOptions {
|
|
26
|
-
css?: string;
|
|
27
|
-
fonts?: Font[];
|
|
28
|
-
limits?: RenderLimits;
|
|
29
|
-
boundMs?: number;
|
|
30
|
-
waitForPageReady?: boolean;
|
|
31
|
-
preferCssPageSize?: boolean;
|
|
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
|
-
};
|
|
42
|
-
}
|
|
43
|
-
export interface ImageOptions extends RenderOptions {
|
|
44
|
-
format?: 'png' | 'jpeg' | 'webp';
|
|
45
|
-
quality?: number[];
|
|
46
|
-
scale?: number;
|
|
47
|
-
width?: number;
|
|
48
|
-
}
|
|
49
15
|
export interface RendererOptions {
|
|
50
16
|
executablePath: string;
|
|
51
17
|
args?: string[];
|
|
@@ -53,6 +19,7 @@ export interface RendererOptions {
|
|
|
53
19
|
jsHeapMb?: number;
|
|
54
20
|
}
|
|
55
21
|
export interface Renderer {
|
|
22
|
+
bookPdf(book: BookInput, options?: RenderOptions): Promise<Uint8Array>;
|
|
56
23
|
pagePdf(page: PageInput, options?: RenderOptions): Promise<Uint8Array>;
|
|
57
24
|
pagesPdf(pages: PageInput[], options?: RenderOptions & {
|
|
58
25
|
imposition?: ImpositionInput;
|
|
@@ -66,4 +33,4 @@ export declare function fontFaces(fonts?: Font[]): string;
|
|
|
66
33
|
export declare function captureImage(tab: Page$1, options: Pick<ImageOptions, 'format' | 'quality' | 'limits'>, fullPage: boolean): Promise<Uint8Array>;
|
|
67
34
|
export declare function openRenderer(options: RendererOptions): Promise<Renderer>;
|
|
68
35
|
//#endregion
|
|
69
|
-
export { type EffectsReport, type PressImage, type PressReport, RenderRefusedError, RendererUnavailableError };
|
|
36
|
+
export { type EffectsReport, type Font, type ImageOptions, type PressDestination, type PressImage, type PressReport, type RenderLimits, type RenderOptions, RenderRefusedError, RendererUnavailableError };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { RenderRefusedError, RendererUnavailableError } from "./errors.js";
|
|
2
|
+
import { renderBrowserBookPdf } from "./book-render.js";
|
|
3
|
+
import { browserJpegDecoder } from "./browser-jpeg.js";
|
|
2
4
|
import { containFixedPageOverflow } from "./overflow-containment.js";
|
|
3
5
|
import { renderDeckPdf } from "./deck-pdf.js";
|
|
6
|
+
import { assertNeutralTolerance } from "./press/color-policy.js";
|
|
4
7
|
import { rewriteEffectsPdf } from "./effects/adapter.js";
|
|
5
8
|
import { assertPrintOptions, printCss, printPage, printViewport } from "./page-paper.js";
|
|
6
9
|
import { renderPagesPdf } from "./pages-pdf.js";
|
|
@@ -53,7 +56,7 @@ async function enforce(pdf, limits) {
|
|
|
53
56
|
}
|
|
54
57
|
return pdf;
|
|
55
58
|
}
|
|
56
|
-
async function finishPdf(pdf, options) {
|
|
59
|
+
async function finishPdf(pdf, options, decodeJpeg) {
|
|
57
60
|
if (options.limits?.maxBytes !== void 0 && pdf.byteLength > options.limits.maxBytes) throw new RenderRefusedError(`The PDF is ${pdf.byteLength} bytes; the limit is ${options.limits.maxBytes}.`);
|
|
58
61
|
let output = pdf;
|
|
59
62
|
if (options.effects !== false) {
|
|
@@ -62,7 +65,9 @@ async function finishPdf(pdf, options) {
|
|
|
62
65
|
options.effects?.report?.(rewritten.report);
|
|
63
66
|
}
|
|
64
67
|
if (options.press) {
|
|
65
|
-
const
|
|
68
|
+
const neutralTolerance = options.press.neutralTolerance ?? 3;
|
|
69
|
+
assertNeutralTolerance(neutralTolerance);
|
|
70
|
+
const converted = await rewritePressPdf(output, options.press.profile, neutralTolerance, decodeJpeg);
|
|
66
71
|
output = converted.bytes;
|
|
67
72
|
options.press.report?.(converted.report);
|
|
68
73
|
}
|
|
@@ -201,6 +206,18 @@ async function openRenderer(options) {
|
|
|
201
206
|
reportHeight: false
|
|
202
207
|
});
|
|
203
208
|
}
|
|
209
|
+
async function finishWithBrowser(pdf, options) {
|
|
210
|
+
if (!options.press || new TextDecoder().decode(options.press.profile.subarray(16, 20)) !== "GRAY") return finishPdf(pdf, options);
|
|
211
|
+
return withTab({
|
|
212
|
+
viewport: {
|
|
213
|
+
width: 1,
|
|
214
|
+
height: 1
|
|
215
|
+
},
|
|
216
|
+
scripts: true,
|
|
217
|
+
html: "<!doctype html>",
|
|
218
|
+
waitForReady: false
|
|
219
|
+
}, (tab) => finishPdf(pdf, options, browserJpegDecoder(tab)));
|
|
220
|
+
}
|
|
204
221
|
function pageViewport(page, width, printing = false) {
|
|
205
222
|
if (page.print) return printViewport(page.print, printing);
|
|
206
223
|
if (page.size === "fluid") return {
|
|
@@ -267,8 +284,15 @@ async function openRenderer(options) {
|
|
|
267
284
|
}));
|
|
268
285
|
}
|
|
269
286
|
return {
|
|
287
|
+
bookPdf(input, opts = {}) {
|
|
288
|
+
return renderBrowserBookPdf(input, opts, {
|
|
289
|
+
render: (setup, pdf) => withTab(setup, (tab) => tab.pdf(pdf)),
|
|
290
|
+
fontFaces: (options) => fontFaces(options.fonts),
|
|
291
|
+
finish: finishWithBrowser
|
|
292
|
+
});
|
|
293
|
+
},
|
|
270
294
|
async pagePdf(input, opts = {}) {
|
|
271
|
-
return
|
|
295
|
+
return finishWithBrowser(await renderPagePdf(Page.parse(input), opts), opts);
|
|
272
296
|
},
|
|
273
297
|
async pageImage(input, opts = {}) {
|
|
274
298
|
const page = Page.parse(input);
|
|
@@ -292,7 +316,7 @@ async function openRenderer(options) {
|
|
|
292
316
|
pagePdf: renderPagePdf,
|
|
293
317
|
piecePdf: renderPiecePdf,
|
|
294
318
|
fontFaces: (options) => fontFaces(options.fonts),
|
|
295
|
-
finish:
|
|
319
|
+
finish: finishWithBrowser
|
|
296
320
|
});
|
|
297
321
|
},
|
|
298
322
|
deckPdf(deck, opts = {}) {
|
|
@@ -302,7 +326,7 @@ async function openRenderer(options) {
|
|
|
302
326
|
waitForReady: false
|
|
303
327
|
}, (tab) => tab.pdf(pdf)),
|
|
304
328
|
fontFaces: (options) => fontFaces(options.fonts),
|
|
305
|
-
finish:
|
|
329
|
+
finish: finishWithBrowser
|
|
306
330
|
});
|
|
307
331
|
},
|
|
308
332
|
async slideImage(deck, index, opts = {}) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { EffectsReport } from "./effects/types.js";
|
|
2
|
+
import { PressReport } from "./press/types.js";
|
|
3
|
+
import { Page } from "playwright-core";
|
|
4
|
+
//#region src/options.d.ts
|
|
5
|
+
export interface Font {
|
|
6
|
+
family: string;
|
|
7
|
+
data: Uint8Array;
|
|
8
|
+
format: 'woff2' | 'woff' | 'truetype' | 'opentype';
|
|
9
|
+
weight?: number | string;
|
|
10
|
+
style?: 'normal' | 'italic';
|
|
11
|
+
}
|
|
12
|
+
export interface RenderLimits {
|
|
13
|
+
maxSheets?: number;
|
|
14
|
+
maxBytes?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface RenderOptions {
|
|
17
|
+
css?: string;
|
|
18
|
+
fonts?: Font[];
|
|
19
|
+
limits?: RenderLimits;
|
|
20
|
+
boundMs?: number;
|
|
21
|
+
waitForPageReady?: boolean;
|
|
22
|
+
preferCssPageSize?: boolean;
|
|
23
|
+
beforeOutput?: (tab: Page) => Promise<void>;
|
|
24
|
+
effects?: false | {
|
|
25
|
+
report?: (report: EffectsReport) => void;
|
|
26
|
+
};
|
|
27
|
+
press?: {
|
|
28
|
+
profile: Uint8Array;
|
|
29
|
+
neutralTolerance?: number;
|
|
30
|
+
report?: (report: PressReport) => void;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface ImageOptions extends RenderOptions {
|
|
34
|
+
format?: 'png' | 'jpeg' | 'webp';
|
|
35
|
+
quality?: number[];
|
|
36
|
+
scale?: number;
|
|
37
|
+
width?: number;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
package/dist/press/adapter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RenderRefusedError } from "../errors.js";
|
|
2
|
-
import { cachedPressColor,
|
|
2
|
+
import { cachedPressColor, destinationColor, newlySnapsToNeutral, pdfMaskLuminosity, pressDestination } from "./color-policy.js";
|
|
3
3
|
import { groupAction, shadingAction } from "./policy.js";
|
|
4
4
|
import { rewriteContent } from "./content.js";
|
|
5
5
|
import { arrayNumbers, decodeContent, dictionaryOf, nameOf, numberOf, replaceArray, resolvedColorSpace } from "./pdf-objects.js";
|
|
@@ -7,6 +7,7 @@ import { walkPressGraph } from "./visitor.js";
|
|
|
7
7
|
import "../effects/types.js";
|
|
8
8
|
import { replaceType1Function, sampleDomainForUses, shadingDeviceMatrix, shadingDomainOf, shadingSampleGridForUses } from "./sample-shading.js";
|
|
9
9
|
import { readProfile } from "../color/profile.js";
|
|
10
|
+
import { convertSuppliedImage, convertedImageCount, createImageState } from "./image.js";
|
|
10
11
|
import { createPaintedBoundsTracker, recordPatternPaintedBounds } from "./pattern-bounds.js";
|
|
11
12
|
import { inspectPressPdf } from "./report.js";
|
|
12
13
|
import { PDFDict, PDFDocument, PDFHexString, PDFName, PDFRef, PDFString } from "pdf-lib";
|
|
@@ -17,12 +18,14 @@ const refuse = (found, supported) => {
|
|
|
17
18
|
throw new RenderRefusedError(`The PDF has unsupported ${found}; supported: ${supported}.`);
|
|
18
19
|
};
|
|
19
20
|
const resolved = (state, object) => object instanceof PDFRef ? state.document.context.lookup(object) : object;
|
|
20
|
-
|
|
21
|
+
const isGrayMask = (state, mask) => state.destination === "gray" && mask !== "none";
|
|
22
|
+
const converterFor = (state, mask) => isGrayMask(state, mask) ? pdfMaskLuminosity : state.convert;
|
|
23
|
+
function convertFunction(state, fn, policy, mask) {
|
|
21
24
|
if (policy.action === "refuse") refuse(policy.found, policy.supported);
|
|
22
25
|
if (!fn) return;
|
|
23
26
|
if (numberOf(fn, "FunctionType") === 2) {
|
|
24
|
-
for (const name of ["C0", "C1"]) replaceArray(fn, name, state
|
|
25
|
-
if (fn.has(key("Range"))) replaceArray(fn, "Range", [
|
|
27
|
+
for (const name of ["C0", "C1"]) replaceArray(fn, name, converterFor(state, mask)(arrayNumbers(fn, name, 3)));
|
|
28
|
+
if (fn.has(key("Range"))) replaceArray(fn, "Range", state.destination === "gray" ? [0, 1] : [
|
|
26
29
|
0,
|
|
27
30
|
1,
|
|
28
31
|
0,
|
|
@@ -35,7 +38,7 @@ function convertFunction(state, fn, policy) {
|
|
|
35
38
|
state.totals.gradientStops += 2;
|
|
36
39
|
return;
|
|
37
40
|
}
|
|
38
|
-
if (fn.has(key("Range"))) replaceArray(fn, "Range", [
|
|
41
|
+
if (fn.has(key("Range"))) replaceArray(fn, "Range", state.destination === "gray" ? [0, 1] : [
|
|
39
42
|
0,
|
|
40
43
|
1,
|
|
41
44
|
0,
|
|
@@ -72,78 +75,81 @@ function shadingUses(state, shading, pattern) {
|
|
|
72
75
|
});
|
|
73
76
|
return uses.filter((use) => use.painted !== void 0);
|
|
74
77
|
}
|
|
75
|
-
function convertType1Shading(state, shading, pattern) {
|
|
78
|
+
function convertType1Shading(state, shading, pattern, mask) {
|
|
76
79
|
const uses = shadingUses(state, shading, pattern);
|
|
77
80
|
const domain = sampleDomainForUses(shadingDomainOf(shading), uses);
|
|
78
81
|
const matrices = uses.length > 0 ? uses.map((use) => use.matrix) : [shadingDeviceMatrix(shading, pattern)];
|
|
79
82
|
const size = shadingSampleGridForUses(domain, matrices);
|
|
80
|
-
replaceType1Function(state.document, shading, pattern, (
|
|
83
|
+
replaceType1Function(state.document, shading, pattern, isGrayMask(state, mask) ? pdfMaskLuminosity : state.sampleConvert, state.budget, void 0, domain, size, state.destination === "gray" ? 1 : 4);
|
|
81
84
|
}
|
|
82
|
-
function convertShading(state, shading, pattern) {
|
|
85
|
+
function convertShading(state, shading, pattern, mask) {
|
|
83
86
|
if (state.seenShadings.has(shading)) return false;
|
|
84
87
|
state.seenShadings.add(shading);
|
|
85
88
|
const type = numberOf(shading, "ShadingType");
|
|
86
89
|
const functionType = functionTypeOf(state, shading);
|
|
87
90
|
const space = resolvedColorSpace(state.document, resolved(state, shading.get(key("ColorSpace"))));
|
|
88
|
-
const
|
|
91
|
+
const destination = state.destination;
|
|
92
|
+
const policy = shadingAction(space.kind, type, functionType, destination);
|
|
89
93
|
if (policy.action === "refuse") {
|
|
90
94
|
if (type === 1) refuse(`ShadingType 1 FunctionType ${functionType ?? "missing"}`, policy.supported);
|
|
91
95
|
refuse(`ShadingType ${type ?? "missing"} color space ${space.label}`, policy.supported);
|
|
92
96
|
}
|
|
93
97
|
if (policy.action === "keep") return false;
|
|
94
|
-
if (shading.has(key("Background"))) replaceArray(shading, "Background", state
|
|
95
|
-
shading.set(key("ColorSpace"), key("DeviceCMYK"));
|
|
98
|
+
if (shading.has(key("Background"))) replaceArray(shading, "Background", converterFor(state, mask)(arrayNumbers(shading, "Background", 3)));
|
|
99
|
+
shading.set(key("ColorSpace"), key(destination === "gray" ? "DeviceGray" : "DeviceCMYK"));
|
|
96
100
|
if (type === 1) {
|
|
97
|
-
convertType1Shading(state, shading, pattern);
|
|
101
|
+
convertType1Shading(state, shading, pattern, mask);
|
|
98
102
|
return false;
|
|
99
103
|
}
|
|
100
104
|
return true;
|
|
101
105
|
}
|
|
102
|
-
function convertGroup(state, owner,
|
|
106
|
+
function convertGroup(state, owner, mask) {
|
|
103
107
|
const group = owner.lookupMaybe(key("Group"), PDFDict);
|
|
104
108
|
if (!group || nameOf(group.get(key("S"))) !== "Transparency" || state.seenGroups.has(group)) return;
|
|
105
109
|
state.seenGroups.add(group);
|
|
106
110
|
const space = resolvedColorSpace(state.document, resolved(state, group.get(key("CS"))));
|
|
107
|
-
const
|
|
111
|
+
const destination = state.destination;
|
|
112
|
+
const policy = groupAction(space.kind, space.label, mask !== "none", destination);
|
|
108
113
|
if (policy.action === "refuse") refuse(`transparency group color space ${space.label}`, policy.supported);
|
|
109
114
|
if (policy.action === "convert") {
|
|
110
|
-
group.set(key("CS"), key("DeviceCMYK"));
|
|
115
|
+
group.set(key("CS"), key(destination === "gray" ? "DeviceGray" : "DeviceCMYK"));
|
|
111
116
|
state.totals.groups++;
|
|
112
117
|
}
|
|
113
118
|
}
|
|
114
|
-
function convertRuleCImage(state, image, width, height, dictionary) {
|
|
119
|
+
function convertRuleCImage(state, image, width, height, dictionary, convert) {
|
|
115
120
|
const source = decodeContent(image, state.budget);
|
|
116
121
|
if (source.length !== width * height * 3) refuse("effect image sample length", "three 8-bit RGB samples per pixel");
|
|
117
|
-
const
|
|
122
|
+
const channels = state.destination === "gray" ? 1 : 4;
|
|
123
|
+
const converted = new Uint8Array(width * height * channels);
|
|
118
124
|
for (let pixel = 0; pixel < width * height; pixel++) {
|
|
119
|
-
const color =
|
|
125
|
+
const color = convert([
|
|
120
126
|
(source[pixel * 3] ?? 0) / 255,
|
|
121
127
|
(source[pixel * 3 + 1] ?? 0) / 255,
|
|
122
128
|
(source[pixel * 3 + 2] ?? 0) / 255
|
|
123
129
|
]);
|
|
124
|
-
for (let channel = 0; channel <
|
|
130
|
+
for (let channel = 0; channel < channels; channel++) converted[pixel * channels + channel] = Math.round(Math.max(0, Math.min(1, color[channel] ?? 0)) * 255);
|
|
125
131
|
}
|
|
126
|
-
dictionary.ColorSpace = key("DeviceCMYK");
|
|
132
|
+
dictionary.ColorSpace = key(state.destination === "gray" ? "DeviceGray" : "DeviceCMYK");
|
|
127
133
|
const replacement = state.document.context.stream(deflateSync(converted), dictionary);
|
|
128
134
|
const reference = state.document.context.getObjectRef(image);
|
|
129
135
|
if (reference) state.document.context.assign(reference, replacement);
|
|
130
136
|
}
|
|
131
|
-
function convertEffectImage(state, image) {
|
|
137
|
+
function convertEffectImage(state, image, mask) {
|
|
132
138
|
if (!image.dict.has(key("SegnaviaGeneratedEffect")) || resolvedColorSpace(state.document, resolved(state, image.dict.get(key("ColorSpace")))).kind !== "rgb") return;
|
|
133
139
|
const width = numberOf(image.dict, "Width") ?? refuse("effect image Width missing", "a positive Width number");
|
|
134
140
|
const height = numberOf(image.dict, "Height") ?? refuse("effect image Height missing", "a positive Height number");
|
|
135
141
|
if (width <= 0 || height <= 0) refuse("effect image dimensions", "positive Width and Height numbers");
|
|
136
142
|
const dictionary = streamDictionary(image);
|
|
143
|
+
const convert = converterFor(state, mask);
|
|
137
144
|
if (!image.dict.has(key("SegnaviaEffectColor"))) {
|
|
138
|
-
convertRuleCImage(state, image, width, height, dictionary);
|
|
145
|
+
convertRuleCImage(state, image, width, height, dictionary, convert);
|
|
139
146
|
return;
|
|
140
147
|
}
|
|
141
|
-
const
|
|
142
|
-
const color = state.convert(source);
|
|
148
|
+
const color = convert(arrayNumbers(image.dict, "SegnaviaEffectColor", 3));
|
|
143
149
|
const palette = color.map((value) => Math.round(Math.max(0, Math.min(1, value)) * 255));
|
|
144
150
|
dictionary.ColorSpace = state.document.context.obj([
|
|
145
151
|
"Indexed",
|
|
146
|
-
"DeviceCMYK",
|
|
152
|
+
state.destination === "gray" ? "DeviceGray" : "DeviceCMYK",
|
|
147
153
|
0,
|
|
148
154
|
PDFHexString.of(Buffer.from(palette).toString("hex"))
|
|
149
155
|
]);
|
|
@@ -185,9 +191,10 @@ function recordContentBounds(state, stream, resources, budget) {
|
|
|
185
191
|
if (state.sequence) state.sequence.feed(bytes, resolve, resolveGs, resolveShading);
|
|
186
192
|
else recordPatternPaintedBounds(bytes, resolve, state.painted, resolveGs, resolveShading, state.shadingPainted);
|
|
187
193
|
}
|
|
188
|
-
function convertContent(state, object, stream, setDirect) {
|
|
194
|
+
function convertContent(state, object, stream, setDirect, mask) {
|
|
189
195
|
const bytes = decodeContent(stream, state.budget);
|
|
190
|
-
const
|
|
196
|
+
const destination = state.destination;
|
|
197
|
+
const rewritten = rewriteContent(bytes, converterFor(state, mask), destination);
|
|
191
198
|
state.totals.fills += rewritten.counts.fills;
|
|
192
199
|
state.totals.strokes += rewritten.counts.strokes;
|
|
193
200
|
const replacement = state.document.context.stream(deflateSync(rewritten.bytes), streamDictionary(stream));
|
|
@@ -195,37 +202,50 @@ function convertContent(state, object, stream, setDirect) {
|
|
|
195
202
|
if (reference) state.document.context.assign(reference, replacement);
|
|
196
203
|
else setDirect(replacement);
|
|
197
204
|
}
|
|
198
|
-
function newState(document, profile) {
|
|
205
|
+
function newState(document, profile, tolerance) {
|
|
206
|
+
const cached = cachedPressColor(profile, tolerance);
|
|
207
|
+
const totals = {
|
|
208
|
+
fills: 0,
|
|
209
|
+
strokes: 0,
|
|
210
|
+
gradientStops: 0,
|
|
211
|
+
groups: 0,
|
|
212
|
+
neutralColors: 0
|
|
213
|
+
};
|
|
199
214
|
return {
|
|
200
215
|
budget: {
|
|
201
216
|
calculatorOperations: 0,
|
|
202
217
|
decodedBytes: 0,
|
|
203
218
|
shadingSamples: 0
|
|
204
219
|
},
|
|
205
|
-
convert:
|
|
220
|
+
convert: (rgb) => {
|
|
221
|
+
if (profile.colorSpace === "CMYK" && newlySnapsToNeutral(rgb, tolerance)) totals.neutralColors++;
|
|
222
|
+
return cached(rgb);
|
|
223
|
+
},
|
|
224
|
+
destination: pressDestination(profile),
|
|
206
225
|
document,
|
|
207
226
|
painted: /* @__PURE__ */ new WeakMap(),
|
|
208
227
|
profile,
|
|
228
|
+
sampleConvert: (rgb) => {
|
|
229
|
+
if (profile.colorSpace === "CMYK" && newlySnapsToNeutral(rgb, tolerance)) totals.neutralColors++;
|
|
230
|
+
return destinationColor(profile, rgb, tolerance);
|
|
231
|
+
},
|
|
209
232
|
seenGroups: /* @__PURE__ */ new WeakSet(),
|
|
210
233
|
seenShadings: /* @__PURE__ */ new WeakSet(),
|
|
211
234
|
shadingPainted: /* @__PURE__ */ new WeakMap(),
|
|
212
235
|
shadingPatterns: /* @__PURE__ */ new WeakMap(),
|
|
213
|
-
totals
|
|
214
|
-
fills: 0,
|
|
215
|
-
strokes: 0,
|
|
216
|
-
gradientStops: 0,
|
|
217
|
-
groups: 0
|
|
218
|
-
}
|
|
236
|
+
totals
|
|
219
237
|
};
|
|
220
238
|
}
|
|
221
|
-
function rewritePressDocument(document, profile) {
|
|
222
|
-
const state = newState(document, profile);
|
|
239
|
+
function rewritePressDocument(document, profile, tolerance = 3) {
|
|
240
|
+
const state = newState(document, profile, tolerance);
|
|
223
241
|
const boundsBudget = { decodedBytes: 0 };
|
|
224
242
|
walkPressGraph(document, {
|
|
225
243
|
beginContentsSequence: () => {
|
|
226
244
|
state.sequence = createPaintedBoundsTracker(state.painted, state.shadingPainted);
|
|
227
245
|
},
|
|
228
|
-
content: (_object, stream, _replace, resources) =>
|
|
246
|
+
content: (_object, stream, _replace, resources) => {
|
|
247
|
+
recordContentBounds(state, stream, resources, boundsBudget);
|
|
248
|
+
},
|
|
229
249
|
endContentsSequence: () => {
|
|
230
250
|
state.sequence = void 0;
|
|
231
251
|
},
|
|
@@ -236,16 +256,16 @@ function rewritePressDocument(document, profile) {
|
|
|
236
256
|
shading: (shading, pattern) => indexShadingPattern(state, shading, pattern)
|
|
237
257
|
});
|
|
238
258
|
walkPressGraph(document, {
|
|
239
|
-
content: (object, stream, replace) => convertContent(state, object, stream, replace),
|
|
240
|
-
function: (fn, decision) => convertFunction(state, fn, decision),
|
|
241
|
-
group: (owner, _stream,
|
|
242
|
-
image: (image) => convertEffectImage(state, image),
|
|
243
|
-
shading: (shading, pattern) => convertShading(state, shading, pattern)
|
|
259
|
+
content: (object, stream, replace, _resources, mask) => convertContent(state, object, stream, replace, mask),
|
|
260
|
+
function: (fn, decision, mask) => convertFunction(state, fn, decision, mask),
|
|
261
|
+
group: (owner, _stream, mask) => convertGroup(state, owner, mask),
|
|
262
|
+
image: (image, mask) => convertEffectImage(state, image, mask),
|
|
263
|
+
shading: (shading, pattern, mask) => convertShading(state, shading, pattern, mask)
|
|
244
264
|
});
|
|
245
265
|
return state.totals;
|
|
246
266
|
}
|
|
247
|
-
function addOutputIntent(document, profile) {
|
|
248
|
-
const profileRef = document.context.register(document.context.stream(profile, { N:
|
|
267
|
+
function addOutputIntent(document, profile, channels) {
|
|
268
|
+
const profileRef = document.context.register(document.context.stream(profile, { N: channels }));
|
|
249
269
|
const intentRef = document.context.register(document.context.obj({
|
|
250
270
|
Type: "OutputIntent",
|
|
251
271
|
S: "GTS_PDFX",
|
|
@@ -254,17 +274,64 @@ function addOutputIntent(document, profile) {
|
|
|
254
274
|
}));
|
|
255
275
|
document.catalog.set(key("OutputIntents"), document.context.obj([intentRef]));
|
|
256
276
|
}
|
|
257
|
-
|
|
277
|
+
function replaceSourceImage(document, image, uses, replacement) {
|
|
278
|
+
const reference = document.context.getObjectRef(image);
|
|
279
|
+
if (reference) document.context.assign(reference, replacement);
|
|
280
|
+
else for (const use of uses) use.replace(replacement);
|
|
281
|
+
}
|
|
282
|
+
async function convertImageUses(state, image, uses) {
|
|
283
|
+
const normal = uses.filter((use) => use.mask === "none");
|
|
284
|
+
const masks = uses.filter((use) => use.mask !== "none");
|
|
285
|
+
if (normal.length > 0) {
|
|
286
|
+
const replacement = await convertSuppliedImage(state, image, "none");
|
|
287
|
+
if (!replacement) return;
|
|
288
|
+
replaceSourceImage(state.document, image, normal, replacement);
|
|
289
|
+
}
|
|
290
|
+
if (masks.length === 0) return;
|
|
291
|
+
const replacement = await convertSuppliedImage(state, image, masks[0]?.mask ?? "luminosity");
|
|
292
|
+
if (!replacement) return;
|
|
293
|
+
if (normal.length === 0) replaceSourceImage(state.document, image, masks, replacement);
|
|
294
|
+
else {
|
|
295
|
+
const reference = state.document.context.getObjectRef(replacement) ?? state.document.context.register(replacement);
|
|
296
|
+
for (const use of masks) use.replace(reference);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function convertGrayImages(document, profile, decodeJpeg) {
|
|
300
|
+
const state = createImageState(document, profile, decodeJpeg);
|
|
301
|
+
const images = [];
|
|
302
|
+
walkPressGraph(document, {
|
|
303
|
+
content: () => void 0,
|
|
304
|
+
function: () => void 0,
|
|
305
|
+
group: () => void 0,
|
|
306
|
+
image: (image, mask, replace) => images.push({
|
|
307
|
+
image,
|
|
308
|
+
mask,
|
|
309
|
+
replace
|
|
310
|
+
}),
|
|
311
|
+
shading: () => false
|
|
312
|
+
});
|
|
313
|
+
const uses = /* @__PURE__ */ new Map();
|
|
314
|
+
for (const found of images) uses.set(found.image, [...uses.get(found.image) ?? [], found]);
|
|
315
|
+
for (const [image, found] of uses) await convertImageUses(state, image, found);
|
|
316
|
+
return convertedImageCount(state);
|
|
317
|
+
}
|
|
318
|
+
async function rewritePressPdf(bytes, profileBytes, tolerance = 3, decodeJpeg) {
|
|
258
319
|
try {
|
|
259
320
|
const profile = readProfile(profileBytes);
|
|
260
|
-
if (profile.colorSpace !== "CMYK") refuse(`press profile color space ${profile.colorSpace}`, "CMYK");
|
|
321
|
+
if (profile.colorSpace !== "CMYK" && profile.colorSpace !== "GRAY") refuse(`press profile color space ${profile.colorSpace}`, "CMYK or GRAY");
|
|
261
322
|
const document = await PDFDocument.load(bytes, { updateMetadata: false });
|
|
262
|
-
rewritePressDocument(document, profile);
|
|
263
|
-
|
|
323
|
+
const counts = rewritePressDocument(document, profile, tolerance);
|
|
324
|
+
let convertedImages = 0;
|
|
325
|
+
const destination = pressDestination(profile);
|
|
326
|
+
if (destination === "gray") convertedImages = await convertGrayImages(document, profile, decodeJpeg);
|
|
327
|
+
addOutputIntent(document, profileBytes, destination === "gray" ? 1 : 4);
|
|
264
328
|
const output = await document.save({ useObjectStreams: false });
|
|
329
|
+
const report = await inspectPressPdf(output, destination);
|
|
330
|
+
report.neutralColors = counts.neutralColors;
|
|
331
|
+
report.convertedImages = convertedImages;
|
|
265
332
|
return {
|
|
266
333
|
bytes: output,
|
|
267
|
-
report
|
|
334
|
+
report
|
|
268
335
|
};
|
|
269
336
|
} catch (error) {
|
|
270
337
|
if (error instanceof RenderRefusedError) throw error;
|