@segnavia/render 0.5.0 → 0.7.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-pdf.js +15 -8
- package/dist/book-render.js +3 -2
- 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 +3 -3
- package/dist/index.js +42 -14
- package/dist/options.d.ts +34 -0
- package/dist/overflow.js +123 -0
- package/dist/page-paper.js +12 -7
- package/dist/pages-pdf.js +7 -4
- package/dist/press/adapter.js +97 -42
- package/dist/press/color-policy.js +12 -3
- 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 +24 -13
- package/dist/press/sample-shading.js +9 -19
- package/dist/press/types.d.ts +7 -2
- package/dist/press/visitor.js +32 -30
- package/package.json +4 -4
package/dist/book-pdf.js
CHANGED
|
@@ -5,10 +5,11 @@ import { Book, formatNumber, resolveDirection, sheetMm } from "@segnavia/format"
|
|
|
5
5
|
import { PDFDocument } from "pdf-lib";
|
|
6
6
|
import { bookDocument, bookPrintPart } from "@segnavia/html";
|
|
7
7
|
//#region src/book-pdf.ts
|
|
8
|
-
async function inspect(
|
|
9
|
-
const document = await PDFDocument.load(bytes);
|
|
8
|
+
async function inspect(output) {
|
|
9
|
+
const document = await PDFDocument.load(output.bytes);
|
|
10
10
|
return {
|
|
11
|
-
bytes,
|
|
11
|
+
bytes: output.bytes,
|
|
12
|
+
overflow: output.overflow,
|
|
12
13
|
pages: document.getPageCount(),
|
|
13
14
|
destinations: namedDestinations(document)
|
|
14
15
|
};
|
|
@@ -127,7 +128,7 @@ async function printBody(book, options, adapter) {
|
|
|
127
128
|
targets: true
|
|
128
129
|
});
|
|
129
130
|
if (firstDocument.issues.length > 0) throw new RenderRefusedError(`The book cannot be printed: ${firstDocument.issues.join("; ")}.`);
|
|
130
|
-
let part = await inspect(await adapter.print(firstDocument.html, book, options));
|
|
131
|
+
let part = await inspect(await adapter.print(firstDocument.html, book, options, "body"));
|
|
131
132
|
requireTargets(part, hashTargets(firstDocument));
|
|
132
133
|
const plan = book.rectoOpenings ? planBlanks(firstDocument.sections, part.destinations) : {
|
|
133
134
|
ids: [],
|
|
@@ -139,7 +140,7 @@ async function printBody(book, options, adapter) {
|
|
|
139
140
|
targets: true,
|
|
140
141
|
blankBefore: plan.ids
|
|
141
142
|
});
|
|
142
|
-
part = await inspect(await adapter.print(finalDocument.html, book, options));
|
|
143
|
+
part = await inspect(await adapter.print(finalDocument.html, book, options, "body"));
|
|
143
144
|
requireTargets(part, hashTargets(finalDocument));
|
|
144
145
|
verifyStarts("body", sectionsFor(firstDocument.sections, "body"), plan.predicted, part.destinations);
|
|
145
146
|
}
|
|
@@ -169,7 +170,7 @@ async function printFront(book, source, body, options, adapter) {
|
|
|
169
170
|
crossLinks: true,
|
|
170
171
|
pageNumbers: numbers
|
|
171
172
|
});
|
|
172
|
-
const printed = await inspect(await adapter.print(document.html, book, options));
|
|
173
|
+
const printed = await inspect(await adapter.print(document.html, book, options, "front"));
|
|
173
174
|
if (ids.length === 0) ids.push(...hashTargets(document));
|
|
174
175
|
requireTargets(printed, ids);
|
|
175
176
|
prints.push(printed);
|
|
@@ -219,8 +220,14 @@ async function renderBookPdf(input, options, adapter) {
|
|
|
219
220
|
const bodyBlankPages = new Set(printedBody.blankBefore.map((id) => (printedBody.part.destinations.get(id)?.page ?? 1) - 1));
|
|
220
221
|
const frontBlankPages = new Set(printedFront.trailingBlank ? [front.pages] : []);
|
|
221
222
|
const marginPages = [...marginsFor(book, sectionsFor(printedBody.document.sections, "front"), front.destinations, front.pages, 0, frontBlankPages, book.numbering.frontMatter), ...marginsFor(book, sectionsFor(printedBody.document.sections, "body"), printedBody.part.destinations, printedBody.part.pages, front.pages, bodyBlankPages, book.numbering.body)];
|
|
222
|
-
const
|
|
223
|
-
|
|
223
|
+
const margin = await adapter.print(marginDocument(book, marginPages), book, options, "margin");
|
|
224
|
+
const output = await adapter.finish(await join(front, printedBody.part, margin.bytes), options);
|
|
225
|
+
options.overflow?.report?.({ entries: [
|
|
226
|
+
front.overflow,
|
|
227
|
+
printedBody.part.overflow,
|
|
228
|
+
margin.overflow
|
|
229
|
+
].filter((entry) => entry !== void 0) });
|
|
230
|
+
return output;
|
|
224
231
|
}
|
|
225
232
|
function bookPageCss(book, marginLayer = false) {
|
|
226
233
|
if (!book.page) return "";
|
package/dist/book-render.js
CHANGED
|
@@ -6,9 +6,10 @@ import { frameDocument, newNonce } from "@segnavia/frame/bridge";
|
|
|
6
6
|
//#region src/book-render.ts
|
|
7
7
|
function renderBrowserBookPdf(input, options, dependencies) {
|
|
8
8
|
return renderBookPdf(input, options, {
|
|
9
|
-
print: (html, book, renderOptions,
|
|
9
|
+
print: (html, book, renderOptions, part) => {
|
|
10
10
|
if (!book.page) throw new RenderRefusedError("Printing a continuous book needs page; set page before calling bookPdf.");
|
|
11
11
|
const size = sheetMm(book.page);
|
|
12
|
+
const marginLayer = part === "margin";
|
|
12
13
|
const css = `${bookPageCss(book, marginLayer)}${marginLayer ? marginCss(book) : ""}${dependencies.fontFaces(renderOptions)}${renderOptions.css ?? ""}`;
|
|
13
14
|
return dependencies.render({
|
|
14
15
|
viewport: {
|
|
@@ -39,7 +40,7 @@ function renderBrowserBookPdf(input, options, dependencies) {
|
|
|
39
40
|
bottom: "0mm",
|
|
40
41
|
left: "0mm"
|
|
41
42
|
}
|
|
42
|
-
});
|
|
43
|
+
}, Math.round(size.width * PX_PER_MM), part);
|
|
43
44
|
},
|
|
44
45
|
finish: dependencies.finish
|
|
45
46
|
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/browser-jpeg.ts
|
|
2
|
+
function browserJpegDecoder(tab) {
|
|
3
|
+
return async (bytes) => {
|
|
4
|
+
const encoded = Buffer.from(bytes).toString("base64");
|
|
5
|
+
const decoded = await tab.evaluate(async (base64) => {
|
|
6
|
+
const binary = atob(base64);
|
|
7
|
+
const source = Uint8Array.from(binary, (value) => value.charCodeAt(0));
|
|
8
|
+
const bitmap = await createImageBitmap(new Blob([source], { type: "image/jpeg" }), {
|
|
9
|
+
colorSpaceConversion: "none",
|
|
10
|
+
premultiplyAlpha: "none"
|
|
11
|
+
});
|
|
12
|
+
try {
|
|
13
|
+
const context = new OffscreenCanvas(bitmap.width, bitmap.height).getContext("2d");
|
|
14
|
+
if (!context) throw new Error("Chromium could not create a 2D canvas for JPEG decoding");
|
|
15
|
+
context.drawImage(bitmap, 0, 0);
|
|
16
|
+
const rgba = context.getImageData(0, 0, bitmap.width, bitmap.height).data;
|
|
17
|
+
const rgb = new Uint8Array(bitmap.width * bitmap.height * 3);
|
|
18
|
+
for (let pixel = 0; pixel < bitmap.width * bitmap.height; pixel++) {
|
|
19
|
+
rgb[pixel * 3] = rgba[pixel * 4] ?? 0;
|
|
20
|
+
rgb[pixel * 3 + 1] = rgba[pixel * 4 + 1] ?? 0;
|
|
21
|
+
rgb[pixel * 3 + 2] = rgba[pixel * 4 + 2] ?? 0;
|
|
22
|
+
}
|
|
23
|
+
let value = "";
|
|
24
|
+
for (let offset = 0; offset < rgb.length; offset += 32768) value += String.fromCharCode(...rgb.subarray(offset, Math.min(rgb.length, offset + 32768)));
|
|
25
|
+
return {
|
|
26
|
+
width: bitmap.width,
|
|
27
|
+
height: bitmap.height,
|
|
28
|
+
rgb: btoa(value)
|
|
29
|
+
};
|
|
30
|
+
} finally {
|
|
31
|
+
bitmap.close();
|
|
32
|
+
}
|
|
33
|
+
}, encoded);
|
|
34
|
+
return {
|
|
35
|
+
width: decoded.width,
|
|
36
|
+
height: decoded.height,
|
|
37
|
+
rgb: new Uint8Array(Buffer.from(decoded.rgb, "base64"))
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { browserJpegDecoder };
|
|
@@ -2,7 +2,7 @@ import { checked } from "./types.js";
|
|
|
2
2
|
import { D50, labToXyz, matrix3, tableDecode, tableEncode, tryInvert3, xyzToLab } from "./spaces.js";
|
|
3
3
|
//#region src/color/black-point.ts
|
|
4
4
|
function detectSourceBlackPoint(colorSpace, pcs, major, relativeA2b, perceptualB2a) {
|
|
5
|
-
if (colorSpace !== "CMYK") return [
|
|
5
|
+
if (colorSpace !== "CMYK" && colorSpace !== "GRAY") return [
|
|
6
6
|
0,
|
|
7
7
|
0,
|
|
8
8
|
0
|
|
@@ -19,11 +19,12 @@ function detectSourceBlackPoint(colorSpace, pcs, major, relativeA2b, perceptualB
|
|
|
19
19
|
checked(result, 2, "black-point A2B result")
|
|
20
20
|
]);
|
|
21
21
|
const lab = pcs === "Lab " ? returned : xyzToLab(returned);
|
|
22
|
-
|
|
22
|
+
const black = [
|
|
23
23
|
Math.min(50, lab[0]),
|
|
24
24
|
0,
|
|
25
25
|
0
|
|
26
26
|
];
|
|
27
|
+
return pcs === "Lab " ? black : labToXyz(black);
|
|
27
28
|
}
|
|
28
29
|
function quadraticRoot(x, y) {
|
|
29
30
|
if (x.length < 4) return 0;
|
|
@@ -61,14 +62,15 @@ function quadraticRoot(x, y) {
|
|
|
61
62
|
return discriminant <= 0 ? 0 : Math.max(0, Math.min(50, (-b + Math.sqrt(discriminant)) / (2 * a)));
|
|
62
63
|
}
|
|
63
64
|
function destinationBlackPoint(colorSpace, pcs, major, relativeA2b, relativeB2a, sourceBlack) {
|
|
64
|
-
if (colorSpace !== "CMYK") return [...sourceBlack];
|
|
65
|
+
if (colorSpace !== "CMYK" && colorSpace !== "GRAY") return [...sourceBlack];
|
|
66
|
+
const sourceLab = pcs === "Lab " ? sourceBlack : xyzToLab(sourceBlack);
|
|
65
67
|
const inputRamp = [], outputRamp = [];
|
|
66
68
|
for (let index = 0; index < 256; index++) {
|
|
67
69
|
const lightness = index * 100 / 255;
|
|
68
70
|
const lab = [
|
|
69
71
|
lightness,
|
|
70
|
-
Math.max(-50, Math.min(50,
|
|
71
|
-
Math.max(-50, Math.min(50,
|
|
72
|
+
Math.max(-50, Math.min(50, sourceLab[1])),
|
|
73
|
+
Math.max(-50, Math.min(50, sourceLab[2]))
|
|
72
74
|
];
|
|
73
75
|
const value = pcs === "Lab " ? lab : labToXyz(lab);
|
|
74
76
|
const device = relativeB2a.run(tableEncode(pcs, relativeB2a.encoding, major, value, false));
|
|
@@ -97,11 +99,12 @@ function destinationBlackPoint(colorSpace, pcs, major, relativeA2b, relativeB2a,
|
|
|
97
99
|
y.push(normalized);
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
|
-
|
|
102
|
+
const result = [
|
|
101
103
|
x.length < 3 ? 0 : quadraticRoot(x, y),
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
sourceLab[1],
|
|
105
|
+
sourceLab[2]
|
|
104
106
|
];
|
|
107
|
+
return pcs === "Lab " ? result : labToXyz(result);
|
|
105
108
|
}
|
|
106
109
|
function compensateBlackPoint(profile, lab, reverse) {
|
|
107
110
|
const point = reverse ? profile.sourceBlackPoint : profile.destinationBlackPoint;
|
package/dist/color/engine.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { clamp, color4, refuse } from "./types.js";
|
|
1
|
+
import { clamp, color1, color4, refuse } from "./types.js";
|
|
2
2
|
import { labToXyz, srgbToLab, tableEncode } from "./spaces.js";
|
|
3
3
|
import { compensateBlackPoint } from "./black-point.js";
|
|
4
4
|
//#region src/color/engine.ts
|
|
@@ -14,5 +14,12 @@ function toPress(profile, rgb) {
|
|
|
14
14
|
clamp(black)
|
|
15
15
|
];
|
|
16
16
|
}
|
|
17
|
+
function toGray(profile, rgb) {
|
|
18
|
+
if (profile.colorSpace !== "GRAY") refuse(`destination color space ${profile.colorSpace}`, "GRAY");
|
|
19
|
+
const lab = compensateBlackPoint(profile, srgbToLab(rgb), false);
|
|
20
|
+
const pcs = profile.pcs === "Lab " ? lab : labToXyz(lab);
|
|
21
|
+
const [gray] = color1(profile.b2a.run(tableEncode(profile.pcs, profile.b2a.encoding, profile.majorVersion, pcs, true)), "B2A result");
|
|
22
|
+
return [clamp(gray)];
|
|
23
|
+
}
|
|
17
24
|
//#endregion
|
|
18
|
-
export { toPress };
|
|
25
|
+
export { toGray, toPress };
|
|
@@ -34,6 +34,16 @@ function multilinear(grid, points, outputs, input) {
|
|
|
34
34
|
}
|
|
35
35
|
return result;
|
|
36
36
|
}
|
|
37
|
+
function linearGrid(grid, points, outputs, input) {
|
|
38
|
+
const count = checked(points, 0, "CLUT dimensions");
|
|
39
|
+
const position = clamp(checked(input, 0, "CLUT input")) * (count - 1);
|
|
40
|
+
const low = Math.min(count - 2, Math.floor(position));
|
|
41
|
+
const fraction = position - low;
|
|
42
|
+
return Array.from({ length: outputs }, (_, channel) => {
|
|
43
|
+
const lower = grid.get(low * outputs + channel);
|
|
44
|
+
return lower + (grid.get((low + 1) * outputs + channel) - lower) * fraction;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
37
47
|
function tetrahedral4(grid, points, outputs, input) {
|
|
38
48
|
const { base, fractions, stride } = layout(points, outputs, input);
|
|
39
49
|
const order = [
|
|
@@ -71,4 +81,4 @@ function matrixInput(values) {
|
|
|
71
81
|
];
|
|
72
82
|
}
|
|
73
83
|
//#endregion
|
|
74
|
-
export { interpolate, matrixInput, multilinear, tetrahedral4 };
|
|
84
|
+
export { interpolate, linearGrid, matrixInput, multilinear, tetrahedral4 };
|
package/dist/color/profile.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { REVERSE_LUT, checked, refuse } from "./types.js";
|
|
2
2
|
import { destinationBlackPoint, detectSourceBlackPoint } from "./black-point.js";
|
|
3
3
|
import { fixed, need, signature, u32 } from "./binary.js";
|
|
4
|
-
import { matrixTables, parseMab, parseMft } from "./tables.js";
|
|
4
|
+
import { grayTables, matrixTables, parseMab, parseMft } from "./tables.js";
|
|
5
5
|
//#region src/color/profile.ts
|
|
6
6
|
const MAX_PROFILE_BYTES = 33554432;
|
|
7
7
|
function header(bytes, view, bounds) {
|
|
@@ -12,7 +12,7 @@ function header(bytes, view, bounds) {
|
|
|
12
12
|
const profileClass = signature(bytes, 12, bounds);
|
|
13
13
|
if (profileClass !== "prtr" && profileClass !== "mntr") refuse(`profile class ${JSON.stringify(profileClass)}`, "prtr or mntr");
|
|
14
14
|
const colorSpace = signature(bytes, 16, bounds);
|
|
15
|
-
if (colorSpace !== "CMYK" && colorSpace !== "RGB ") refuse(`color space ${JSON.stringify(colorSpace)}`, "CMYK or RGB");
|
|
15
|
+
if (colorSpace !== "CMYK" && colorSpace !== "GRAY" && colorSpace !== "RGB ") refuse(`color space ${JSON.stringify(colorSpace)}`, "CMYK, GRAY, or RGB");
|
|
16
16
|
const pcs = signature(bytes, 20, bounds);
|
|
17
17
|
if (pcs !== "Lab " && pcs !== "XYZ ") refuse(`PCS ${JSON.stringify(pcs)}`, "Lab or XYZ");
|
|
18
18
|
u32(view, 0, bounds);
|
|
@@ -55,6 +55,13 @@ function table(bytes, view, all, names, inputs, outputs) {
|
|
|
55
55
|
if (!record) throw new Error(`ICC profile is missing ${name} after its tag set was validated`);
|
|
56
56
|
return recordTable(bytes, view, record, inputs, outputs);
|
|
57
57
|
}
|
|
58
|
+
function optionalTable(bytes, view, all, names, inputs, outputs) {
|
|
59
|
+
const name = names.find((candidate) => all[candidate]);
|
|
60
|
+
if (!name) return void 0;
|
|
61
|
+
const record = all[name];
|
|
62
|
+
if (!record) throw new Error(`ICC profile is missing ${name} after its tag set was validated`);
|
|
63
|
+
return recordTable(bytes, view, record, inputs, outputs);
|
|
64
|
+
}
|
|
58
65
|
function readProfile(bytes) {
|
|
59
66
|
if (bytes.length > 33554432) refuse(`profile length ${bytes.length}`, `at most ${MAX_PROFILE_BYTES} bytes`);
|
|
60
67
|
const bounds = {
|
|
@@ -63,12 +70,15 @@ function readProfile(bytes) {
|
|
|
63
70
|
}, view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
64
71
|
const parsed = header(bytes, view, bounds), all = records(bytes, view, bounds);
|
|
65
72
|
if (all.wtpt?.type !== "XYZ ") refuse(`white point tag type ${JSON.stringify(all.wtpt?.type ?? "missing")}`, "XYZ ");
|
|
66
|
-
const channels = parsed.colorSpace === "CMYK" ? 4 : 3;
|
|
73
|
+
const channels = parsed.colorSpace === "CMYK" ? 4 : parsed.colorSpace === "GRAY" ? 1 : 3;
|
|
67
74
|
const matrix = parsed.colorSpace === "RGB " ? matrixTables(bytes, view, all) : void 0;
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
75
|
+
const trcRecord = all.kTRC;
|
|
76
|
+
const gray = parsed.colorSpace === "GRAY" && trcRecord ? grayTables(bytes, view, trcRecord, parsed.pcs) : void 0;
|
|
77
|
+
const select = (names, inputs, outputs, fallback) => optionalTable(bytes, view, all, names, inputs, outputs) ?? fallback ?? table(bytes, view, all, names, inputs, outputs);
|
|
78
|
+
const a2b = matrix?.a2b ?? select(["A2B1", "A2B0"], channels, 3, gray?.a2b);
|
|
79
|
+
const b2a = matrix?.b2a ?? select(["B2A1", "B2A0"], 3, channels, gray?.b2a);
|
|
80
|
+
const perceptualA2b = matrix?.a2b ?? select(["A2B0", "A2B1"], channels, 3, gray?.a2b);
|
|
81
|
+
const perceptualB2a = matrix?.b2a ?? select(["B2A0", "B2A1"], 3, channels, gray?.b2a);
|
|
72
82
|
const gamutRecord = all.gamt, gamut = gamutRecord ? recordTable(bytes, view, gamutRecord, 3, 1) : void 0;
|
|
73
83
|
const white = all.wtpt;
|
|
74
84
|
if (!white) throw new Error("ICC profile is missing wtpt after its tag set was validated");
|
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,6 +1,6 @@
|
|
|
1
1
|
import { EffectsReport } from "./effects/types.js";
|
|
2
|
-
import { PressImage, PressReport } from "./press/types.js";
|
|
3
|
-
import { Font, ImageOptions, RenderLimits, RenderOptions } from "./options.js";
|
|
2
|
+
import { PressDestination, PressImage, PressReport } from "./press/types.js";
|
|
3
|
+
import { Font, ImageOptions, OverflowContribution, OverflowEntry, OverflowPart, OverflowReport, RenderLimits, RenderOptions } from "./options.js";
|
|
4
4
|
import { RenderRefusedError, RendererUnavailableError } from "./errors.js";
|
|
5
5
|
import { BookInput, Deck, ImpositionInput, PageInput } from "@segnavia/format";
|
|
6
6
|
import { PrintOptions } from "@segnavia/frame/print-document";
|
|
@@ -33,4 +33,4 @@ export declare function fontFaces(fonts?: Font[]): string;
|
|
|
33
33
|
export declare function captureImage(tab: Page$1, options: Pick<ImageOptions, 'format' | 'quality' | 'limits'>, fullPage: boolean): Promise<Uint8Array>;
|
|
34
34
|
export declare function openRenderer(options: RendererOptions): Promise<Renderer>;
|
|
35
35
|
//#endregion
|
|
36
|
-
export { type EffectsReport, type Font, type ImageOptions, type PressImage, type PressReport, type RenderLimits, type RenderOptions, RenderRefusedError, RendererUnavailableError };
|
|
36
|
+
export { type EffectsReport, type Font, type ImageOptions, type OverflowContribution, type OverflowEntry, type OverflowPart, type OverflowReport, type PressDestination, type PressImage, type PressReport, type RenderLimits, type RenderOptions, RenderRefusedError, RendererUnavailableError };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { RenderRefusedError, RendererUnavailableError } from "./errors.js";
|
|
2
2
|
import { renderBrowserBookPdf } from "./book-render.js";
|
|
3
|
+
import { browserJpegDecoder } from "./browser-jpeg.js";
|
|
3
4
|
import { containFixedPageOverflow } from "./overflow-containment.js";
|
|
4
5
|
import { renderDeckPdf } from "./deck-pdf.js";
|
|
5
6
|
import { assertNeutralTolerance } from "./press/color-policy.js";
|
|
6
7
|
import { rewriteEffectsPdf } from "./effects/adapter.js";
|
|
8
|
+
import { measureOverflow, printWithOverflow } from "./overflow.js";
|
|
7
9
|
import { assertPrintOptions, printCss, printPage, printViewport } from "./page-paper.js";
|
|
8
10
|
import { renderPagesPdf } from "./pages-pdf.js";
|
|
9
11
|
import { rewritePressPdf } from "./press/adapter.js";
|
|
@@ -55,7 +57,7 @@ async function enforce(pdf, limits) {
|
|
|
55
57
|
}
|
|
56
58
|
return pdf;
|
|
57
59
|
}
|
|
58
|
-
async function finishPdf(pdf, options) {
|
|
60
|
+
async function finishPdf(pdf, options, decodeJpeg) {
|
|
59
61
|
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}.`);
|
|
60
62
|
let output = pdf;
|
|
61
63
|
if (options.effects !== false) {
|
|
@@ -66,7 +68,7 @@ async function finishPdf(pdf, options) {
|
|
|
66
68
|
if (options.press) {
|
|
67
69
|
const neutralTolerance = options.press.neutralTolerance ?? 3;
|
|
68
70
|
assertNeutralTolerance(neutralTolerance);
|
|
69
|
-
const converted = await rewritePressPdf(output, options.press.profile, neutralTolerance);
|
|
71
|
+
const converted = await rewritePressPdf(output, options.press.profile, neutralTolerance, decodeJpeg);
|
|
70
72
|
output = converted.bytes;
|
|
71
73
|
options.press.report?.(converted.report);
|
|
72
74
|
}
|
|
@@ -205,6 +207,18 @@ async function openRenderer(options) {
|
|
|
205
207
|
reportHeight: false
|
|
206
208
|
});
|
|
207
209
|
}
|
|
210
|
+
async function finishWithBrowser(pdf, options) {
|
|
211
|
+
if (!options.press || new TextDecoder().decode(options.press.profile.subarray(16, 20)) !== "GRAY") return finishPdf(pdf, options);
|
|
212
|
+
return withTab({
|
|
213
|
+
viewport: {
|
|
214
|
+
width: 1,
|
|
215
|
+
height: 1
|
|
216
|
+
},
|
|
217
|
+
scripts: true,
|
|
218
|
+
html: "<!doctype html>",
|
|
219
|
+
waitForReady: false
|
|
220
|
+
}, (tab) => finishPdf(pdf, options, browserJpegDecoder(tab)));
|
|
221
|
+
}
|
|
208
222
|
function pageViewport(page, width, printing = false) {
|
|
209
223
|
if (page.print) return printViewport(page.print, printing);
|
|
210
224
|
if (page.size === "fluid") return {
|
|
@@ -217,10 +231,10 @@ async function openRenderer(options) {
|
|
|
217
231
|
};
|
|
218
232
|
}
|
|
219
233
|
const image = captureImage;
|
|
220
|
-
async function renderPagePdf(page, opts) {
|
|
234
|
+
async function renderPagePdf(page, opts, inputIndex) {
|
|
221
235
|
if (page.print) assertPrintOptions(page.print, opts.preferCssPageSize);
|
|
222
236
|
const viewport = pageViewport(page, void 0, true);
|
|
223
|
-
return
|
|
237
|
+
return withTab({
|
|
224
238
|
viewport,
|
|
225
239
|
scripts: page.scripts === "sandboxed",
|
|
226
240
|
html: pageDocument(page, opts, true),
|
|
@@ -228,15 +242,26 @@ async function openRenderer(options) {
|
|
|
228
242
|
boundMs: opts.boundMs,
|
|
229
243
|
beforeOutput: opts.beforeOutput
|
|
230
244
|
}, async (tab) => {
|
|
231
|
-
if (page.print) return printPage(tab, page.print, opts.preferCssPageSize);
|
|
245
|
+
if (page.print) return printPage(tab, page.print, opts.preferCssPageSize, inputIndex);
|
|
232
246
|
const fixedHeight = typeof page.size === "object" && page.size.height !== "auto" ? page.size.height : null;
|
|
233
247
|
const height = fixedHeight ?? await contentHeight(tab);
|
|
234
|
-
|
|
248
|
+
if (fixedHeight !== null) {
|
|
249
|
+
const overflow = await measureOverflow(tab, viewport.width, "page", inputIndex);
|
|
250
|
+
return {
|
|
251
|
+
bytes: await tab.pdf({
|
|
252
|
+
width: `${viewport.width}px`,
|
|
253
|
+
height: `${height}px`,
|
|
254
|
+
printBackground: true
|
|
255
|
+
}),
|
|
256
|
+
overflow
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return printWithOverflow(tab, viewport.width, "page", {
|
|
235
260
|
width: `${viewport.width}px`,
|
|
236
261
|
height: `${height}px`,
|
|
237
262
|
printBackground: true,
|
|
238
|
-
pageRanges:
|
|
239
|
-
});
|
|
263
|
+
pageRanges: "1"
|
|
264
|
+
}, inputIndex);
|
|
240
265
|
});
|
|
241
266
|
}
|
|
242
267
|
async function renderPiecePdf(page, geometry, css, opts) {
|
|
@@ -273,13 +298,16 @@ async function openRenderer(options) {
|
|
|
273
298
|
return {
|
|
274
299
|
bookPdf(input, opts = {}) {
|
|
275
300
|
return renderBrowserBookPdf(input, opts, {
|
|
276
|
-
render: (setup, pdf) => withTab(setup, (tab) => tab
|
|
301
|
+
render: (setup, pdf, pageWidth, part) => withTab(setup, (tab) => printWithOverflow(tab, pageWidth, part, pdf)),
|
|
277
302
|
fontFaces: (options) => fontFaces(options.fonts),
|
|
278
|
-
finish:
|
|
303
|
+
finish: finishWithBrowser
|
|
279
304
|
});
|
|
280
305
|
},
|
|
281
306
|
async pagePdf(input, opts = {}) {
|
|
282
|
-
|
|
307
|
+
const printed = await renderPagePdf(Page.parse(input), opts);
|
|
308
|
+
const output = await finishWithBrowser(printed.bytes, opts);
|
|
309
|
+
if (printed.overflow) opts.overflow?.report?.({ entries: [printed.overflow] });
|
|
310
|
+
return output;
|
|
283
311
|
},
|
|
284
312
|
async pageImage(input, opts = {}) {
|
|
285
313
|
const page = Page.parse(input);
|
|
@@ -300,10 +328,10 @@ async function openRenderer(options) {
|
|
|
300
328
|
},
|
|
301
329
|
pagesPdf(inputs, opts = {}) {
|
|
302
330
|
return renderPagesPdf(inputs, opts, {
|
|
303
|
-
pagePdf: renderPagePdf,
|
|
331
|
+
pagePdf: (page, options, inputIndex) => renderPagePdf(page, options, inputIndex),
|
|
304
332
|
piecePdf: renderPiecePdf,
|
|
305
333
|
fontFaces: (options) => fontFaces(options.fonts),
|
|
306
|
-
finish:
|
|
334
|
+
finish: finishWithBrowser
|
|
307
335
|
});
|
|
308
336
|
},
|
|
309
337
|
deckPdf(deck, opts = {}) {
|
|
@@ -313,7 +341,7 @@ async function openRenderer(options) {
|
|
|
313
341
|
waitForReady: false
|
|
314
342
|
}, (tab) => tab.pdf(pdf)),
|
|
315
343
|
fontFaces: (options) => fontFaces(options.fonts),
|
|
316
|
-
finish:
|
|
344
|
+
finish: finishWithBrowser
|
|
317
345
|
});
|
|
318
346
|
},
|
|
319
347
|
async slideImage(deck, index, opts = {}) {
|
package/dist/options.d.ts
CHANGED
|
@@ -2,6 +2,37 @@ import { EffectsReport } from "./effects/types.js";
|
|
|
2
2
|
import { PressReport } from "./press/types.js";
|
|
3
3
|
import { Page } from "playwright-core";
|
|
4
4
|
//#region src/options.d.ts
|
|
5
|
+
export type OverflowPart = 'page' | 'front' | 'body' | 'margin';
|
|
6
|
+
export interface OverflowContribution {
|
|
7
|
+
/** A short id or tag-and-class path locating the element. */
|
|
8
|
+
locator: string;
|
|
9
|
+
/** Border-box pixels clipped beyond the inline start of the page. */
|
|
10
|
+
clippedStart: number;
|
|
11
|
+
/** Border-box pixels clipped beyond the inline end of the page. */
|
|
12
|
+
clippedEnd: number;
|
|
13
|
+
}
|
|
14
|
+
export interface OverflowEntry {
|
|
15
|
+
/** Output part printed by this entry. */
|
|
16
|
+
part: OverflowPart;
|
|
17
|
+
/** Input position when the public render is pagesPdf. */
|
|
18
|
+
inputIndex?: number;
|
|
19
|
+
/** Printable inline width in CSS pixels. */
|
|
20
|
+
pageWidth: number;
|
|
21
|
+
/** Greatest document scroll width in CSS pixels. */
|
|
22
|
+
extent: number;
|
|
23
|
+
/** Pixels clipped beyond the inline start of the page. */
|
|
24
|
+
clippedStart: number;
|
|
25
|
+
/** Pixels clipped beyond the inline end of the page. */
|
|
26
|
+
clippedEnd: number;
|
|
27
|
+
/** PDF scale used to preserve the print's true size. */
|
|
28
|
+
scale: number;
|
|
29
|
+
/** Up to twenty border boxes that extend beyond the page. */
|
|
30
|
+
elements: OverflowContribution[];
|
|
31
|
+
}
|
|
32
|
+
export interface OverflowReport {
|
|
33
|
+
/** Kept prints in output order. */
|
|
34
|
+
entries: OverflowEntry[];
|
|
35
|
+
}
|
|
5
36
|
export interface Font {
|
|
6
37
|
family: string;
|
|
7
38
|
data: Uint8Array;
|
|
@@ -24,6 +55,9 @@ export interface RenderOptions {
|
|
|
24
55
|
effects?: false | {
|
|
25
56
|
report?: (report: EffectsReport) => void;
|
|
26
57
|
};
|
|
58
|
+
overflow?: {
|
|
59
|
+
report?: (report: OverflowReport) => void;
|
|
60
|
+
};
|
|
27
61
|
press?: {
|
|
28
62
|
profile: Uint8Array;
|
|
29
63
|
neutralTolerance?: number;
|