av6-pdf-engine 1.0.0 → 1.0.2
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/.prettierignore +4 -0
- package/.prettierrc +6 -0
- package/README.md +155 -0
- package/dist/index.d.mts +296 -0
- package/dist/index.d.ts +296 -3
- package/dist/index.js +1979 -18
- package/dist/index.mjs +1940 -0
- package/package.json +12 -21
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/renderer-engine/blocks/barcode.d.ts +0 -3
- package/dist/renderer-engine/blocks/barcode.d.ts.map +0 -1
- package/dist/renderer-engine/blocks/barcode.js +0 -47
- package/dist/renderer-engine/blocks/barcode.js.map +0 -1
- package/dist/renderer-engine/blocks/columns.d.ts +0 -3
- package/dist/renderer-engine/blocks/columns.d.ts.map +0 -1
- package/dist/renderer-engine/blocks/columns.js +0 -105
- package/dist/renderer-engine/blocks/columns.js.map +0 -1
- package/dist/renderer-engine/blocks/image.d.ts +0 -4
- package/dist/renderer-engine/blocks/image.d.ts.map +0 -1
- package/dist/renderer-engine/blocks/image.js +0 -57
- package/dist/renderer-engine/blocks/image.js.map +0 -1
- package/dist/renderer-engine/blocks/index.d.ts +0 -8
- package/dist/renderer-engine/blocks/index.d.ts.map +0 -1
- package/dist/renderer-engine/blocks/index.js +0 -24
- package/dist/renderer-engine/blocks/index.js.map +0 -1
- package/dist/renderer-engine/blocks/key-value-grid.d.ts +0 -3
- package/dist/renderer-engine/blocks/key-value-grid.d.ts.map +0 -1
- package/dist/renderer-engine/blocks/key-value-grid.js +0 -245
- package/dist/renderer-engine/blocks/key-value-grid.js.map +0 -1
- package/dist/renderer-engine/blocks/line.d.ts +0 -3
- package/dist/renderer-engine/blocks/line.d.ts.map +0 -1
- package/dist/renderer-engine/blocks/line.js +0 -35
- package/dist/renderer-engine/blocks/line.js.map +0 -1
- package/dist/renderer-engine/blocks/table.d.ts +0 -4
- package/dist/renderer-engine/blocks/table.d.ts.map +0 -1
- package/dist/renderer-engine/blocks/table.js +0 -201
- package/dist/renderer-engine/blocks/table.js.map +0 -1
- package/dist/renderer-engine/blocks/text.d.ts +0 -3
- package/dist/renderer-engine/blocks/text.d.ts.map +0 -1
- package/dist/renderer-engine/blocks/text.js +0 -52
- package/dist/renderer-engine/blocks/text.js.map +0 -1
- package/dist/renderer-engine/index.d.ts +0 -4
- package/dist/renderer-engine/index.d.ts.map +0 -1
- package/dist/renderer-engine/index.js +0 -215
- package/dist/renderer-engine/index.js.map +0 -1
- package/dist/renderer-engine/utils/block-renderer.d.ts +0 -17
- package/dist/renderer-engine/utils/block-renderer.d.ts.map +0 -1
- package/dist/renderer-engine/utils/block-renderer.js +0 -86
- package/dist/renderer-engine/utils/block-renderer.js.map +0 -1
- package/dist/renderer-engine/utils/context.d.ts +0 -3
- package/dist/renderer-engine/utils/context.d.ts.map +0 -1
- package/dist/renderer-engine/utils/context.js +0 -16
- package/dist/renderer-engine/utils/context.js.map +0 -1
- package/dist/renderer-engine/utils/ensure-space.d.ts +0 -9
- package/dist/renderer-engine/utils/ensure-space.d.ts.map +0 -1
- package/dist/renderer-engine/utils/ensure-space.js +0 -18
- package/dist/renderer-engine/utils/ensure-space.js.map +0 -1
- package/dist/renderer-engine/utils/env.d.ts +0 -3
- package/dist/renderer-engine/utils/env.d.ts.map +0 -1
- package/dist/renderer-engine/utils/env.js +0 -10
- package/dist/renderer-engine/utils/env.js.map +0 -1
- package/dist/renderer-engine/utils/finish-page.d.ts +0 -13
- package/dist/renderer-engine/utils/finish-page.d.ts.map +0 -1
- package/dist/renderer-engine/utils/finish-page.js +0 -27
- package/dist/renderer-engine/utils/finish-page.js.map +0 -1
- package/dist/renderer-engine/utils/footer.d.ts +0 -3
- package/dist/renderer-engine/utils/footer.d.ts.map +0 -1
- package/dist/renderer-engine/utils/footer.js +0 -89
- package/dist/renderer-engine/utils/footer.js.map +0 -1
- package/dist/renderer-engine/utils/header.d.ts +0 -3
- package/dist/renderer-engine/utils/header.d.ts.map +0 -1
- package/dist/renderer-engine/utils/header.js +0 -47
- package/dist/renderer-engine/utils/header.js.map +0 -1
- package/dist/renderer-engine/utils/image-loader.d.ts +0 -4
- package/dist/renderer-engine/utils/image-loader.d.ts.map +0 -1
- package/dist/renderer-engine/utils/image-loader.js +0 -52
- package/dist/renderer-engine/utils/image-loader.js.map +0 -1
- package/dist/renderer-engine/utils/index.d.ts +0 -19
- package/dist/renderer-engine/utils/index.d.ts.map +0 -1
- package/dist/renderer-engine/utils/index.js +0 -35
- package/dist/renderer-engine/utils/index.js.map +0 -1
- package/dist/renderer-engine/utils/layout.d.ts +0 -8
- package/dist/renderer-engine/utils/layout.d.ts.map +0 -1
- package/dist/renderer-engine/utils/layout.js +0 -22
- package/dist/renderer-engine/utils/layout.js.map +0 -1
- package/dist/renderer-engine/utils/measure-block-height.d.ts +0 -8
- package/dist/renderer-engine/utils/measure-block-height.d.ts.map +0 -1
- package/dist/renderer-engine/utils/measure-block-height.js +0 -220
- package/dist/renderer-engine/utils/measure-block-height.js.map +0 -1
- package/dist/renderer-engine/utils/page-background.d.ts +0 -6
- package/dist/renderer-engine/utils/page-background.d.ts.map +0 -1
- package/dist/renderer-engine/utils/page-background.js +0 -30
- package/dist/renderer-engine/utils/page-background.js.map +0 -1
- package/dist/renderer-engine/utils/page-flow.d.ts +0 -3
- package/dist/renderer-engine/utils/page-flow.d.ts.map +0 -1
- package/dist/renderer-engine/utils/page-flow.js +0 -9
- package/dist/renderer-engine/utils/page-flow.js.map +0 -1
- package/dist/renderer-engine/utils/page-limit.d.ts +0 -8
- package/dist/renderer-engine/utils/page-limit.d.ts.map +0 -1
- package/dist/renderer-engine/utils/page-limit.js +0 -15
- package/dist/renderer-engine/utils/page-limit.js.map +0 -1
- package/dist/renderer-engine/utils/qr-bar-code.d.ts +0 -17
- package/dist/renderer-engine/utils/qr-bar-code.d.ts.map +0 -1
- package/dist/renderer-engine/utils/qr-bar-code.js +0 -141
- package/dist/renderer-engine/utils/qr-bar-code.js.map +0 -1
- package/dist/renderer-engine/utils/signature.d.ts +0 -20
- package/dist/renderer-engine/utils/signature.d.ts.map +0 -1
- package/dist/renderer-engine/utils/signature.js +0 -214
- package/dist/renderer-engine/utils/signature.js.map +0 -1
- package/dist/renderer-engine/utils/start-page-layout.d.ts +0 -20
- package/dist/renderer-engine/utils/start-page-layout.d.ts.map +0 -1
- package/dist/renderer-engine/utils/start-page-layout.js +0 -34
- package/dist/renderer-engine/utils/start-page-layout.js.map +0 -1
- package/dist/renderer-engine/utils/styles.d.ts +0 -7
- package/dist/renderer-engine/utils/styles.d.ts.map +0 -1
- package/dist/renderer-engine/utils/styles.js +0 -124
- package/dist/renderer-engine/utils/styles.js.map +0 -1
- package/dist/renderer-engine/utils/watermark.d.ts +0 -11
- package/dist/renderer-engine/utils/watermark.d.ts.map +0 -1
- package/dist/renderer-engine/utils/watermark.js +0 -57
- package/dist/renderer-engine/utils/watermark.js.map +0 -1
- package/dist/types/barcode.d.ts +0 -23
- package/dist/types/barcode.d.ts.map +0 -1
- package/dist/types/barcode.js +0 -11
- package/dist/types/barcode.js.map +0 -1
- package/dist/types/columns.d.ts +0 -14
- package/dist/types/columns.d.ts.map +0 -1
- package/dist/types/columns.js +0 -3
- package/dist/types/columns.js.map +0 -1
- package/dist/types/common.d.ts +0 -102
- package/dist/types/common.d.ts.map +0 -1
- package/dist/types/common.js +0 -3
- package/dist/types/common.js.map +0 -1
- package/dist/types/context.d.ts +0 -17
- package/dist/types/context.d.ts.map +0 -1
- package/dist/types/context.js +0 -3
- package/dist/types/context.js.map +0 -1
- package/dist/types/image.d.ts +0 -13
- package/dist/types/image.d.ts.map +0 -1
- package/dist/types/image.js +0 -3
- package/dist/types/image.js.map +0 -1
- package/dist/types/index.d.ts +0 -13
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -29
- package/dist/types/index.js.map +0 -1
- package/dist/types/key-value-grid.d.ts +0 -34
- package/dist/types/key-value-grid.d.ts.map +0 -1
- package/dist/types/key-value-grid.js +0 -3
- package/dist/types/key-value-grid.js.map +0 -1
- package/dist/types/line.d.ts +0 -11
- package/dist/types/line.d.ts.map +0 -1
- package/dist/types/line.js +0 -3
- package/dist/types/line.js.map +0 -1
- package/dist/types/page-break.d.ts +0 -5
- package/dist/types/page-break.d.ts.map +0 -1
- package/dist/types/page-break.js +0 -3
- package/dist/types/page-break.js.map +0 -1
- package/dist/types/qr.d.ts +0 -22
- package/dist/types/qr.d.ts.map +0 -1
- package/dist/types/qr.js +0 -10
- package/dist/types/qr.js.map +0 -1
- package/dist/types/signature.d.ts +0 -12
- package/dist/types/signature.d.ts.map +0 -1
- package/dist/types/signature.js +0 -3
- package/dist/types/signature.js.map +0 -1
- package/dist/types/table.d.ts +0 -32
- package/dist/types/table.d.ts.map +0 -1
- package/dist/types/table.js +0 -3
- package/dist/types/table.js.map +0 -1
- package/dist/types/text.d.ts +0 -19
- package/dist/types/text.d.ts.map +0 -1
- package/dist/types/text.js +0 -3
- package/dist/types/text.js.map +0 -1
- package/dist/types/utility.d.ts +0 -1
- package/dist/types/utility.d.ts.map +0 -1
- package/dist/types/utility.js +0 -2
- package/dist/types/utility.js.map +0 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1940 @@
|
|
|
1
|
+
// src/renderer-engine/index.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import PDFDocument from "pdfkit";
|
|
4
|
+
|
|
5
|
+
// src/renderer-engine/blocks/barcode.ts
|
|
6
|
+
var processBarcodeBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
|
|
7
|
+
const imgSource = block.src;
|
|
8
|
+
const imgWidth = block.width ?? 120;
|
|
9
|
+
const imgHeight = block.height ?? 40;
|
|
10
|
+
const localLeft = block.marginLeft ?? 0;
|
|
11
|
+
const localRight = block.marginRight ?? 0;
|
|
12
|
+
const baseLeft = env.marginLeft + localLeft;
|
|
13
|
+
const availableWidth = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
14
|
+
const mt = block.marginTop ?? 0;
|
|
15
|
+
const mb = block.marginBottom ?? 0;
|
|
16
|
+
const baseY = y ?? ctx.currentY;
|
|
17
|
+
const startY = baseY + mt;
|
|
18
|
+
const heightNeeded = mt + imgHeight + mb;
|
|
19
|
+
if (y === null) {
|
|
20
|
+
ensureSpaceFor(heightNeeded, env);
|
|
21
|
+
}
|
|
22
|
+
let x = baseLeft;
|
|
23
|
+
if (block.align === "center") {
|
|
24
|
+
x = baseLeft + (availableWidth - imgWidth) / 2;
|
|
25
|
+
} else if (block.align === "right") {
|
|
26
|
+
x = baseLeft + availableWidth - imgWidth;
|
|
27
|
+
}
|
|
28
|
+
const rotation = typeof block.rotate === "number" ? block.rotate : block.orientation === "vertical" ? 90 : 0;
|
|
29
|
+
try {
|
|
30
|
+
drawRotatedImage(doc, imgSource, x, startY, imgWidth, imgHeight, rotation);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.warn("Failed to load barcode image:", e);
|
|
33
|
+
}
|
|
34
|
+
const newY = startY + imgHeight + mb;
|
|
35
|
+
if (y === null) ctx.currentY = newY;
|
|
36
|
+
return newY;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/renderer-engine/blocks/columns.ts
|
|
40
|
+
var processColumnsBlock = (ctx, block, y, env, computeColumnPixelWidths2, renderBlock, ensureSpaceFor, measureBlockHeight) => {
|
|
41
|
+
const blockLeft = block.marginLeft ?? 0;
|
|
42
|
+
const blockRight = block.marginRight ?? 0;
|
|
43
|
+
const baseLeft = env.marginLeft + blockLeft;
|
|
44
|
+
const totalWidth = Math.max(env.innerWidth - blockLeft - blockRight, 1);
|
|
45
|
+
const mt = block.marginTop ?? 0;
|
|
46
|
+
const mb = block.marginBottom ?? 0;
|
|
47
|
+
const cols = block.columns ?? [];
|
|
48
|
+
const n = cols.length;
|
|
49
|
+
if (!n) {
|
|
50
|
+
const baseY0 = y ?? ctx.currentY;
|
|
51
|
+
const endY = baseY0 + mt + mb;
|
|
52
|
+
if (y === null) ctx.currentY = endY;
|
|
53
|
+
return endY;
|
|
54
|
+
}
|
|
55
|
+
let colWidths;
|
|
56
|
+
const mode = block.mode ?? "fixedGap";
|
|
57
|
+
let gap;
|
|
58
|
+
if (mode === "spaceBetween" && n > 1) {
|
|
59
|
+
if (block.widths && block.widths.length === n) {
|
|
60
|
+
colWidths = computeColumnPixelWidths2(block.widths, totalWidth);
|
|
61
|
+
} else {
|
|
62
|
+
colWidths = Array(n).fill(totalWidth / n);
|
|
63
|
+
}
|
|
64
|
+
const totalColsWidth = colWidths.reduce((a, b) => a + b, 0);
|
|
65
|
+
const remaining = Math.max(totalWidth - totalColsWidth, 0);
|
|
66
|
+
gap = remaining / (n - 1);
|
|
67
|
+
} else {
|
|
68
|
+
gap = block.gap ?? 20;
|
|
69
|
+
const totalGapsWidth = gap * (n - 1);
|
|
70
|
+
const widthForCols = Math.max(totalWidth - totalGapsWidth, 1);
|
|
71
|
+
if (block.widths && block.widths.length === n) {
|
|
72
|
+
colWidths = computeColumnPixelWidths2(block.widths, widthForCols);
|
|
73
|
+
} else {
|
|
74
|
+
colWidths = Array(n).fill(widthForCols / n);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const xBases = [];
|
|
78
|
+
{
|
|
79
|
+
let x = baseLeft;
|
|
80
|
+
for (let i = 0; i < n; i++) {
|
|
81
|
+
xBases.push(x);
|
|
82
|
+
x += colWidths[i] + (i < n - 1 ? gap : 0);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const measureColHeights = [];
|
|
86
|
+
for (let colIndex = 0; colIndex < n; colIndex++) {
|
|
87
|
+
const colEnvMeasure = {
|
|
88
|
+
marginLeft: 0,
|
|
89
|
+
innerWidth: colWidths[colIndex],
|
|
90
|
+
allowPageBreak: false
|
|
91
|
+
// measure as keep-together
|
|
92
|
+
};
|
|
93
|
+
let h = 0;
|
|
94
|
+
for (const b of cols[colIndex]) {
|
|
95
|
+
if (b.visible === false) continue;
|
|
96
|
+
h += measureBlockHeight(b, colEnvMeasure);
|
|
97
|
+
}
|
|
98
|
+
measureColHeights.push(h);
|
|
99
|
+
}
|
|
100
|
+
const maxColHeight = Math.max(...measureColHeights, 0);
|
|
101
|
+
const totalHeightNeeded = mt + maxColHeight + mb;
|
|
102
|
+
if (y === null) {
|
|
103
|
+
ensureSpaceFor(totalHeightNeeded, env);
|
|
104
|
+
}
|
|
105
|
+
const baseY = y ?? ctx.currentY;
|
|
106
|
+
const startY = baseY + mt;
|
|
107
|
+
const heights = [];
|
|
108
|
+
for (let colIndex = 0; colIndex < n; colIndex++) {
|
|
109
|
+
const colEnv = {
|
|
110
|
+
marginLeft: xBases[colIndex],
|
|
111
|
+
innerWidth: colWidths[colIndex],
|
|
112
|
+
allowPageBreak: false
|
|
113
|
+
// keep whole column block intact on one page
|
|
114
|
+
};
|
|
115
|
+
let colY = startY;
|
|
116
|
+
for (const b of cols[colIndex]) {
|
|
117
|
+
if (b.visible === false) continue;
|
|
118
|
+
colY = renderBlock(b, colY, colEnv);
|
|
119
|
+
}
|
|
120
|
+
heights.push(colY - startY);
|
|
121
|
+
}
|
|
122
|
+
const maxHeight = Math.max(...heights, 0);
|
|
123
|
+
const newY = startY + maxHeight + mb;
|
|
124
|
+
if (y === null) ctx.currentY = newY;
|
|
125
|
+
return newY;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// src/renderer-engine/blocks/image.ts
|
|
129
|
+
var drawRotatedImage = (doc, src, x, y, width, height, rotationDeg) => {
|
|
130
|
+
if (!rotationDeg) {
|
|
131
|
+
doc.image(src, x, y, { width, height });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const cx = x + width / 2;
|
|
135
|
+
const cy = y + height / 2;
|
|
136
|
+
doc.save();
|
|
137
|
+
doc.rotate(rotationDeg, { origin: [cx, cy] });
|
|
138
|
+
doc.image(src, x, y, { width, height });
|
|
139
|
+
doc.restore();
|
|
140
|
+
};
|
|
141
|
+
var processImageBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
|
|
142
|
+
const imgSource = block.src;
|
|
143
|
+
const imgWidth = block.width ?? 80;
|
|
144
|
+
const imgHeight = block.height ?? 50;
|
|
145
|
+
const localLeft = block.marginLeft ?? 0;
|
|
146
|
+
const localRight = block.marginRight ?? 0;
|
|
147
|
+
const baseLeft = env.marginLeft + localLeft;
|
|
148
|
+
const availableWidth = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
149
|
+
const mt = block.marginTop ?? 0;
|
|
150
|
+
const mb = block.marginBottom ?? 0;
|
|
151
|
+
const baseY = y ?? ctx.currentY;
|
|
152
|
+
const startY = baseY + mt;
|
|
153
|
+
const heightNeeded = mt + imgHeight + mb;
|
|
154
|
+
if (y === null) {
|
|
155
|
+
ensureSpaceFor(heightNeeded, env);
|
|
156
|
+
}
|
|
157
|
+
let x = baseLeft;
|
|
158
|
+
if (block.align === "center") {
|
|
159
|
+
x = baseLeft + (availableWidth - imgWidth) / 2;
|
|
160
|
+
} else if (block.align === "right") {
|
|
161
|
+
x = baseLeft + availableWidth - imgWidth;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
doc.image(imgSource, x, startY, {
|
|
165
|
+
width: imgWidth,
|
|
166
|
+
height: imgHeight
|
|
167
|
+
});
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.warn("Failed to load image:", e);
|
|
170
|
+
}
|
|
171
|
+
const newY = startY + imgHeight + mb;
|
|
172
|
+
if (y === null) ctx.currentY = newY;
|
|
173
|
+
return newY;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/renderer-engine/blocks/key-value-grid.ts
|
|
177
|
+
var processKeyValueGridBlock = (doc, ctx, styles, block, y, env, computeColumnPixelWidths2, ensureSpaceFor) => {
|
|
178
|
+
const mt = block.marginTop ?? 0;
|
|
179
|
+
const mb = block.marginBottom ?? 0;
|
|
180
|
+
const rowGap = block.rowGap ?? 4;
|
|
181
|
+
const orientation = block.orientation ?? "horizontal";
|
|
182
|
+
const cols = block.columns ?? [];
|
|
183
|
+
const colCount = cols.length;
|
|
184
|
+
if (!colCount) {
|
|
185
|
+
const baseY2 = y ?? ctx.currentY;
|
|
186
|
+
const endY2 = baseY2 + mt + mb;
|
|
187
|
+
if (y === null) ctx.currentY = endY2;
|
|
188
|
+
return endY2;
|
|
189
|
+
}
|
|
190
|
+
const blockLeft = block.marginLeft ?? 0;
|
|
191
|
+
const blockRight = block.marginRight ?? 0;
|
|
192
|
+
const baseLeft = env.marginLeft + blockLeft;
|
|
193
|
+
const totalWidth = Math.max(env.innerWidth - blockLeft - blockRight, 1);
|
|
194
|
+
const columnGap = block.columnGap ?? 30;
|
|
195
|
+
const totalGapWidth = columnGap * (colCount - 1);
|
|
196
|
+
const widthForCols = Math.max(totalWidth - totalGapWidth, 1);
|
|
197
|
+
let colWidths;
|
|
198
|
+
if (block.columnWidths && block.columnWidths.length === colCount) {
|
|
199
|
+
const safeWidths = block.columnWidths.map(
|
|
200
|
+
(w) => w === void 0 ? "*" : w
|
|
201
|
+
);
|
|
202
|
+
colWidths = computeColumnPixelWidths2(safeWidths, widthForCols);
|
|
203
|
+
} else {
|
|
204
|
+
const equal = widthForCols / colCount;
|
|
205
|
+
colWidths = Array(colCount).fill(equal);
|
|
206
|
+
}
|
|
207
|
+
const xBases = [];
|
|
208
|
+
{
|
|
209
|
+
let x = baseLeft;
|
|
210
|
+
for (let i = 0; i < colCount; i++) {
|
|
211
|
+
xBases.push(x);
|
|
212
|
+
x += colWidths[i] + columnGap;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const maxRows = Math.max(...cols.map((c) => c.length));
|
|
216
|
+
const separatorText = block.separator;
|
|
217
|
+
const sepBoxWidth = separatorText ? 10 : 0;
|
|
218
|
+
const baseKeyWidthRatio = 0.35;
|
|
219
|
+
const resolveStyled = (base, styleNames) => resolveTextBlock(styles, {
|
|
220
|
+
...base,
|
|
221
|
+
style: styleNames
|
|
222
|
+
});
|
|
223
|
+
const measureTextHeight = (tb, width) => {
|
|
224
|
+
const fontName = getFontNameForText(tb);
|
|
225
|
+
doc.font(fontName);
|
|
226
|
+
doc.fontSize(tb.fontSize ?? 10);
|
|
227
|
+
return doc.heightOfString(tb.text ?? "", { width });
|
|
228
|
+
};
|
|
229
|
+
const buildKeyBlock = (item) => ({
|
|
230
|
+
type: "text",
|
|
231
|
+
text: item.key ?? "",
|
|
232
|
+
align: block.keyAlign ?? "left",
|
|
233
|
+
...block.keyTextStyle ?? {},
|
|
234
|
+
...item.keyTextStyle ?? {}
|
|
235
|
+
});
|
|
236
|
+
const buildValueBlock = (item) => ({
|
|
237
|
+
type: "text",
|
|
238
|
+
text: item.value ?? "",
|
|
239
|
+
align: block.valueAlign ?? "left",
|
|
240
|
+
...block.valueTextStyle ?? {},
|
|
241
|
+
...item.valueTextStyle ?? {}
|
|
242
|
+
});
|
|
243
|
+
let totalHeight = mt;
|
|
244
|
+
if (orientation === "vertical") {
|
|
245
|
+
const keyValueGap = block.verticalKeyValueGap ?? 2;
|
|
246
|
+
let maxColHeight = 0;
|
|
247
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
248
|
+
const col = cols[colIndex];
|
|
249
|
+
const colWidth = colWidths[colIndex];
|
|
250
|
+
let colHeight = 0;
|
|
251
|
+
for (const item of col) {
|
|
252
|
+
const keyStyleNames = item.keyStyle ?? block.keyStyle ?? void 0;
|
|
253
|
+
const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
|
|
254
|
+
const keyBase = buildKeyBlock(item);
|
|
255
|
+
const keyResolved = resolveStyled(keyBase, keyStyleNames);
|
|
256
|
+
const keyHeight = measureTextHeight(keyResolved, colWidth);
|
|
257
|
+
const valueBase = buildValueBlock(item);
|
|
258
|
+
const valueResolved = resolveStyled(valueBase, valueStyleNames);
|
|
259
|
+
const valueHeight = measureTextHeight(valueResolved, colWidth);
|
|
260
|
+
const rowHeight = keyHeight + keyValueGap + valueHeight;
|
|
261
|
+
colHeight += rowHeight + rowGap;
|
|
262
|
+
}
|
|
263
|
+
if (colHeight > maxColHeight) maxColHeight = colHeight;
|
|
264
|
+
}
|
|
265
|
+
totalHeight += maxColHeight;
|
|
266
|
+
} else {
|
|
267
|
+
for (let rowIndex = 0; rowIndex < maxRows; rowIndex++) {
|
|
268
|
+
let rowHeight = 0;
|
|
269
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
270
|
+
const col = cols[colIndex];
|
|
271
|
+
const item = col[rowIndex];
|
|
272
|
+
if (!item) continue;
|
|
273
|
+
const colWidth = colWidths[colIndex];
|
|
274
|
+
const keyWidthPx = block.keyWidth === "*" ? Math.max(colWidth * baseKeyWidthRatio, 20) : block.keyWidth ?? 80;
|
|
275
|
+
const keyStyleNames = item.keyStyle ?? block.keyStyle ?? void 0;
|
|
276
|
+
const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
|
|
277
|
+
const keyBase = buildKeyBlock(item);
|
|
278
|
+
const keyResolved = resolveStyled(keyBase, keyStyleNames);
|
|
279
|
+
const keyHeight = measureTextHeight(keyResolved, keyWidthPx);
|
|
280
|
+
const valueBase = buildValueBlock(item);
|
|
281
|
+
const valueResolved = resolveStyled(valueBase, valueStyleNames);
|
|
282
|
+
const valueXInsideCol = keyWidthPx + (separatorText ? sepBoxWidth + 4 : 4);
|
|
283
|
+
const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
|
|
284
|
+
const valueHeight = measureTextHeight(valueResolved, valueWidth);
|
|
285
|
+
const cellHeight = Math.max(keyHeight, valueHeight);
|
|
286
|
+
if (cellHeight > rowHeight) rowHeight = cellHeight;
|
|
287
|
+
}
|
|
288
|
+
if (rowHeight > 0) {
|
|
289
|
+
totalHeight += rowHeight + rowGap;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
totalHeight += mb;
|
|
294
|
+
if (y === null) {
|
|
295
|
+
ensureSpaceFor(totalHeight, env);
|
|
296
|
+
}
|
|
297
|
+
const baseY = y ?? ctx.currentY;
|
|
298
|
+
let rowY = baseY + mt;
|
|
299
|
+
if (orientation === "vertical") {
|
|
300
|
+
const keyValueGap = block.verticalKeyValueGap ?? 2;
|
|
301
|
+
let maxColBottom = rowY;
|
|
302
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
303
|
+
const col = cols[colIndex];
|
|
304
|
+
const colWidth = colWidths[colIndex];
|
|
305
|
+
const colX = xBases[colIndex];
|
|
306
|
+
let colY = rowY;
|
|
307
|
+
for (const item of col) {
|
|
308
|
+
const keyStyleNames = item.keyStyle ?? block.keyStyle ?? void 0;
|
|
309
|
+
const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
|
|
310
|
+
const keyBlock = buildKeyBlock(item);
|
|
311
|
+
const keyResolved = resolveStyled(keyBlock, keyStyleNames);
|
|
312
|
+
const keyHeight = measureTextHeight(keyResolved, colWidth);
|
|
313
|
+
drawStyledText(doc, keyResolved, colX, colY, colWidth);
|
|
314
|
+
colY += keyHeight + keyValueGap;
|
|
315
|
+
const valueBlock = buildValueBlock(item);
|
|
316
|
+
const valueResolved = resolveStyled(valueBlock, valueStyleNames);
|
|
317
|
+
const valueHeight = measureTextHeight(valueResolved, colWidth);
|
|
318
|
+
drawStyledText(doc, valueResolved, colX, colY, colWidth);
|
|
319
|
+
colY += valueHeight + rowGap;
|
|
320
|
+
}
|
|
321
|
+
if (colY > maxColBottom) maxColBottom = colY;
|
|
322
|
+
}
|
|
323
|
+
rowY = maxColBottom;
|
|
324
|
+
} else {
|
|
325
|
+
for (let rowIndex = 0; rowIndex < maxRows; rowIndex++) {
|
|
326
|
+
let rowHeight = 0;
|
|
327
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
328
|
+
const col = cols[colIndex];
|
|
329
|
+
const item = col[rowIndex];
|
|
330
|
+
if (!item) continue;
|
|
331
|
+
const colWidth = colWidths[colIndex];
|
|
332
|
+
const keyWidthPx = block.keyWidth === "*" ? Math.max(colWidth * baseKeyWidthRatio, 20) : block.keyWidth ?? 80;
|
|
333
|
+
const keyStyleNames = item.keyStyle ?? block.keyStyle ?? void 0;
|
|
334
|
+
const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
|
|
335
|
+
const keyBase = buildKeyBlock(item);
|
|
336
|
+
const keyResolved = resolveStyled(keyBase, keyStyleNames);
|
|
337
|
+
const keyHeight = measureTextHeight(keyResolved, keyWidthPx);
|
|
338
|
+
const valueBase = buildValueBlock(item);
|
|
339
|
+
const valueResolved = resolveStyled(valueBase, valueStyleNames);
|
|
340
|
+
const valueXInsideCol = keyWidthPx + (separatorText ? sepBoxWidth + 4 : 4);
|
|
341
|
+
const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
|
|
342
|
+
const valueHeight = measureTextHeight(valueResolved, valueWidth);
|
|
343
|
+
const cellHeight = Math.max(keyHeight, valueHeight);
|
|
344
|
+
if (cellHeight > rowHeight) rowHeight = cellHeight;
|
|
345
|
+
}
|
|
346
|
+
if (rowHeight === 0) continue;
|
|
347
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
348
|
+
const col = cols[colIndex];
|
|
349
|
+
const item = col[rowIndex];
|
|
350
|
+
if (!item) continue;
|
|
351
|
+
const colX = xBases[colIndex];
|
|
352
|
+
const colWidth = colWidths[colIndex];
|
|
353
|
+
const keyWidthPx = block.keyWidth === "*" ? Math.max(colWidth * baseKeyWidthRatio, 20) : block.keyWidth ?? 80;
|
|
354
|
+
const keyStyleNames = item.keyStyle ?? block.keyStyle ?? void 0;
|
|
355
|
+
const valueStyleNames = item.valueStyle ?? block.valueStyle ?? void 0;
|
|
356
|
+
const keyBlock = buildKeyBlock(item);
|
|
357
|
+
const keyResolved = resolveStyled(keyBlock, keyStyleNames);
|
|
358
|
+
drawStyledText(doc, keyResolved, colX, rowY, keyWidthPx);
|
|
359
|
+
if (separatorText) {
|
|
360
|
+
const sepBlock = {
|
|
361
|
+
type: "text",
|
|
362
|
+
text: separatorText,
|
|
363
|
+
align: "left"
|
|
364
|
+
};
|
|
365
|
+
const sepResolved = resolveStyled(sepBlock, keyStyleNames);
|
|
366
|
+
const sepX = colX + keyWidthPx + 2;
|
|
367
|
+
drawStyledText(doc, sepResolved, sepX, rowY, sepBoxWidth);
|
|
368
|
+
}
|
|
369
|
+
const valueX = colX + keyWidthPx + (separatorText ? sepBoxWidth + 4 : 4);
|
|
370
|
+
const valueWidth = Math.max(colWidth - (valueX - colX), 1);
|
|
371
|
+
const valueBlock = buildValueBlock(item);
|
|
372
|
+
const valueResolved = resolveStyled(valueBlock, valueStyleNames);
|
|
373
|
+
drawStyledText(doc, valueResolved, valueX, rowY, valueWidth);
|
|
374
|
+
}
|
|
375
|
+
rowY += rowHeight + rowGap;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const endY = rowY + mb;
|
|
379
|
+
if (y === null) ctx.currentY = endY;
|
|
380
|
+
return endY;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/renderer-engine/blocks/line.ts
|
|
384
|
+
var processLineBlock = (doc, ctx, block, y, env, ensureSpaceFor) => {
|
|
385
|
+
const localLeft = block.marginLeft ?? 0;
|
|
386
|
+
const localRight = block.marginRight ?? 0;
|
|
387
|
+
const xLeft = env.marginLeft + localLeft;
|
|
388
|
+
const width = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
389
|
+
const mt = block.marginTop ?? 0;
|
|
390
|
+
const mb = block.marginBottom ?? 0;
|
|
391
|
+
const baseY = y ?? ctx.currentY;
|
|
392
|
+
const startY = baseY + mt;
|
|
393
|
+
const lineWidth = block.lineWidth ?? 1;
|
|
394
|
+
const heightNeeded = mt + lineWidth + mb;
|
|
395
|
+
if (y === null) {
|
|
396
|
+
ensureSpaceFor(heightNeeded, env);
|
|
397
|
+
}
|
|
398
|
+
doc.save();
|
|
399
|
+
if (block.color) {
|
|
400
|
+
doc.strokeColor(block.color);
|
|
401
|
+
}
|
|
402
|
+
doc.lineWidth(lineWidth);
|
|
403
|
+
doc.moveTo(xLeft, startY).lineTo(xLeft + width, startY).stroke();
|
|
404
|
+
doc.restore();
|
|
405
|
+
const newY = startY + lineWidth + mb;
|
|
406
|
+
if (y === null) ctx.currentY = newY;
|
|
407
|
+
return newY;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
// src/renderer-engine/blocks/table.ts
|
|
411
|
+
var measureTableHeight = (doc, table, env, styles, computeColumnPixelWidths2) => {
|
|
412
|
+
const localLeft = table.marginLeft ?? 0;
|
|
413
|
+
const localRight = table.marginRight ?? 0;
|
|
414
|
+
const width = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
415
|
+
const colWidths = computeColumnPixelWidths2(table.widths, width);
|
|
416
|
+
let totalHeight = 0;
|
|
417
|
+
totalHeight += table.marginTop ?? 0;
|
|
418
|
+
table.body.forEach((row) => {
|
|
419
|
+
let rowHeight = 0;
|
|
420
|
+
row.forEach((rawCell, colIndex) => {
|
|
421
|
+
const cell = resolveTableCell(styles, rawCell);
|
|
422
|
+
const colW = colWidths[colIndex] ?? 50;
|
|
423
|
+
const fontSize = cell.fontSize ?? 10;
|
|
424
|
+
const isBold = !!cell.bold;
|
|
425
|
+
const isItalic = !!cell.italic;
|
|
426
|
+
const fontName = cell.font ? cell.font : isBold && isItalic ? "Helvetica-BoldOblique" : isBold ? "Helvetica-Bold" : isItalic ? "Helvetica-Oblique" : "Helvetica";
|
|
427
|
+
doc.font(fontName);
|
|
428
|
+
doc.fontSize(fontSize);
|
|
429
|
+
const h = doc.heightOfString(cell.text, { width: colW });
|
|
430
|
+
rowHeight = Math.max(rowHeight, h + 4);
|
|
431
|
+
});
|
|
432
|
+
totalHeight += rowHeight;
|
|
433
|
+
});
|
|
434
|
+
totalHeight += table.lineGap ?? 6;
|
|
435
|
+
totalHeight += table.marginBottom ?? 0;
|
|
436
|
+
return totalHeight;
|
|
437
|
+
};
|
|
438
|
+
var processTableBlock = (doc, ctx, styles, table, y, env, computeColumnPixelWidths2, bottomLimitForContent, finishPage2) => {
|
|
439
|
+
const localLeft = table.marginLeft ?? 0;
|
|
440
|
+
const localRight = table.marginRight ?? 0;
|
|
441
|
+
const baseLeft = env.marginLeft + localLeft;
|
|
442
|
+
const width = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
443
|
+
const mt = table.marginTop ?? 0;
|
|
444
|
+
const mb = table.marginBottom ?? 0;
|
|
445
|
+
const baseY = y ?? ctx.currentY;
|
|
446
|
+
const startY = baseY + mt;
|
|
447
|
+
const colWidths = computeColumnPixelWidths2(table.widths, width);
|
|
448
|
+
const layout = table.layout ?? {
|
|
449
|
+
border: "all",
|
|
450
|
+
hLineColor: "#ccc",
|
|
451
|
+
vLineColor: "#ccc"
|
|
452
|
+
};
|
|
453
|
+
const borderAll = layout.border !== "none";
|
|
454
|
+
const headerRows = table.headerRows ?? 0;
|
|
455
|
+
const measureRowHeight = (row) => {
|
|
456
|
+
let rowHeight = 0;
|
|
457
|
+
row.forEach((rawCell, colIndex) => {
|
|
458
|
+
const cell = resolveTableCell(styles, rawCell);
|
|
459
|
+
const colW = colWidths[colIndex] ?? 50;
|
|
460
|
+
const fontSize = cell.fontSize ?? 10;
|
|
461
|
+
const fontName = cell.font ? cell.font : cell.bold ? "Helvetica-Bold" : "Helvetica";
|
|
462
|
+
doc.font(fontName);
|
|
463
|
+
doc.fontSize(fontSize);
|
|
464
|
+
const textWidth = Math.max(colW - 4, 1);
|
|
465
|
+
const h = doc.heightOfString(cell.text, {
|
|
466
|
+
width: textWidth
|
|
467
|
+
}) + 4;
|
|
468
|
+
rowHeight = Math.max(rowHeight, h);
|
|
469
|
+
});
|
|
470
|
+
return rowHeight || 12;
|
|
471
|
+
};
|
|
472
|
+
const drawRow = (row, rowTop2, rowHeight, isHeaderRow, drawTopBorder) => {
|
|
473
|
+
row.forEach((rawCell, colIndex) => {
|
|
474
|
+
const cell = resolveTableCell(styles, rawCell);
|
|
475
|
+
const colW = colWidths[colIndex] ?? 50;
|
|
476
|
+
const x = baseLeft + colWidths.slice(0, colIndex).reduce((a, b) => a + b, 0);
|
|
477
|
+
const fontSize = cell.fontSize ?? 10;
|
|
478
|
+
doc.save();
|
|
479
|
+
if (cell.fillColor) {
|
|
480
|
+
doc.rect(x, rowTop2, colW, rowHeight).fillOpacity(1).fill(cell.fillColor);
|
|
481
|
+
doc.fillOpacity(1);
|
|
482
|
+
}
|
|
483
|
+
const isBold = !!cell.bold;
|
|
484
|
+
const isItalic = !!cell.italic;
|
|
485
|
+
const fontName = cell.font ? cell.font : isBold && isItalic ? "Helvetica-BoldOblique" : isBold ? "Helvetica-Bold" : isItalic ? "Helvetica-Oblique" : "Helvetica";
|
|
486
|
+
doc.font(fontName);
|
|
487
|
+
doc.fontSize(fontSize);
|
|
488
|
+
if (cell.color) {
|
|
489
|
+
doc.fillColor(cell.color);
|
|
490
|
+
} else {
|
|
491
|
+
doc.fillColor("black");
|
|
492
|
+
}
|
|
493
|
+
const align = cell.align ?? "left";
|
|
494
|
+
const textWidth = Math.max(colW - 4, 1);
|
|
495
|
+
const textHeight = doc.heightOfString(cell.text, {
|
|
496
|
+
width: textWidth
|
|
497
|
+
});
|
|
498
|
+
let textY = rowTop2 + 2;
|
|
499
|
+
if (isHeaderRow) {
|
|
500
|
+
const available = rowHeight - 4;
|
|
501
|
+
const offset = Math.max((available - textHeight) / 2, 0);
|
|
502
|
+
textY = rowTop2 + 2 + offset;
|
|
503
|
+
}
|
|
504
|
+
doc.text(cell.text, x + 2, textY, {
|
|
505
|
+
width: textWidth,
|
|
506
|
+
align,
|
|
507
|
+
underline: !!cell.underline,
|
|
508
|
+
strike: !!cell.strike,
|
|
509
|
+
link: cell.link
|
|
510
|
+
});
|
|
511
|
+
doc.restore();
|
|
512
|
+
});
|
|
513
|
+
if (borderAll) {
|
|
514
|
+
const bottomY = rowTop2 + rowHeight;
|
|
515
|
+
doc.save();
|
|
516
|
+
doc.lineWidth(0.5);
|
|
517
|
+
doc.strokeColor(layout.hLineColor ?? "#ccc");
|
|
518
|
+
if (drawTopBorder) {
|
|
519
|
+
doc.moveTo(baseLeft, rowTop2).lineTo(baseLeft + width, rowTop2).stroke();
|
|
520
|
+
}
|
|
521
|
+
doc.moveTo(baseLeft, bottomY).lineTo(baseLeft + width, bottomY).stroke();
|
|
522
|
+
doc.restore();
|
|
523
|
+
doc.save();
|
|
524
|
+
doc.lineWidth(0.5);
|
|
525
|
+
doc.strokeColor(layout.vLineColor ?? "#ccc");
|
|
526
|
+
let x = baseLeft;
|
|
527
|
+
colWidths.forEach((colW) => {
|
|
528
|
+
doc.moveTo(x, rowTop2).lineTo(x, bottomY).stroke();
|
|
529
|
+
x += colW;
|
|
530
|
+
});
|
|
531
|
+
doc.moveTo(baseLeft + width, rowTop2).lineTo(baseLeft + width, bottomY).stroke();
|
|
532
|
+
doc.restore();
|
|
533
|
+
}
|
|
534
|
+
return rowTop2 + rowHeight;
|
|
535
|
+
};
|
|
536
|
+
const drawHeaderRowsOnNewPage = () => {
|
|
537
|
+
if (!headerRows) return doc.page.margins.top + mt;
|
|
538
|
+
let rowTop2 = doc.page.margins.top + mt;
|
|
539
|
+
for (let i = 0; i < headerRows; i++) {
|
|
540
|
+
const headerRow = table.body[i];
|
|
541
|
+
const headerHeight = measureRowHeight(headerRow);
|
|
542
|
+
rowTop2 = drawRow(headerRow, rowTop2, headerHeight, true, true);
|
|
543
|
+
}
|
|
544
|
+
return rowTop2;
|
|
545
|
+
};
|
|
546
|
+
let rowTop = startY;
|
|
547
|
+
table.body.forEach((row, rowIndex) => {
|
|
548
|
+
const isHeaderRow = rowIndex < headerRows;
|
|
549
|
+
const rowHeight = measureRowHeight(row);
|
|
550
|
+
const bottomLimit = bottomLimitForContent();
|
|
551
|
+
if (rowTop + rowHeight > bottomLimit) {
|
|
552
|
+
finishPage2(true);
|
|
553
|
+
rowTop = headerRows ? drawHeaderRowsOnNewPage() : doc.page.margins.top + mt;
|
|
554
|
+
}
|
|
555
|
+
const drawTopBorder = true;
|
|
556
|
+
rowTop = drawRow(row, rowTop, rowHeight, isHeaderRow, drawTopBorder);
|
|
557
|
+
});
|
|
558
|
+
const newY = rowTop + (table.lineGap ?? 6) + mb;
|
|
559
|
+
if (y === null) ctx.currentY = newY;
|
|
560
|
+
return newY;
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// src/renderer-engine/blocks/text.ts
|
|
564
|
+
var processTextBlock = (doc, ctx, styles, block, y, env, ensureSpaceFor) => {
|
|
565
|
+
const tb = resolveTextBlock(styles, block);
|
|
566
|
+
const localLeft = tb.marginLeft ?? 0;
|
|
567
|
+
const localRight = tb.marginRight ?? 0;
|
|
568
|
+
const xLeft = env.marginLeft + localLeft;
|
|
569
|
+
const width = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
570
|
+
const baseY = y ?? ctx.currentY;
|
|
571
|
+
const startY = baseY;
|
|
572
|
+
doc.fontSize(tb.fontSize ?? 10);
|
|
573
|
+
const isBold = !!tb.bold;
|
|
574
|
+
const isItalic = !!tb.italic;
|
|
575
|
+
let fontName = tb.font;
|
|
576
|
+
if (!fontName) {
|
|
577
|
+
if (isBold && isItalic) fontName = "Helvetica-BoldOblique";
|
|
578
|
+
else if (isBold) fontName = "Helvetica-Bold";
|
|
579
|
+
else if (isItalic) fontName = "Helvetica-Oblique";
|
|
580
|
+
else fontName = "Helvetica";
|
|
581
|
+
}
|
|
582
|
+
doc.font(fontName);
|
|
583
|
+
if (tb.color) doc.fillColor(tb.color);
|
|
584
|
+
else doc.fillColor("black");
|
|
585
|
+
const textHeight = doc.heightOfString(tb.text, { width });
|
|
586
|
+
if (y === null) {
|
|
587
|
+
const gapCheck = tb.lineGap ?? 4;
|
|
588
|
+
ensureSpaceFor(textHeight + gapCheck, env);
|
|
589
|
+
}
|
|
590
|
+
doc.text(tb.text, xLeft, startY, {
|
|
591
|
+
width,
|
|
592
|
+
align: tb.align ?? "left",
|
|
593
|
+
underline: !!tb.underline,
|
|
594
|
+
strike: !!tb.strike,
|
|
595
|
+
link: tb.link
|
|
596
|
+
});
|
|
597
|
+
const gap = tb.lineGap ?? 4;
|
|
598
|
+
const newY = startY + textHeight + gap;
|
|
599
|
+
if (y === null) ctx.currentY = newY;
|
|
600
|
+
return newY;
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// src/renderer-engine/utils/block-renderer.ts
|
|
604
|
+
function createBlockRenderer(deps) {
|
|
605
|
+
const {
|
|
606
|
+
doc,
|
|
607
|
+
ctx,
|
|
608
|
+
styles,
|
|
609
|
+
computeColumnPixelWidths: computeColumnPixelWidths2,
|
|
610
|
+
finishPage: finishPage2,
|
|
611
|
+
processSignatureBlock
|
|
612
|
+
} = deps;
|
|
613
|
+
const bottomLimitForContent = createBottomLimitForContent(doc, ctx);
|
|
614
|
+
const ensureSpaceFor = createEnsureSpaceFor(
|
|
615
|
+
ctx,
|
|
616
|
+
bottomLimitForContent,
|
|
617
|
+
finishPage2
|
|
618
|
+
);
|
|
619
|
+
const measureBlockHeight = createMeasureBlockHeight({
|
|
620
|
+
doc,
|
|
621
|
+
styles,
|
|
622
|
+
computeColumnPixelWidths: computeColumnPixelWidths2
|
|
623
|
+
});
|
|
624
|
+
const renderBlock = (block, y, env) => {
|
|
625
|
+
if (block.visible === false) {
|
|
626
|
+
return y ?? ctx.currentY;
|
|
627
|
+
}
|
|
628
|
+
switch (block.type) {
|
|
629
|
+
case "text":
|
|
630
|
+
return processTextBlock(
|
|
631
|
+
doc,
|
|
632
|
+
ctx,
|
|
633
|
+
styles,
|
|
634
|
+
block,
|
|
635
|
+
y,
|
|
636
|
+
env,
|
|
637
|
+
ensureSpaceFor
|
|
638
|
+
);
|
|
639
|
+
case "image":
|
|
640
|
+
return processImageBlock(
|
|
641
|
+
doc,
|
|
642
|
+
ctx,
|
|
643
|
+
block,
|
|
644
|
+
y,
|
|
645
|
+
env,
|
|
646
|
+
ensureSpaceFor
|
|
647
|
+
);
|
|
648
|
+
case "qr": {
|
|
649
|
+
const qb = block;
|
|
650
|
+
const imageLike = {
|
|
651
|
+
type: "image",
|
|
652
|
+
src: qb.src,
|
|
653
|
+
width: qb.size,
|
|
654
|
+
height: qb.size,
|
|
655
|
+
align: qb.align,
|
|
656
|
+
marginTop: qb.marginTop,
|
|
657
|
+
marginBottom: qb.marginBottom,
|
|
658
|
+
marginLeft: qb.marginLeft,
|
|
659
|
+
marginRight: qb.marginRight
|
|
660
|
+
};
|
|
661
|
+
return processImageBlock(doc, ctx, imageLike, y, env, ensureSpaceFor);
|
|
662
|
+
}
|
|
663
|
+
case "barcode":
|
|
664
|
+
return processBarcodeBlock(doc, ctx, block, y, env, ensureSpaceFor);
|
|
665
|
+
case "line":
|
|
666
|
+
return processLineBlock(
|
|
667
|
+
doc,
|
|
668
|
+
ctx,
|
|
669
|
+
block,
|
|
670
|
+
y,
|
|
671
|
+
env,
|
|
672
|
+
ensureSpaceFor
|
|
673
|
+
);
|
|
674
|
+
case "table":
|
|
675
|
+
return processTableBlock(
|
|
676
|
+
doc,
|
|
677
|
+
ctx,
|
|
678
|
+
styles,
|
|
679
|
+
block,
|
|
680
|
+
y,
|
|
681
|
+
env,
|
|
682
|
+
computeColumnPixelWidths2,
|
|
683
|
+
bottomLimitForContent,
|
|
684
|
+
finishPage2
|
|
685
|
+
);
|
|
686
|
+
case "columns":
|
|
687
|
+
return processColumnsBlock(
|
|
688
|
+
ctx,
|
|
689
|
+
block,
|
|
690
|
+
y,
|
|
691
|
+
env,
|
|
692
|
+
computeColumnPixelWidths2,
|
|
693
|
+
renderBlock,
|
|
694
|
+
ensureSpaceFor,
|
|
695
|
+
measureBlockHeight
|
|
696
|
+
);
|
|
697
|
+
case "keyValueGrid":
|
|
698
|
+
return processKeyValueGridBlock(
|
|
699
|
+
doc,
|
|
700
|
+
ctx,
|
|
701
|
+
styles,
|
|
702
|
+
block,
|
|
703
|
+
y,
|
|
704
|
+
env,
|
|
705
|
+
computeColumnPixelWidths2,
|
|
706
|
+
ensureSpaceFor
|
|
707
|
+
);
|
|
708
|
+
case "signature":
|
|
709
|
+
if (!env.allowPageBreak) {
|
|
710
|
+
throw new Error(
|
|
711
|
+
"Signature block is only allowed in main content flow."
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
if (y !== null) {
|
|
715
|
+
throw new Error(
|
|
716
|
+
"Signature block must be part of main flow, not drawn at explicit Y."
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
processSignatureBlock(block);
|
|
720
|
+
return ctx.currentY;
|
|
721
|
+
case "pageBreak":
|
|
722
|
+
if (!env.allowPageBreak || ctx.inFooter) {
|
|
723
|
+
return y ?? ctx.currentY;
|
|
724
|
+
}
|
|
725
|
+
finishPage2(true);
|
|
726
|
+
return ctx.currentY;
|
|
727
|
+
default:
|
|
728
|
+
return ctx.currentY;
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
const renderBlockArray = (blocks, startY, env) => {
|
|
732
|
+
let localY = startY;
|
|
733
|
+
for (const block of blocks) {
|
|
734
|
+
if (block.visible === false) {
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
localY = renderBlock(block, localY, env);
|
|
738
|
+
}
|
|
739
|
+
return localY;
|
|
740
|
+
};
|
|
741
|
+
return {
|
|
742
|
+
renderBlock,
|
|
743
|
+
renderBlockArray
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/renderer-engine/utils/context.ts
|
|
748
|
+
function createInitialContext(doc) {
|
|
749
|
+
return {
|
|
750
|
+
pageNumber: 1,
|
|
751
|
+
currentY: doc.page.margins.top,
|
|
752
|
+
signatureBlock: null,
|
|
753
|
+
signatureTopY: null,
|
|
754
|
+
signatureHeight: 0,
|
|
755
|
+
signaturePlaced: false,
|
|
756
|
+
afterSignature: false,
|
|
757
|
+
inFooter: false
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/renderer-engine/utils/ensure-space.ts
|
|
762
|
+
function createEnsureSpaceFor(ctx, bottomLimitForContent, finishPage2) {
|
|
763
|
+
return (heightNeeded, env) => {
|
|
764
|
+
if (!env.allowPageBreak || ctx.inFooter) return;
|
|
765
|
+
const bottomLimit = bottomLimitForContent();
|
|
766
|
+
if (ctx.currentY + heightNeeded > bottomLimit) {
|
|
767
|
+
finishPage2(true);
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// src/renderer-engine/utils/env.ts
|
|
773
|
+
var contentEnv = (doc) => ({
|
|
774
|
+
marginLeft: doc.page.margins.left,
|
|
775
|
+
innerWidth: doc.page.width - doc.page.margins.left - doc.page.margins.right,
|
|
776
|
+
allowPageBreak: true
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// src/renderer-engine/utils/finish-page.ts
|
|
780
|
+
function finishPage({
|
|
781
|
+
addNewPage,
|
|
782
|
+
doc,
|
|
783
|
+
def,
|
|
784
|
+
ctx,
|
|
785
|
+
footerBandHeight,
|
|
786
|
+
renderBlockArray,
|
|
787
|
+
startNewPageLayout
|
|
788
|
+
}) {
|
|
789
|
+
const mode = def.watermark?.mode;
|
|
790
|
+
if (def.watermark && watermarkUsesLast(mode)) {
|
|
791
|
+
const isLast = !addNewPage;
|
|
792
|
+
drawWatermarkForPage(doc, def.watermark, ctx.pageNumber, isLast);
|
|
793
|
+
}
|
|
794
|
+
if (ctx.signatureBlock && ctx.signatureTopY !== null) {
|
|
795
|
+
const env = contentEnv(doc);
|
|
796
|
+
drawSignatureBlock(
|
|
797
|
+
doc,
|
|
798
|
+
ctx.signatureBlock,
|
|
799
|
+
ctx.signatureTopY,
|
|
800
|
+
env,
|
|
801
|
+
renderBlockArray
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
drawFooter(doc, def, ctx, footerBandHeight, renderBlockArray);
|
|
805
|
+
if (addNewPage) {
|
|
806
|
+
doc.addPage();
|
|
807
|
+
ctx.pageNumber += 1;
|
|
808
|
+
startNewPageLayout();
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/renderer-engine/utils/footer.ts
|
|
813
|
+
function normalizeFooter(input, ctx, doc) {
|
|
814
|
+
if (!input) return null;
|
|
815
|
+
if (typeof input === "function") {
|
|
816
|
+
const result = input(ctx.pageNumber, {
|
|
817
|
+
width: doc.page.width,
|
|
818
|
+
height: doc.page.height
|
|
819
|
+
});
|
|
820
|
+
if (!result) return null;
|
|
821
|
+
return normalizeFooter(result, ctx, doc);
|
|
822
|
+
}
|
|
823
|
+
if (input.blocks && Array.isArray(input.blocks)) {
|
|
824
|
+
const f = input;
|
|
825
|
+
return {
|
|
826
|
+
visible: f.visible,
|
|
827
|
+
blocks: f.blocks,
|
|
828
|
+
marginTop: f.marginTop,
|
|
829
|
+
marginBottom: f.marginBottom,
|
|
830
|
+
marginLeft: f.marginLeft,
|
|
831
|
+
marginRight: f.marginRight,
|
|
832
|
+
backgroundColor: f.backgroundColor,
|
|
833
|
+
backgroundImage: f.backgroundImage
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
if (input.type) {
|
|
837
|
+
return { blocks: [input] };
|
|
838
|
+
}
|
|
839
|
+
if (Array.isArray(input)) {
|
|
840
|
+
return { blocks: input };
|
|
841
|
+
}
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
function drawFooter(doc, def, ctx, footerBandHeight, renderBlockArray) {
|
|
845
|
+
const footerConfig = def.footer;
|
|
846
|
+
if (!footerConfig) return;
|
|
847
|
+
const layout = normalizeFooter(footerConfig, ctx, doc);
|
|
848
|
+
if (!layout || !layout.blocks.length) return;
|
|
849
|
+
if (layout.visible === false) return;
|
|
850
|
+
const bandHeight = footerBandHeight;
|
|
851
|
+
if (!bandHeight) return;
|
|
852
|
+
const { marginTop = 4, backgroundColor, backgroundImage, blocks } = layout;
|
|
853
|
+
const footerMarginLeft = layout.marginLeft ?? 0;
|
|
854
|
+
const footerMarginRight = layout.marginRight ?? 0;
|
|
855
|
+
const contentWidth = doc.page.width - footerMarginLeft - footerMarginRight;
|
|
856
|
+
const footerEnv = {
|
|
857
|
+
marginLeft: footerMarginLeft,
|
|
858
|
+
innerWidth: contentWidth,
|
|
859
|
+
allowPageBreak: false
|
|
860
|
+
};
|
|
861
|
+
const pageHeight = doc.page.height;
|
|
862
|
+
const bandTop = pageHeight - bandHeight;
|
|
863
|
+
if (backgroundColor) {
|
|
864
|
+
doc.save();
|
|
865
|
+
doc.rect(0, bandTop, doc.page.width, bandHeight).fill(backgroundColor);
|
|
866
|
+
doc.restore();
|
|
867
|
+
}
|
|
868
|
+
if (backgroundImage) {
|
|
869
|
+
try {
|
|
870
|
+
doc.image(backgroundImage, 0, bandTop, {
|
|
871
|
+
width: doc.page.width,
|
|
872
|
+
height: bandHeight
|
|
873
|
+
});
|
|
874
|
+
} catch (e) {
|
|
875
|
+
console.warn("Failed to load footer background image:", e);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
doc.save();
|
|
879
|
+
doc.rect(footerMarginLeft, bandTop, contentWidth, bandHeight).clip();
|
|
880
|
+
doc.translate(0, bandTop);
|
|
881
|
+
ctx.inFooter = true;
|
|
882
|
+
const localStartY = marginTop;
|
|
883
|
+
renderBlockArray(blocks, localStartY, footerEnv);
|
|
884
|
+
ctx.inFooter = false;
|
|
885
|
+
doc.restore();
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/renderer-engine/utils/header.ts
|
|
889
|
+
function drawHeader(doc, def, headerBandHeight, header, renderBlockArray) {
|
|
890
|
+
if (!header || !header.blocks.length) return;
|
|
891
|
+
if (header.visible === false) return;
|
|
892
|
+
if (!headerBandHeight) return;
|
|
893
|
+
const bandTop = 0;
|
|
894
|
+
const bandHeight = def.margins.top;
|
|
895
|
+
const headerMarginLeft = header.marginLeft ?? 0;
|
|
896
|
+
const headerMarginRight = header.marginRight ?? 0;
|
|
897
|
+
const contentWidth = doc.page.width - headerMarginLeft - headerMarginRight;
|
|
898
|
+
const headerEnv = {
|
|
899
|
+
marginLeft: headerMarginLeft,
|
|
900
|
+
innerWidth: contentWidth,
|
|
901
|
+
allowPageBreak: false
|
|
902
|
+
};
|
|
903
|
+
if (header.backgroundColor) {
|
|
904
|
+
doc.save();
|
|
905
|
+
doc.rect(0, bandTop, doc.page.width, bandHeight).fill(header.backgroundColor);
|
|
906
|
+
doc.restore();
|
|
907
|
+
}
|
|
908
|
+
if (header.backgroundImage) {
|
|
909
|
+
try {
|
|
910
|
+
doc.image(header.backgroundImage, 0, bandTop, {
|
|
911
|
+
width: doc.page.width,
|
|
912
|
+
height: bandHeight
|
|
913
|
+
});
|
|
914
|
+
} catch (e) {
|
|
915
|
+
console.warn("Failed to load header background image:", e);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
doc.save();
|
|
919
|
+
doc.rect(headerMarginLeft, bandTop, contentWidth, bandHeight).clip();
|
|
920
|
+
const startY = bandTop + (header.marginTop ?? 0);
|
|
921
|
+
renderBlockArray(header.blocks, startY, headerEnv);
|
|
922
|
+
doc.restore();
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/renderer-engine/utils/image-loader.ts
|
|
926
|
+
import axios from "axios";
|
|
927
|
+
async function normalizeImageSrc(src) {
|
|
928
|
+
if (!src) return src;
|
|
929
|
+
if (Buffer.isBuffer(src)) return src;
|
|
930
|
+
if (src.startsWith("http://") || src.startsWith("https://")) {
|
|
931
|
+
const res = await axios.get(src, {
|
|
932
|
+
responseType: "arraybuffer"
|
|
933
|
+
});
|
|
934
|
+
return Buffer.from(res.data);
|
|
935
|
+
}
|
|
936
|
+
return src;
|
|
937
|
+
}
|
|
938
|
+
async function materializeImagesInBlocks(blocks) {
|
|
939
|
+
const out = [];
|
|
940
|
+
for (const block of blocks) {
|
|
941
|
+
if (block.type === "image") {
|
|
942
|
+
const img = { ...block };
|
|
943
|
+
img.src = await normalizeImageSrc(img.src);
|
|
944
|
+
out.push(img);
|
|
945
|
+
} else if (block.type === "columns") {
|
|
946
|
+
out.push({
|
|
947
|
+
...block,
|
|
948
|
+
columns: await Promise.all(
|
|
949
|
+
block.columns.map((col) => materializeImagesInBlocks(col))
|
|
950
|
+
)
|
|
951
|
+
});
|
|
952
|
+
} else if (block.type === "signature" && block.blocks) {
|
|
953
|
+
out.push({
|
|
954
|
+
...block,
|
|
955
|
+
blocks: await materializeImagesInBlocks(block.blocks)
|
|
956
|
+
});
|
|
957
|
+
} else {
|
|
958
|
+
out.push(block);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return out;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/renderer-engine/utils/layout.ts
|
|
965
|
+
function computeColumnPixelWidths(widths, totalWidth) {
|
|
966
|
+
let fixedTotal = 0;
|
|
967
|
+
let starCount = 0;
|
|
968
|
+
widths.forEach((w) => {
|
|
969
|
+
if (w === "*") starCount++;
|
|
970
|
+
else fixedTotal += w;
|
|
971
|
+
});
|
|
972
|
+
const remaining = Math.max(totalWidth - fixedTotal, 0);
|
|
973
|
+
const starWidth = starCount > 0 ? remaining / starCount : 0;
|
|
974
|
+
return widths.map((w) => w === "*" ? starWidth : w);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// src/renderer-engine/utils/styles.ts
|
|
978
|
+
function mergeStyleDefs(styles, names) {
|
|
979
|
+
const result = {};
|
|
980
|
+
if (!names || !styles) return result;
|
|
981
|
+
const arr = Array.isArray(names) ? names : [names];
|
|
982
|
+
for (const name of arr) {
|
|
983
|
+
const st = styles[name];
|
|
984
|
+
if (!st) continue;
|
|
985
|
+
if (st.fontSize !== void 0) result.fontSize = st.fontSize;
|
|
986
|
+
if (st.lineGap !== void 0) result.lineGap = st.lineGap;
|
|
987
|
+
if (st.align !== void 0) result.align = st.align;
|
|
988
|
+
if (st.bold !== void 0) result.bold = st.bold;
|
|
989
|
+
if (st.italic !== void 0) result.italic = st.italic;
|
|
990
|
+
if (st.underline !== void 0) result.underline = st.underline;
|
|
991
|
+
if (st.strike !== void 0) result.strike = st.strike;
|
|
992
|
+
if (st.link !== void 0) result.link = st.link;
|
|
993
|
+
if (st.color !== void 0) result.color = st.color;
|
|
994
|
+
if (st.fillColor !== void 0) result.fillColor = st.fillColor;
|
|
995
|
+
if (st.font !== void 0) result.font = st.font;
|
|
996
|
+
}
|
|
997
|
+
return result;
|
|
998
|
+
}
|
|
999
|
+
function resolveTextBlock(styles, block) {
|
|
1000
|
+
const styleDef = mergeStyleDefs(styles, block.style);
|
|
1001
|
+
return {
|
|
1002
|
+
...styleDef,
|
|
1003
|
+
...block,
|
|
1004
|
+
fontSize: block.fontSize ?? styleDef.fontSize,
|
|
1005
|
+
lineGap: block.lineGap ?? styleDef.lineGap,
|
|
1006
|
+
align: block.align ?? styleDef.align,
|
|
1007
|
+
bold: block.bold ?? styleDef.bold,
|
|
1008
|
+
italic: block.italic ?? styleDef.italic,
|
|
1009
|
+
underline: block.underline ?? styleDef.underline,
|
|
1010
|
+
strike: block.strike ?? styleDef.strike,
|
|
1011
|
+
link: block.link ?? styleDef.link,
|
|
1012
|
+
color: block.color ?? styleDef.color,
|
|
1013
|
+
// fillColor: block.fillColor ?? styleDef.fillColor,
|
|
1014
|
+
font: block.font ?? styleDef.font
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
function resolveTableCell(styles, cell) {
|
|
1018
|
+
const styleDef = mergeStyleDefs(styles, cell.style);
|
|
1019
|
+
return {
|
|
1020
|
+
...styleDef,
|
|
1021
|
+
...cell,
|
|
1022
|
+
fontSize: cell.fontSize ?? styleDef.fontSize,
|
|
1023
|
+
align: cell.align ?? styleDef.align,
|
|
1024
|
+
bold: cell.bold ?? styleDef.bold,
|
|
1025
|
+
italic: cell.italic ?? styleDef.italic,
|
|
1026
|
+
color: cell.color ?? styleDef.color,
|
|
1027
|
+
fillColor: cell.fillColor ?? styleDef.fillColor,
|
|
1028
|
+
font: cell.font ?? styleDef.font
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
var getFontNameForText = (tb) => {
|
|
1032
|
+
if (tb.font) return tb.font;
|
|
1033
|
+
const bold = tb.bold ?? false;
|
|
1034
|
+
const italic = tb.italic ?? false;
|
|
1035
|
+
if (bold && italic) return "Helvetica-BoldOblique";
|
|
1036
|
+
if (bold) return "Helvetica-Bold";
|
|
1037
|
+
if (italic) return "Helvetica-Oblique";
|
|
1038
|
+
return "Helvetica";
|
|
1039
|
+
};
|
|
1040
|
+
var drawStyledText = (doc, tb, x, y, width) => {
|
|
1041
|
+
doc.save();
|
|
1042
|
+
doc.fontSize(tb.fontSize ?? 10);
|
|
1043
|
+
doc.font(getFontNameForText(tb));
|
|
1044
|
+
if (tb.color) doc.fillColor(tb.color);
|
|
1045
|
+
else doc.fillColor("black");
|
|
1046
|
+
const textHeight = doc.heightOfString(tb.text, { width });
|
|
1047
|
+
doc.text(tb.text, x, y, {
|
|
1048
|
+
width,
|
|
1049
|
+
align: tb.align ?? "left"
|
|
1050
|
+
});
|
|
1051
|
+
const underline = tb.underline;
|
|
1052
|
+
const strike = tb.strike;
|
|
1053
|
+
if (underline) {
|
|
1054
|
+
const lineY = y + textHeight;
|
|
1055
|
+
doc.moveTo(x, lineY).lineTo(x + width, lineY).stroke();
|
|
1056
|
+
}
|
|
1057
|
+
if (strike) {
|
|
1058
|
+
const lineY = y + textHeight / 2;
|
|
1059
|
+
doc.moveTo(x, lineY).lineTo(x + width, lineY).stroke();
|
|
1060
|
+
}
|
|
1061
|
+
doc.restore();
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
// src/renderer-engine/utils/measure-block-height.ts
|
|
1065
|
+
function createMeasureBlockHeight(deps) {
|
|
1066
|
+
const { doc, styles, computeColumnPixelWidths: computeColumnPixelWidths2 } = deps;
|
|
1067
|
+
const measureText = (b, env) => {
|
|
1068
|
+
const tb = resolveTextBlock(styles, b);
|
|
1069
|
+
const width = Math.max(env.innerWidth, 1);
|
|
1070
|
+
doc.font(getFontNameForText(tb));
|
|
1071
|
+
doc.fontSize(tb.fontSize ?? 10);
|
|
1072
|
+
const h = doc.heightOfString(tb.text ?? "", { width });
|
|
1073
|
+
const gap = tb.lineGap ?? 4;
|
|
1074
|
+
return h + gap;
|
|
1075
|
+
};
|
|
1076
|
+
const measureImageLike = (mt, mb, h) => (mt ?? 0) + h + (mb ?? 0);
|
|
1077
|
+
const measureImage = (b) => {
|
|
1078
|
+
const h = b.height ?? 50;
|
|
1079
|
+
return measureImageLike(b.marginTop, b.marginBottom, h);
|
|
1080
|
+
};
|
|
1081
|
+
const measureQr = (b) => {
|
|
1082
|
+
const size = b.size ?? 80;
|
|
1083
|
+
return measureImageLike(b.marginTop, b.marginBottom, size);
|
|
1084
|
+
};
|
|
1085
|
+
const measureBarcode = (b) => {
|
|
1086
|
+
const h = b.height ?? 40;
|
|
1087
|
+
return measureImageLike(b.marginTop, b.marginBottom, h);
|
|
1088
|
+
};
|
|
1089
|
+
const measureLine = (b) => {
|
|
1090
|
+
const lw = b.lineWidth ?? 1;
|
|
1091
|
+
const mt = b.marginTop ?? 0;
|
|
1092
|
+
const mb = b.marginBottom ?? 0;
|
|
1093
|
+
return mt + lw + mb;
|
|
1094
|
+
};
|
|
1095
|
+
const measureTable = (b, env) => {
|
|
1096
|
+
const mt = b.marginTop ?? 0;
|
|
1097
|
+
const mb = b.marginBottom ?? 0;
|
|
1098
|
+
const ml = b.marginLeft ?? 0;
|
|
1099
|
+
const mr = b.marginRight ?? 0;
|
|
1100
|
+
const innerWidth = Math.max(env.innerWidth - ml - mr, 1);
|
|
1101
|
+
const fakeEnv = {
|
|
1102
|
+
marginLeft: 0,
|
|
1103
|
+
innerWidth,
|
|
1104
|
+
allowPageBreak: false
|
|
1105
|
+
};
|
|
1106
|
+
const h = measureTableHeight(
|
|
1107
|
+
doc,
|
|
1108
|
+
b,
|
|
1109
|
+
fakeEnv,
|
|
1110
|
+
styles,
|
|
1111
|
+
computeColumnPixelWidths2
|
|
1112
|
+
);
|
|
1113
|
+
return mt + h + mb;
|
|
1114
|
+
};
|
|
1115
|
+
const measureColumns = (b, env) => {
|
|
1116
|
+
const mt = b.marginTop ?? 0;
|
|
1117
|
+
const mb = b.marginBottom ?? 0;
|
|
1118
|
+
const blockLeft = b.marginLeft ?? 0;
|
|
1119
|
+
const blockRight = b.marginRight ?? 0;
|
|
1120
|
+
const totalWidth = Math.max(env.innerWidth - blockLeft - blockRight, 1);
|
|
1121
|
+
const cols = b.columns ?? [];
|
|
1122
|
+
const n = cols.length;
|
|
1123
|
+
if (!n) return mt + mb;
|
|
1124
|
+
let colWidths;
|
|
1125
|
+
const mode = b.mode ?? "fixedGap";
|
|
1126
|
+
let gap;
|
|
1127
|
+
if (mode === "spaceBetween" && n > 1) {
|
|
1128
|
+
if (b.widths && b.widths.length === n) {
|
|
1129
|
+
colWidths = computeColumnPixelWidths2(b.widths, totalWidth);
|
|
1130
|
+
} else {
|
|
1131
|
+
colWidths = Array(n).fill(totalWidth / n);
|
|
1132
|
+
}
|
|
1133
|
+
const totalColsWidth = colWidths.reduce((a, x) => a + x, 0);
|
|
1134
|
+
const remaining = Math.max(totalWidth - totalColsWidth, 0);
|
|
1135
|
+
gap = remaining / (n - 1);
|
|
1136
|
+
void gap;
|
|
1137
|
+
} else {
|
|
1138
|
+
gap = b.gap ?? 20;
|
|
1139
|
+
const totalGapsWidth = gap * (n - 1);
|
|
1140
|
+
const widthForCols = Math.max(totalWidth - totalGapsWidth, 1);
|
|
1141
|
+
if (b.widths && b.widths.length === n) {
|
|
1142
|
+
colWidths = computeColumnPixelWidths2(b.widths, widthForCols);
|
|
1143
|
+
} else {
|
|
1144
|
+
colWidths = Array(n).fill(widthForCols / n);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
const heights = [];
|
|
1148
|
+
for (let i = 0; i < n; i++) {
|
|
1149
|
+
const colBlocks = cols[i] ?? [];
|
|
1150
|
+
const colEnv = {
|
|
1151
|
+
marginLeft: 0,
|
|
1152
|
+
innerWidth: colWidths[i],
|
|
1153
|
+
allowPageBreak: false
|
|
1154
|
+
};
|
|
1155
|
+
let colH = 0;
|
|
1156
|
+
for (const child of colBlocks) {
|
|
1157
|
+
if (!child || child.visible === false) continue;
|
|
1158
|
+
colH += measure(child, colEnv);
|
|
1159
|
+
}
|
|
1160
|
+
heights.push(colH);
|
|
1161
|
+
}
|
|
1162
|
+
const maxH = Math.max(...heights, 0);
|
|
1163
|
+
return mt + maxH + mb;
|
|
1164
|
+
};
|
|
1165
|
+
const measureKeyValueGrid = (b, env) => {
|
|
1166
|
+
const mt = b.marginTop ?? 0;
|
|
1167
|
+
const mb = b.marginBottom ?? 0;
|
|
1168
|
+
const rowGap = b.rowGap ?? 4;
|
|
1169
|
+
const orientation = b.orientation ?? "horizontal";
|
|
1170
|
+
const cols = b.columns ?? [];
|
|
1171
|
+
const colCount = cols.length;
|
|
1172
|
+
if (!colCount) return mt + mb;
|
|
1173
|
+
const blockLeft = b.marginLeft ?? 0;
|
|
1174
|
+
const blockRight = b.marginRight ?? 0;
|
|
1175
|
+
const totalWidth = Math.max(env.innerWidth - blockLeft - blockRight, 1);
|
|
1176
|
+
let colWidths;
|
|
1177
|
+
if (b.columnWidths && b.columnWidths.length === colCount) {
|
|
1178
|
+
const safe = b.columnWidths.map((w) => w === void 0 ? "*" : w);
|
|
1179
|
+
colWidths = computeColumnPixelWidths2(safe, totalWidth);
|
|
1180
|
+
} else {
|
|
1181
|
+
colWidths = Array(colCount).fill(totalWidth / colCount);
|
|
1182
|
+
}
|
|
1183
|
+
const separatorText = b.separator;
|
|
1184
|
+
const sepBoxWidth = separatorText ? 10 : 0;
|
|
1185
|
+
const baseKeyWidthRatio = 0.35;
|
|
1186
|
+
const measureKVText = (tb, width) => {
|
|
1187
|
+
const r = resolveTextBlock(styles, tb);
|
|
1188
|
+
const fontName = getFontNameForText(r);
|
|
1189
|
+
doc.font(fontName);
|
|
1190
|
+
doc.fontSize(r.fontSize ?? 10);
|
|
1191
|
+
return doc.heightOfString(r.text ?? "", { width });
|
|
1192
|
+
};
|
|
1193
|
+
let totalHeight = mt;
|
|
1194
|
+
if (orientation === "vertical") {
|
|
1195
|
+
const keyValueGap = b.verticalKeyValueGap ?? 2;
|
|
1196
|
+
let maxColHeight = 0;
|
|
1197
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
1198
|
+
const col = cols[colIndex] ?? [];
|
|
1199
|
+
const colWidth = colWidths[colIndex];
|
|
1200
|
+
let colHeight = 0;
|
|
1201
|
+
for (const item of col) {
|
|
1202
|
+
const keyH = measureKVText(
|
|
1203
|
+
{ type: "text", text: item.key ?? "" },
|
|
1204
|
+
colWidth
|
|
1205
|
+
);
|
|
1206
|
+
const valH = measureKVText(
|
|
1207
|
+
{ type: "text", text: item.value ?? "" },
|
|
1208
|
+
colWidth
|
|
1209
|
+
);
|
|
1210
|
+
colHeight += keyH + keyValueGap + valH + rowGap;
|
|
1211
|
+
}
|
|
1212
|
+
if (colHeight > maxColHeight) maxColHeight = colHeight;
|
|
1213
|
+
}
|
|
1214
|
+
totalHeight += maxColHeight;
|
|
1215
|
+
} else {
|
|
1216
|
+
const maxRows = Math.max(...cols.map((c) => c ? c.length : 0), 0);
|
|
1217
|
+
for (let rowIndex = 0; rowIndex < maxRows; rowIndex++) {
|
|
1218
|
+
let rowHeight = 0;
|
|
1219
|
+
for (let colIndex = 0; colIndex < colCount; colIndex++) {
|
|
1220
|
+
const item = cols[colIndex]?.[rowIndex];
|
|
1221
|
+
if (!item) continue;
|
|
1222
|
+
const colWidth = colWidths[colIndex];
|
|
1223
|
+
const keyWidthPx = b.keyWidth === "*" ? Math.max(colWidth * baseKeyWidthRatio, 20) : b.keyWidth ?? 80;
|
|
1224
|
+
const keyH = measureKVText(
|
|
1225
|
+
{ type: "text", text: item.key ?? "" },
|
|
1226
|
+
keyWidthPx
|
|
1227
|
+
);
|
|
1228
|
+
const valueXInsideCol = keyWidthPx + (separatorText ? sepBoxWidth + 4 : 4);
|
|
1229
|
+
const valueWidth = Math.max(colWidth - valueXInsideCol, 1);
|
|
1230
|
+
const valH = measureKVText(
|
|
1231
|
+
{ type: "text", text: item.value ?? "" },
|
|
1232
|
+
valueWidth
|
|
1233
|
+
);
|
|
1234
|
+
rowHeight = Math.max(rowHeight, Math.max(keyH, valH));
|
|
1235
|
+
}
|
|
1236
|
+
if (rowHeight > 0) totalHeight += rowHeight + rowGap;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
totalHeight += mb;
|
|
1240
|
+
return totalHeight;
|
|
1241
|
+
};
|
|
1242
|
+
const measure = (block, env) => {
|
|
1243
|
+
if (!block || block.visible === false) return 0;
|
|
1244
|
+
switch (block.type) {
|
|
1245
|
+
case "text":
|
|
1246
|
+
return measureText(block, env);
|
|
1247
|
+
case "image":
|
|
1248
|
+
return measureImage(block);
|
|
1249
|
+
case "qr":
|
|
1250
|
+
return measureQr(block);
|
|
1251
|
+
case "barcode":
|
|
1252
|
+
return measureBarcode(block);
|
|
1253
|
+
case "line":
|
|
1254
|
+
return measureLine(block);
|
|
1255
|
+
case "table":
|
|
1256
|
+
return measureTable(block, env);
|
|
1257
|
+
case "columns":
|
|
1258
|
+
return measureColumns(block, env);
|
|
1259
|
+
case "keyValueGrid":
|
|
1260
|
+
return measureKeyValueGrid(block, env);
|
|
1261
|
+
case "signature":
|
|
1262
|
+
return block.height ?? 0;
|
|
1263
|
+
case "pageBreak":
|
|
1264
|
+
return 0;
|
|
1265
|
+
default:
|
|
1266
|
+
return 0;
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
return (block, env) => measure(block, env);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// src/renderer-engine/utils/page-background.ts
|
|
1273
|
+
function drawPageBackground(doc, pageBackground) {
|
|
1274
|
+
if (!pageBackground) return;
|
|
1275
|
+
const { src, opacity = 1 } = pageBackground;
|
|
1276
|
+
const pageWidth = doc.page.width;
|
|
1277
|
+
const pageHeight = doc.page.height;
|
|
1278
|
+
doc.save();
|
|
1279
|
+
try {
|
|
1280
|
+
if (opacity < 1) {
|
|
1281
|
+
doc.opacity(opacity);
|
|
1282
|
+
}
|
|
1283
|
+
doc.image(src, 0, 0, {
|
|
1284
|
+
width: pageWidth,
|
|
1285
|
+
height: pageHeight
|
|
1286
|
+
});
|
|
1287
|
+
} catch (e) {
|
|
1288
|
+
console.warn("Failed to load page background image:", e);
|
|
1289
|
+
}
|
|
1290
|
+
doc.restore();
|
|
1291
|
+
doc.opacity(1);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// src/renderer-engine/utils/page-limit.ts
|
|
1295
|
+
function createBottomLimitForContent(doc, ctx) {
|
|
1296
|
+
return () => {
|
|
1297
|
+
const pageBottomForContent = doc.page.height - doc.page.margins.bottom;
|
|
1298
|
+
return ctx.signatureTopY ?? pageBottomForContent;
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// src/renderer-engine/utils/qr-bar-code.ts
|
|
1303
|
+
import bwipjs from "bwip-js";
|
|
1304
|
+
import QRCode from "qrcode";
|
|
1305
|
+
function generateQrBuffer(value, size, version, errorCorrectionLevel) {
|
|
1306
|
+
const options = {
|
|
1307
|
+
width: size,
|
|
1308
|
+
margin: 0
|
|
1309
|
+
};
|
|
1310
|
+
if (version && version >= 1 && version <= 40) {
|
|
1311
|
+
options.version = version;
|
|
1312
|
+
}
|
|
1313
|
+
options.errorCorrectionLevel = errorCorrectionLevel ?? "M";
|
|
1314
|
+
return new Promise((resolve, reject) => {
|
|
1315
|
+
QRCode.toBuffer(value, options, (err, buffer) => {
|
|
1316
|
+
if (err) {
|
|
1317
|
+
return reject(err);
|
|
1318
|
+
}
|
|
1319
|
+
resolve(buffer);
|
|
1320
|
+
});
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
function mapBarcodeTypeToBcid(bcType) {
|
|
1324
|
+
switch (bcType) {
|
|
1325
|
+
case "EAN13":
|
|
1326
|
+
return "ean13";
|
|
1327
|
+
case "CODE39":
|
|
1328
|
+
return "code39";
|
|
1329
|
+
case "ITF":
|
|
1330
|
+
return "interleaved2of5";
|
|
1331
|
+
case "CODE93":
|
|
1332
|
+
return "code93";
|
|
1333
|
+
case "CODE128":
|
|
1334
|
+
default:
|
|
1335
|
+
return "code128";
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
function generateBarcodeBuffer(value, options = {}) {
|
|
1339
|
+
const {
|
|
1340
|
+
bcType,
|
|
1341
|
+
scale = 3,
|
|
1342
|
+
barHeight = 10,
|
|
1343
|
+
includetext = false,
|
|
1344
|
+
textalign = "center"
|
|
1345
|
+
} = options;
|
|
1346
|
+
const bcid = mapBarcodeTypeToBcid(bcType);
|
|
1347
|
+
return new Promise((resolve, reject) => {
|
|
1348
|
+
let textValue = value;
|
|
1349
|
+
if (bcType === "EAN13") {
|
|
1350
|
+
const digitsOnly = textValue.replace(/\D/g, "");
|
|
1351
|
+
if (digitsOnly.length === 13) {
|
|
1352
|
+
textValue = digitsOnly.slice(0, 12);
|
|
1353
|
+
} else if (digitsOnly.length === 12) {
|
|
1354
|
+
textValue = textValue;
|
|
1355
|
+
} else {
|
|
1356
|
+
return reject(
|
|
1357
|
+
new Error(
|
|
1358
|
+
`EAN13 barcode value must have 12 or 13 digits, got "${value}"`
|
|
1359
|
+
)
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
const bwipOptions = {
|
|
1364
|
+
bcid,
|
|
1365
|
+
text: textValue,
|
|
1366
|
+
scale,
|
|
1367
|
+
height: barHeight,
|
|
1368
|
+
includetext
|
|
1369
|
+
};
|
|
1370
|
+
bwipOptions.textxalign = textalign;
|
|
1371
|
+
bwipjs.toBuffer(bwipOptions, (err, png) => {
|
|
1372
|
+
if (err) {
|
|
1373
|
+
return reject(err);
|
|
1374
|
+
}
|
|
1375
|
+
resolve(png);
|
|
1376
|
+
});
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
async function materializeQrAndBarcodesInBlocks(blocks) {
|
|
1380
|
+
const out = [];
|
|
1381
|
+
for (const block of blocks) {
|
|
1382
|
+
if (block.type === "columns") {
|
|
1383
|
+
const colBlock = block;
|
|
1384
|
+
const newCols = [];
|
|
1385
|
+
for (const col of colBlock.columns) {
|
|
1386
|
+
newCols.push(await materializeQrAndBarcodesInBlocks(col));
|
|
1387
|
+
}
|
|
1388
|
+
out.push({
|
|
1389
|
+
...colBlock,
|
|
1390
|
+
columns: newCols
|
|
1391
|
+
});
|
|
1392
|
+
} else if (block.type === "signature") {
|
|
1393
|
+
const sig = block;
|
|
1394
|
+
if (sig.blocks && sig.blocks.length) {
|
|
1395
|
+
const newInner = await materializeQrAndBarcodesInBlocks(sig.blocks);
|
|
1396
|
+
out.push({
|
|
1397
|
+
...sig,
|
|
1398
|
+
blocks: newInner
|
|
1399
|
+
});
|
|
1400
|
+
} else {
|
|
1401
|
+
out.push(block);
|
|
1402
|
+
}
|
|
1403
|
+
} else if (block.type === "qr") {
|
|
1404
|
+
const qb = { ...block };
|
|
1405
|
+
if (!qb.src && qb.value) {
|
|
1406
|
+
const size = qb.size ?? 80;
|
|
1407
|
+
const buf = await generateQrBuffer(
|
|
1408
|
+
qb.value,
|
|
1409
|
+
size,
|
|
1410
|
+
qb.qrVersion,
|
|
1411
|
+
qb.errorCorrectionLevel
|
|
1412
|
+
);
|
|
1413
|
+
qb.src = buf;
|
|
1414
|
+
}
|
|
1415
|
+
out.push(qb);
|
|
1416
|
+
} else if (block.type === "barcode") {
|
|
1417
|
+
const bb = { ...block };
|
|
1418
|
+
if (!bb.src && bb.value) {
|
|
1419
|
+
const buf = await generateBarcodeBuffer(bb.value, {
|
|
1420
|
+
bcType: bb.bcType,
|
|
1421
|
+
scale: bb.scale,
|
|
1422
|
+
barHeight: bb.barHeight,
|
|
1423
|
+
includetext: bb.includetext,
|
|
1424
|
+
textalign: bb.textalign
|
|
1425
|
+
});
|
|
1426
|
+
bb.src = buf;
|
|
1427
|
+
}
|
|
1428
|
+
out.push(bb);
|
|
1429
|
+
} else {
|
|
1430
|
+
out.push(block);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return out;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// src/renderer-engine/utils/signature.ts
|
|
1437
|
+
function drawSignatureBlock(doc, block, y, env, renderBlockArray) {
|
|
1438
|
+
const localLeft = block.marginLeft ?? 0;
|
|
1439
|
+
const localRight = block.marginRight ?? 0;
|
|
1440
|
+
const bandLeft = env.marginLeft + localLeft;
|
|
1441
|
+
const bandWidth = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
1442
|
+
const bandHeight = block.height;
|
|
1443
|
+
doc.save();
|
|
1444
|
+
doc.rect(bandLeft, y, bandWidth, bandHeight).clip();
|
|
1445
|
+
if (block.blocks && block.blocks.length) {
|
|
1446
|
+
const sigEnv = {
|
|
1447
|
+
marginLeft: bandLeft,
|
|
1448
|
+
innerWidth: bandWidth,
|
|
1449
|
+
allowPageBreak: false
|
|
1450
|
+
};
|
|
1451
|
+
renderBlockArray(block.blocks, y, sigEnv);
|
|
1452
|
+
} else {
|
|
1453
|
+
const imgWidth = block.width ?? 120;
|
|
1454
|
+
const xCenter = bandLeft + (bandWidth - imgWidth) / 2;
|
|
1455
|
+
if (block.image) {
|
|
1456
|
+
try {
|
|
1457
|
+
doc.image(block.image, xCenter, y, { width: imgWidth });
|
|
1458
|
+
} catch (e) {
|
|
1459
|
+
console.warn("Failed to load signature image:", e);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
const label = block.label ?? "Authorized Signatory";
|
|
1463
|
+
const labelY = y + bandHeight - 14;
|
|
1464
|
+
doc.fontSize(9).text(label, bandLeft, labelY, {
|
|
1465
|
+
width: bandWidth,
|
|
1466
|
+
align: "center"
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
doc.restore();
|
|
1470
|
+
}
|
|
1471
|
+
function createProcessSignatureBlock({
|
|
1472
|
+
doc,
|
|
1473
|
+
ctx,
|
|
1474
|
+
styles,
|
|
1475
|
+
finishPage: finishPage2,
|
|
1476
|
+
contentEnvFn
|
|
1477
|
+
}) {
|
|
1478
|
+
return (block) => {
|
|
1479
|
+
if (ctx.signatureBlock) {
|
|
1480
|
+
finishPage2(true);
|
|
1481
|
+
}
|
|
1482
|
+
const env = contentEnvFn(doc);
|
|
1483
|
+
const localLeft = block.marginLeft ?? 0;
|
|
1484
|
+
const localRight = block.marginRight ?? 0;
|
|
1485
|
+
const bandWidth = Math.max(env.innerWidth - localLeft - localRight, 1);
|
|
1486
|
+
let requiredHeight = block.height;
|
|
1487
|
+
if (block.blocks && block.blocks.length) {
|
|
1488
|
+
let required = 0;
|
|
1489
|
+
const measureImageLike = (mt, mb, h) => {
|
|
1490
|
+
return (mt ?? 0) + h + (mb ?? 0);
|
|
1491
|
+
};
|
|
1492
|
+
const measureColumns = (c) => {
|
|
1493
|
+
const blockLeft = c.marginLeft ?? 0;
|
|
1494
|
+
const blockRight = c.marginRight ?? 0;
|
|
1495
|
+
const totalWidth = Math.max(bandWidth - blockLeft - blockRight, 1);
|
|
1496
|
+
const cols = c.columns;
|
|
1497
|
+
const n = cols.length;
|
|
1498
|
+
if (!n) return (c.marginTop ?? 0) + (c.marginBottom ?? 0);
|
|
1499
|
+
let colWidths;
|
|
1500
|
+
let gap;
|
|
1501
|
+
const mode = c.mode ?? "fixedGap";
|
|
1502
|
+
if (mode === "spaceBetween" && n > 1) {
|
|
1503
|
+
if (c.widths && c.widths.length === n) {
|
|
1504
|
+
colWidths = computeColumnPixelWidths(c.widths, totalWidth);
|
|
1505
|
+
} else {
|
|
1506
|
+
const equal = totalWidth / n;
|
|
1507
|
+
colWidths = Array(n).fill(equal);
|
|
1508
|
+
}
|
|
1509
|
+
const totalColsWidth = colWidths.reduce((a, b) => a + b, 0);
|
|
1510
|
+
const remaining = Math.max(totalWidth - totalColsWidth, 0);
|
|
1511
|
+
gap = remaining / (n - 1);
|
|
1512
|
+
} else {
|
|
1513
|
+
gap = c.gap ?? 20;
|
|
1514
|
+
const totalGapsWidth = gap * (n - 1);
|
|
1515
|
+
const widthForCols = Math.max(totalWidth - totalGapsWidth, 1);
|
|
1516
|
+
if (c.widths && c.widths.length === n) {
|
|
1517
|
+
colWidths = computeColumnPixelWidths(c.widths, widthForCols);
|
|
1518
|
+
} else {
|
|
1519
|
+
const equal = widthForCols / n;
|
|
1520
|
+
colWidths = Array(n).fill(equal);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
const mt = c.marginTop ?? 0;
|
|
1524
|
+
const mb = c.marginBottom ?? 0;
|
|
1525
|
+
const colHeights = [];
|
|
1526
|
+
cols.forEach((colBlocks, idx) => {
|
|
1527
|
+
const colEnv = {
|
|
1528
|
+
marginLeft: 0,
|
|
1529
|
+
innerWidth: colWidths[idx],
|
|
1530
|
+
allowPageBreak: false
|
|
1531
|
+
};
|
|
1532
|
+
let colY = 0;
|
|
1533
|
+
for (const b of colBlocks) {
|
|
1534
|
+
colY += measureInnerBlock(b, colEnv);
|
|
1535
|
+
}
|
|
1536
|
+
colHeights.push(colY);
|
|
1537
|
+
});
|
|
1538
|
+
const maxColHeight = Math.max(...colHeights, 0);
|
|
1539
|
+
return mt + maxColHeight + mb;
|
|
1540
|
+
};
|
|
1541
|
+
const innerEnv = {
|
|
1542
|
+
marginLeft: 0,
|
|
1543
|
+
innerWidth: bandWidth,
|
|
1544
|
+
allowPageBreak: false
|
|
1545
|
+
};
|
|
1546
|
+
const measureInnerBlock = (b, envForColumns) => {
|
|
1547
|
+
switch (b.type) {
|
|
1548
|
+
case "text": {
|
|
1549
|
+
const tb = resolveTextBlock(styles, b);
|
|
1550
|
+
const localLeft2 = tb.marginLeft ?? 0;
|
|
1551
|
+
const localRight2 = tb.marginRight ?? 0;
|
|
1552
|
+
const width = Math.max(
|
|
1553
|
+
envForColumns.innerWidth - localLeft2 - localRight2,
|
|
1554
|
+
1
|
|
1555
|
+
);
|
|
1556
|
+
doc.fontSize(tb.fontSize ?? 10);
|
|
1557
|
+
const isBold = !!tb.bold;
|
|
1558
|
+
const isItalic = !!tb.italic;
|
|
1559
|
+
let fontName = tb.font;
|
|
1560
|
+
if (!fontName) {
|
|
1561
|
+
if (isBold && isItalic) fontName = "Helvetica-BoldOblique";
|
|
1562
|
+
else if (isBold) fontName = "Helvetica-Bold";
|
|
1563
|
+
else if (isItalic) fontName = "Helvetica-Oblique";
|
|
1564
|
+
else fontName = "Helvetica";
|
|
1565
|
+
}
|
|
1566
|
+
doc.font(fontName);
|
|
1567
|
+
const h = doc.heightOfString(tb.text, { width });
|
|
1568
|
+
const gap = tb.lineGap ?? 4;
|
|
1569
|
+
return h + gap;
|
|
1570
|
+
}
|
|
1571
|
+
case "image": {
|
|
1572
|
+
const ib = b;
|
|
1573
|
+
const h = ib.height ?? 50;
|
|
1574
|
+
return measureImageLike(ib.marginTop, ib.marginBottom, h);
|
|
1575
|
+
}
|
|
1576
|
+
case "qr": {
|
|
1577
|
+
const qb = b;
|
|
1578
|
+
const size = qb.size ?? 80;
|
|
1579
|
+
return measureImageLike(qb.marginTop, qb.marginBottom, size);
|
|
1580
|
+
}
|
|
1581
|
+
case "barcode": {
|
|
1582
|
+
const bb = b;
|
|
1583
|
+
const h = bb.height ?? 40;
|
|
1584
|
+
return measureImageLike(bb.marginTop, bb.marginBottom, h);
|
|
1585
|
+
}
|
|
1586
|
+
case "line": {
|
|
1587
|
+
const lb = b;
|
|
1588
|
+
const lw = lb.lineWidth ?? 1;
|
|
1589
|
+
const mt = lb.marginTop ?? 0;
|
|
1590
|
+
const mb = lb.marginBottom ?? 0;
|
|
1591
|
+
return mt + lw + mb;
|
|
1592
|
+
}
|
|
1593
|
+
case "table": {
|
|
1594
|
+
const t = b;
|
|
1595
|
+
const fakeEnv = {
|
|
1596
|
+
marginLeft: t.marginLeft ?? 0,
|
|
1597
|
+
innerWidth: bandWidth - (t.marginLeft ?? 0) - (t.marginRight ?? 0),
|
|
1598
|
+
allowPageBreak: false
|
|
1599
|
+
};
|
|
1600
|
+
return measureTableHeight(
|
|
1601
|
+
doc,
|
|
1602
|
+
t,
|
|
1603
|
+
fakeEnv,
|
|
1604
|
+
styles,
|
|
1605
|
+
computeColumnPixelWidths
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
case "columns":
|
|
1609
|
+
return measureColumns(b);
|
|
1610
|
+
case "signature":
|
|
1611
|
+
return b.height;
|
|
1612
|
+
default:
|
|
1613
|
+
return 0;
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
for (const inner of block.blocks) {
|
|
1617
|
+
required += measureInnerBlock(inner, innerEnv);
|
|
1618
|
+
}
|
|
1619
|
+
requiredHeight = Math.max(requiredHeight, required);
|
|
1620
|
+
}
|
|
1621
|
+
const sigHeight = requiredHeight;
|
|
1622
|
+
const pageHeight = doc.page.height;
|
|
1623
|
+
const candidateTop = pageHeight - doc.page.margins.bottom - sigHeight;
|
|
1624
|
+
if (ctx.currentY > candidateTop) {
|
|
1625
|
+
finishPage2(true);
|
|
1626
|
+
}
|
|
1627
|
+
const newPageHeight = doc.page.height;
|
|
1628
|
+
const newCandidateTop = newPageHeight - doc.page.margins.bottom - sigHeight;
|
|
1629
|
+
ctx.signatureBlock = { ...block, height: sigHeight };
|
|
1630
|
+
ctx.signatureTopY = newCandidateTop;
|
|
1631
|
+
ctx.signatureHeight = sigHeight;
|
|
1632
|
+
ctx.signaturePlaced = true;
|
|
1633
|
+
ctx.afterSignature = true;
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// src/renderer-engine/utils/start-page-layout.ts
|
|
1638
|
+
function createStartNewPageLayout({
|
|
1639
|
+
doc,
|
|
1640
|
+
def,
|
|
1641
|
+
ctx,
|
|
1642
|
+
headerBandHeight,
|
|
1643
|
+
renderBlockArray
|
|
1644
|
+
}) {
|
|
1645
|
+
return () => {
|
|
1646
|
+
drawPageBackground(doc, def.pageBackground);
|
|
1647
|
+
drawHeader(
|
|
1648
|
+
doc,
|
|
1649
|
+
def,
|
|
1650
|
+
headerBandHeight,
|
|
1651
|
+
def.header ?? void 0,
|
|
1652
|
+
renderBlockArray
|
|
1653
|
+
);
|
|
1654
|
+
const mode = def.watermark?.mode;
|
|
1655
|
+
if (def.watermark && !watermarkUsesLast(mode)) {
|
|
1656
|
+
drawWatermarkForPage(doc, def.watermark, ctx.pageNumber, false);
|
|
1657
|
+
}
|
|
1658
|
+
ctx.currentY = doc.page.margins.top;
|
|
1659
|
+
ctx.signatureBlock = null;
|
|
1660
|
+
ctx.signatureTopY = null;
|
|
1661
|
+
ctx.signatureHeight = 0;
|
|
1662
|
+
ctx.signaturePlaced = false;
|
|
1663
|
+
ctx.afterSignature = false;
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// src/renderer-engine/utils/watermark.ts
|
|
1668
|
+
function watermarkUsesLast(mode) {
|
|
1669
|
+
return mode === "last" || mode === "firstLast" || mode === "exceptLast" || mode === "exceptFirstLast";
|
|
1670
|
+
}
|
|
1671
|
+
function drawWatermarkForPage(doc, watermark, pageNumber, isLast) {
|
|
1672
|
+
const {
|
|
1673
|
+
text,
|
|
1674
|
+
opacity = 0.06,
|
|
1675
|
+
fontSize = 60,
|
|
1676
|
+
color = "gray",
|
|
1677
|
+
angle = 45,
|
|
1678
|
+
mode = "all"
|
|
1679
|
+
} = watermark;
|
|
1680
|
+
const shouldDraw = (() => {
|
|
1681
|
+
switch (mode) {
|
|
1682
|
+
case "all":
|
|
1683
|
+
return true;
|
|
1684
|
+
case "first":
|
|
1685
|
+
return pageNumber === 1;
|
|
1686
|
+
case "last":
|
|
1687
|
+
return isLast;
|
|
1688
|
+
case "firstLast":
|
|
1689
|
+
return pageNumber === 1 || isLast;
|
|
1690
|
+
case "exceptFirstLast":
|
|
1691
|
+
return pageNumber !== 1 && !isLast;
|
|
1692
|
+
case "exceptFirst":
|
|
1693
|
+
return pageNumber !== 1;
|
|
1694
|
+
case "exceptLast":
|
|
1695
|
+
return !isLast;
|
|
1696
|
+
default:
|
|
1697
|
+
return true;
|
|
1698
|
+
}
|
|
1699
|
+
})();
|
|
1700
|
+
if (!shouldDraw) return;
|
|
1701
|
+
const centerX = doc.page.width / 2;
|
|
1702
|
+
const centerY = doc.page.height / 2;
|
|
1703
|
+
doc.save();
|
|
1704
|
+
doc.fillColor(color);
|
|
1705
|
+
doc.opacity(opacity);
|
|
1706
|
+
doc.fontSize(fontSize);
|
|
1707
|
+
const textWidth = doc.widthOfString(text);
|
|
1708
|
+
const textHeight = doc.currentLineHeight();
|
|
1709
|
+
const x = centerX - textWidth / 2;
|
|
1710
|
+
const y = centerY - textHeight / 2;
|
|
1711
|
+
doc.rotate(angle, { origin: [centerX, centerY] });
|
|
1712
|
+
doc.text(text, x, y);
|
|
1713
|
+
doc.restore();
|
|
1714
|
+
doc.opacity(1);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/renderer-engine/index.ts
|
|
1718
|
+
async function renderCustomPdf(def, outputPath) {
|
|
1719
|
+
if (def.header) {
|
|
1720
|
+
def.header.blocks = await materializeQrAndBarcodesInBlocks(
|
|
1721
|
+
def.header.blocks
|
|
1722
|
+
);
|
|
1723
|
+
def.header.blocks = await materializeImagesInBlocks(def.header.blocks);
|
|
1724
|
+
}
|
|
1725
|
+
def.content = await materializeQrAndBarcodesInBlocks(def.content);
|
|
1726
|
+
def.content = await materializeImagesInBlocks(def.content);
|
|
1727
|
+
if (def.pageBackground?.src) {
|
|
1728
|
+
def.pageBackground.src = await normalizeImageSrc(def.pageBackground.src);
|
|
1729
|
+
}
|
|
1730
|
+
if (def.header?.backgroundImage) {
|
|
1731
|
+
def.header.backgroundImage = await normalizeImageSrc(
|
|
1732
|
+
def.header.backgroundImage
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
if (def.footer && typeof def.footer !== "function") {
|
|
1736
|
+
const footer = def.footer;
|
|
1737
|
+
if (footer.backgroundImage) {
|
|
1738
|
+
footer.backgroundImage = await normalizeImageSrc(footer.backgroundImage);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
return new Promise((resolve, reject) => {
|
|
1742
|
+
const headerBandHeight = def.margins.top ?? 0;
|
|
1743
|
+
const footerBandHeight = def.margins.bottom ?? 0;
|
|
1744
|
+
const doc = new PDFDocument({
|
|
1745
|
+
size: def.pageSize || "A4",
|
|
1746
|
+
margins: {
|
|
1747
|
+
top: headerBandHeight,
|
|
1748
|
+
bottom: footerBandHeight,
|
|
1749
|
+
left: def.margins.left,
|
|
1750
|
+
right: def.margins.right
|
|
1751
|
+
}
|
|
1752
|
+
});
|
|
1753
|
+
if (def.fonts && def.fonts.length) {
|
|
1754
|
+
for (const f of def.fonts) {
|
|
1755
|
+
try {
|
|
1756
|
+
doc.registerFont(f.name, f.src);
|
|
1757
|
+
} catch (e) {
|
|
1758
|
+
console.warn("Failed to register font:", f.name, e);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
const stream = fs.createWriteStream(outputPath);
|
|
1763
|
+
doc.pipe(stream);
|
|
1764
|
+
const ctx = createInitialContext(doc);
|
|
1765
|
+
const styles = def.styles ?? {};
|
|
1766
|
+
const finishPage2 = (addNewPage) => finishPage({
|
|
1767
|
+
addNewPage,
|
|
1768
|
+
doc,
|
|
1769
|
+
def,
|
|
1770
|
+
ctx,
|
|
1771
|
+
footerBandHeight,
|
|
1772
|
+
renderBlockArray,
|
|
1773
|
+
startNewPageLayout
|
|
1774
|
+
});
|
|
1775
|
+
const processSignatureBlock = createProcessSignatureBlock({
|
|
1776
|
+
doc,
|
|
1777
|
+
ctx,
|
|
1778
|
+
styles,
|
|
1779
|
+
finishPage: finishPage2,
|
|
1780
|
+
contentEnvFn: contentEnv
|
|
1781
|
+
});
|
|
1782
|
+
const { renderBlock, renderBlockArray } = createBlockRenderer({
|
|
1783
|
+
doc,
|
|
1784
|
+
ctx,
|
|
1785
|
+
styles,
|
|
1786
|
+
computeColumnPixelWidths,
|
|
1787
|
+
finishPage: finishPage2,
|
|
1788
|
+
processSignatureBlock
|
|
1789
|
+
});
|
|
1790
|
+
const startNewPageLayout = createStartNewPageLayout({
|
|
1791
|
+
doc,
|
|
1792
|
+
def,
|
|
1793
|
+
ctx,
|
|
1794
|
+
headerBandHeight,
|
|
1795
|
+
renderBlockArray
|
|
1796
|
+
});
|
|
1797
|
+
startNewPageLayout();
|
|
1798
|
+
for (const block of def.content) {
|
|
1799
|
+
if (block.type === "signature") {
|
|
1800
|
+
processSignatureBlock(block);
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1803
|
+
if (ctx.afterSignature) {
|
|
1804
|
+
finishPage2(true);
|
|
1805
|
+
ctx.afterSignature = false;
|
|
1806
|
+
}
|
|
1807
|
+
renderBlock(block, null, contentEnv(doc));
|
|
1808
|
+
}
|
|
1809
|
+
finishPage2(false);
|
|
1810
|
+
doc.end();
|
|
1811
|
+
stream.on("finish", () => resolve());
|
|
1812
|
+
stream.on("error", (err) => reject(err));
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
async function renderCustomPdfToBuffer(def) {
|
|
1816
|
+
if (def.header) {
|
|
1817
|
+
def.header.blocks = await materializeQrAndBarcodesInBlocks(
|
|
1818
|
+
def.header.blocks
|
|
1819
|
+
);
|
|
1820
|
+
def.header.blocks = await materializeImagesInBlocks(def.header.blocks);
|
|
1821
|
+
}
|
|
1822
|
+
def.content = await materializeQrAndBarcodesInBlocks(def.content);
|
|
1823
|
+
def.content = await materializeImagesInBlocks(def.content);
|
|
1824
|
+
if (def.pageBackground?.src) {
|
|
1825
|
+
def.pageBackground.src = await normalizeImageSrc(def.pageBackground.src);
|
|
1826
|
+
}
|
|
1827
|
+
if (def.header?.backgroundImage) {
|
|
1828
|
+
def.header.backgroundImage = await normalizeImageSrc(
|
|
1829
|
+
def.header.backgroundImage
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
if (def.footer && typeof def.footer !== "function") {
|
|
1833
|
+
const footer = def.footer;
|
|
1834
|
+
if (footer.backgroundImage) {
|
|
1835
|
+
footer.backgroundImage = await normalizeImageSrc(footer.backgroundImage);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
return new Promise((resolve, reject) => {
|
|
1839
|
+
const headerBandHeight = def.margins.top ?? 0;
|
|
1840
|
+
const footerBandHeight = def.margins.bottom ?? 0;
|
|
1841
|
+
const doc = new PDFDocument({
|
|
1842
|
+
size: def.pageSize || "A4",
|
|
1843
|
+
margins: {
|
|
1844
|
+
top: headerBandHeight,
|
|
1845
|
+
bottom: footerBandHeight,
|
|
1846
|
+
left: def.margins.left,
|
|
1847
|
+
right: def.margins.right
|
|
1848
|
+
}
|
|
1849
|
+
});
|
|
1850
|
+
const chunks = [];
|
|
1851
|
+
doc.on("data", (chunk) => {
|
|
1852
|
+
chunks.push(chunk);
|
|
1853
|
+
});
|
|
1854
|
+
doc.on("end", () => {
|
|
1855
|
+
resolve(Buffer.concat(chunks));
|
|
1856
|
+
});
|
|
1857
|
+
doc.on("error", (err) => {
|
|
1858
|
+
reject(err);
|
|
1859
|
+
});
|
|
1860
|
+
if (def.fonts && def.fonts.length) {
|
|
1861
|
+
for (const f of def.fonts) {
|
|
1862
|
+
try {
|
|
1863
|
+
doc.registerFont(f.name, f.src);
|
|
1864
|
+
} catch (e) {
|
|
1865
|
+
console.warn("Failed to register font:", f.name, e);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
const ctx = createInitialContext(doc);
|
|
1870
|
+
const styles = def.styles ?? {};
|
|
1871
|
+
const finishPage2 = (addNewPage) => finishPage({
|
|
1872
|
+
addNewPage,
|
|
1873
|
+
doc,
|
|
1874
|
+
def,
|
|
1875
|
+
ctx,
|
|
1876
|
+
footerBandHeight,
|
|
1877
|
+
renderBlockArray,
|
|
1878
|
+
startNewPageLayout
|
|
1879
|
+
});
|
|
1880
|
+
const processSignatureBlock = createProcessSignatureBlock({
|
|
1881
|
+
doc,
|
|
1882
|
+
ctx,
|
|
1883
|
+
styles,
|
|
1884
|
+
finishPage: finishPage2,
|
|
1885
|
+
contentEnvFn: contentEnv
|
|
1886
|
+
});
|
|
1887
|
+
const { renderBlock, renderBlockArray } = createBlockRenderer({
|
|
1888
|
+
doc,
|
|
1889
|
+
ctx,
|
|
1890
|
+
styles,
|
|
1891
|
+
computeColumnPixelWidths,
|
|
1892
|
+
finishPage: finishPage2,
|
|
1893
|
+
processSignatureBlock
|
|
1894
|
+
});
|
|
1895
|
+
const startNewPageLayout = createStartNewPageLayout({
|
|
1896
|
+
doc,
|
|
1897
|
+
def,
|
|
1898
|
+
ctx,
|
|
1899
|
+
headerBandHeight,
|
|
1900
|
+
renderBlockArray
|
|
1901
|
+
});
|
|
1902
|
+
startNewPageLayout();
|
|
1903
|
+
for (const block of def.content) {
|
|
1904
|
+
if (block.type === "signature") {
|
|
1905
|
+
processSignatureBlock(block);
|
|
1906
|
+
continue;
|
|
1907
|
+
}
|
|
1908
|
+
if (ctx.afterSignature) {
|
|
1909
|
+
finishPage2(true);
|
|
1910
|
+
ctx.afterSignature = false;
|
|
1911
|
+
}
|
|
1912
|
+
renderBlock(block, null, contentEnv(doc));
|
|
1913
|
+
}
|
|
1914
|
+
finishPage2(false);
|
|
1915
|
+
doc.end();
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// src/types/barcode.ts
|
|
1920
|
+
var BARCODE_TYPES = {
|
|
1921
|
+
CODE128: "CODE128",
|
|
1922
|
+
EAN13: "EAN13",
|
|
1923
|
+
CODE39: "CODE39",
|
|
1924
|
+
ITF: "ITF",
|
|
1925
|
+
CODE93: "CODE93"
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1928
|
+
// src/types/qr.ts
|
|
1929
|
+
var QR_ERROR_LEVEL = {
|
|
1930
|
+
LOW: "L",
|
|
1931
|
+
MEDIUM: "M",
|
|
1932
|
+
QUARTILE: "Q",
|
|
1933
|
+
HIGH: "H"
|
|
1934
|
+
};
|
|
1935
|
+
export {
|
|
1936
|
+
BARCODE_TYPES,
|
|
1937
|
+
QR_ERROR_LEVEL,
|
|
1938
|
+
renderCustomPdf,
|
|
1939
|
+
renderCustomPdfToBuffer
|
|
1940
|
+
};
|