@signiphi/pdf-compose 0.1.0-beta.7 → 0.1.0-beta.8
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/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +285 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +285 -25
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +3 -0
- package/dist/styles/index.d.ts +1 -1
- package/dist/utils/markdown-parser.d.ts.map +1 -1
- package/dist/utils/markdown-validator.d.ts.map +1 -1
- package/dist/utils/markdown-writer.d.ts.map +1 -1
- package/dist/utils/pdf-generator.d.ts +50 -0
- package/dist/utils/pdf-generator.d.ts.map +1 -1
- package/dist/utils/template-pipeline.d.ts +19 -2
- package/dist/utils/template-pipeline.d.ts.map +1 -1
- package/dist/utils/variable-helpers.d.ts.map +1 -1
- package/package.json +6 -4
- package/src/styles/index.d.ts +1 -1
package/dist/index.mjs
CHANGED
|
@@ -574,6 +574,11 @@ function replaceVariablesInContent(content, values) {
|
|
|
574
574
|
}
|
|
575
575
|
return textNode;
|
|
576
576
|
}
|
|
577
|
+
if (node.type === "imageNode" && node.attrs?.varName) {
|
|
578
|
+
const varName = node.attrs.varName;
|
|
579
|
+
const resolved = values[varName] || node.attrs.src || "";
|
|
580
|
+
return { ...node, attrs: { ...node.attrs, src: resolved } };
|
|
581
|
+
}
|
|
577
582
|
if (node.content) {
|
|
578
583
|
return { ...node, content: node.content.map(walkNode) };
|
|
579
584
|
}
|
|
@@ -702,7 +707,8 @@ function expandRepeatContent(content, values) {
|
|
|
702
707
|
values: enrichedValues
|
|
703
708
|
};
|
|
704
709
|
}
|
|
705
|
-
var TOKEN_REGEX = /\{\{(field|var)\|([^}]+)\}\}/g;
|
|
710
|
+
var TOKEN_REGEX = /\{\{(field|var|image)\|([^}]+)\}\}/g;
|
|
711
|
+
var BLOCK_IMAGE_REGEX = /^!\[([^\]]*)\]\(([^)\s]+)\)$/;
|
|
706
712
|
var DIRECTIVE_OPEN = /^:::(panel|columns|col|watermark|repeat|subtotals)(?:\{([^}]*)\})?$/;
|
|
707
713
|
var DIRECTIVE_CLOSE = /^:::$/;
|
|
708
714
|
function parseDirectiveAttrs(str) {
|
|
@@ -847,6 +853,44 @@ function parseVariableToken(tokenBody) {
|
|
|
847
853
|
}
|
|
848
854
|
return attrs;
|
|
849
855
|
}
|
|
856
|
+
function parseImageToken(tokenBody) {
|
|
857
|
+
const attrs = {
|
|
858
|
+
src: "",
|
|
859
|
+
varName: "",
|
|
860
|
+
alt: "",
|
|
861
|
+
width: "",
|
|
862
|
+
height: "",
|
|
863
|
+
align: ""
|
|
864
|
+
};
|
|
865
|
+
const pairs = tokenBody.split("|");
|
|
866
|
+
for (const pair of pairs) {
|
|
867
|
+
const colonIdx = pair.indexOf(":");
|
|
868
|
+
if (colonIdx === -1) continue;
|
|
869
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
870
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
871
|
+
switch (key) {
|
|
872
|
+
case "src":
|
|
873
|
+
attrs.src = value;
|
|
874
|
+
break;
|
|
875
|
+
case "var":
|
|
876
|
+
attrs.varName = value;
|
|
877
|
+
break;
|
|
878
|
+
case "alt":
|
|
879
|
+
attrs.alt = value;
|
|
880
|
+
break;
|
|
881
|
+
case "width":
|
|
882
|
+
attrs.width = value;
|
|
883
|
+
break;
|
|
884
|
+
case "height":
|
|
885
|
+
attrs.height = value;
|
|
886
|
+
break;
|
|
887
|
+
case "align":
|
|
888
|
+
attrs.align = value;
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return attrs;
|
|
893
|
+
}
|
|
850
894
|
function parseInline(text) {
|
|
851
895
|
text = preprocessMarkedTokens(text);
|
|
852
896
|
const nodes = [];
|
|
@@ -870,6 +914,8 @@ function parseInline(text) {
|
|
|
870
914
|
const node = { type: "variableNode", attrs };
|
|
871
915
|
if (marks.length > 0) node.marks = marks;
|
|
872
916
|
nodes.push(node);
|
|
917
|
+
} else if (match[1] === "image") {
|
|
918
|
+
nodes.push({ type: "imageNode", attrs: parseImageToken(match[2]) });
|
|
873
919
|
}
|
|
874
920
|
lastIndex = match.index + match[0].length;
|
|
875
921
|
}
|
|
@@ -1183,10 +1229,24 @@ function parseBlocks(lines, startIdx, stopCondition) {
|
|
|
1183
1229
|
i++;
|
|
1184
1230
|
continue;
|
|
1185
1231
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
content
|
|
1189
|
-
|
|
1232
|
+
const imageMatch = line.trim().match(BLOCK_IMAGE_REGEX);
|
|
1233
|
+
if (imageMatch) {
|
|
1234
|
+
content.push({
|
|
1235
|
+
type: "imageNode",
|
|
1236
|
+
attrs: { src: imageMatch[2], varName: "", alt: imageMatch[1], width: "", height: "", align: "" }
|
|
1237
|
+
});
|
|
1238
|
+
i++;
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
const inlineNodes = parseInline(line);
|
|
1242
|
+
const nonWhitespace = inlineNodes.filter(
|
|
1243
|
+
(n) => !(n.type === "text" && !(n.text || "").trim())
|
|
1244
|
+
);
|
|
1245
|
+
if (nonWhitespace.length === 1 && nonWhitespace[0].type === "imageNode") {
|
|
1246
|
+
content.push(nonWhitespace[0]);
|
|
1247
|
+
} else {
|
|
1248
|
+
content.push({ type: "paragraph", content: inlineNodes });
|
|
1249
|
+
}
|
|
1190
1250
|
i++;
|
|
1191
1251
|
}
|
|
1192
1252
|
return { blocks: content, nextIdx: i };
|
|
@@ -1337,6 +1397,34 @@ var CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
|
|
|
1337
1397
|
var CONTENT_HEIGHT = PAGE_HEIGHT - 2 * MARGIN;
|
|
1338
1398
|
var LINE_HEIGHT_FACTOR = 1.4;
|
|
1339
1399
|
var BODY_FONT_SIZE = 10;
|
|
1400
|
+
function hexToRgbColor(hex) {
|
|
1401
|
+
if (!hex) return null;
|
|
1402
|
+
const m = hex.trim().match(/^#?([0-9a-fA-F]{6})$/);
|
|
1403
|
+
if (!m) return null;
|
|
1404
|
+
const n = parseInt(m[1], 16);
|
|
1405
|
+
return rgb((n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255);
|
|
1406
|
+
}
|
|
1407
|
+
function contrastTextColor(fill) {
|
|
1408
|
+
const c = fill;
|
|
1409
|
+
const luminance = 0.299 * c.red + 0.587 * c.green + 0.114 * c.blue;
|
|
1410
|
+
return luminance > 0.6 ? rgb(0, 0, 0) : rgb(1, 1, 1);
|
|
1411
|
+
}
|
|
1412
|
+
function resolveTheme(theme) {
|
|
1413
|
+
const primary = hexToRgbColor(theme?.primaryColor);
|
|
1414
|
+
const accent = hexToRgbColor(theme?.accentColor);
|
|
1415
|
+
const border = hexToRgbColor(theme?.borderColor);
|
|
1416
|
+
return {
|
|
1417
|
+
tableHeaderBg: primary ?? rgb(0.3, 0.3, 0.3),
|
|
1418
|
+
tableHeaderText: primary ? contrastTextColor(primary) : rgb(1, 1, 1),
|
|
1419
|
+
tableBorder: border ?? rgb(0.75, 0.75, 0.75),
|
|
1420
|
+
panelDarkHeaderBg: primary ?? rgb(0.25, 0.25, 0.25),
|
|
1421
|
+
panelDarkHeaderText: primary ? contrastTextColor(primary) : rgb(1, 1, 1),
|
|
1422
|
+
panelBorder: border ?? rgb(0.6, 0.6, 0.6),
|
|
1423
|
+
rule: primary ?? rgb(0, 0, 0),
|
|
1424
|
+
pageHeaderText: primary ?? rgb(0, 0, 0),
|
|
1425
|
+
pageHeaderRule: accent ?? primary ?? rgb(0.75, 0.75, 0.75)
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1340
1428
|
var DEFAULT_REGION = { leftX: MARGIN, width: CONTENT_WIDTH };
|
|
1341
1429
|
var FIELD_DISPLAY = {
|
|
1342
1430
|
["text" /* TEXT */]: { label: "Text", width: 120, height: 30 },
|
|
@@ -1368,6 +1456,53 @@ function getFieldDimensions(fieldType, attrs, font, fontSize) {
|
|
|
1368
1456
|
const height = textLineHeight + 4;
|
|
1369
1457
|
return { width, height };
|
|
1370
1458
|
}
|
|
1459
|
+
async function loadImageBytes(src) {
|
|
1460
|
+
if (src.startsWith("data:")) {
|
|
1461
|
+
const comma = src.indexOf(",");
|
|
1462
|
+
if (comma === -1) throw new Error("malformed data URL");
|
|
1463
|
+
const meta = src.slice(0, comma);
|
|
1464
|
+
if (!/;base64$/i.test(meta)) throw new Error("data URL must be base64-encoded");
|
|
1465
|
+
const bin = atob(src.slice(comma + 1));
|
|
1466
|
+
const bytes = new Uint8Array(bin.length);
|
|
1467
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
1468
|
+
return bytes;
|
|
1469
|
+
}
|
|
1470
|
+
const res = await fetch(src);
|
|
1471
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1472
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
1473
|
+
}
|
|
1474
|
+
function describeImageSrc(src) {
|
|
1475
|
+
return src.length > 80 ? `${src.slice(0, 77)}...` : src;
|
|
1476
|
+
}
|
|
1477
|
+
async function preloadImages(pdfDoc, content) {
|
|
1478
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
1479
|
+
(function walk(node) {
|
|
1480
|
+
if (node.type === "imageNode") {
|
|
1481
|
+
const src = node.attrs?.src || "";
|
|
1482
|
+
if (src) srcs.add(src);
|
|
1483
|
+
}
|
|
1484
|
+
for (const child of node.content || []) walk(child);
|
|
1485
|
+
})(content);
|
|
1486
|
+
const images = /* @__PURE__ */ new Map();
|
|
1487
|
+
const warnings = [];
|
|
1488
|
+
for (const src of srcs) {
|
|
1489
|
+
try {
|
|
1490
|
+
const bytes = await loadImageBytes(src);
|
|
1491
|
+
const isPng = bytes.length > 4 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
|
|
1492
|
+
const isJpeg = bytes.length > 2 && bytes[0] === 255 && bytes[1] === 216;
|
|
1493
|
+
if (isPng) {
|
|
1494
|
+
images.set(src, await pdfDoc.embedPng(bytes));
|
|
1495
|
+
} else if (isJpeg) {
|
|
1496
|
+
images.set(src, await pdfDoc.embedJpg(bytes));
|
|
1497
|
+
} else {
|
|
1498
|
+
warnings.push(`Image skipped (unsupported format \u2014 only PNG/JPEG embed): ${describeImageSrc(src)}`);
|
|
1499
|
+
}
|
|
1500
|
+
} catch (err) {
|
|
1501
|
+
warnings.push(`Image skipped (${getErrorMessage(err)}): ${describeImageSrc(src)}`);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return { images, warnings };
|
|
1505
|
+
}
|
|
1371
1506
|
function ensureSpace(state, neededHeight) {
|
|
1372
1507
|
if (state.y + neededHeight > CONTENT_HEIGHT) {
|
|
1373
1508
|
const newPage = state.pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
@@ -1715,9 +1850,9 @@ function renderTable(state, node, region = DEFAULT_REGION) {
|
|
|
1715
1850
|
const cellPadding = borderless ? 2 : 4;
|
|
1716
1851
|
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1717
1852
|
const rowPadding = cellPadding * 2;
|
|
1718
|
-
const borderColor =
|
|
1719
|
-
const headerBg =
|
|
1720
|
-
const headerTextColor =
|
|
1853
|
+
const borderColor = state.theme.tableBorder;
|
|
1854
|
+
const headerBg = state.theme.tableHeaderBg;
|
|
1855
|
+
const headerTextColor = state.theme.tableHeaderText;
|
|
1721
1856
|
const firstRow = tableRows[0];
|
|
1722
1857
|
const colCount = firstRow.content?.length || 0;
|
|
1723
1858
|
if (colCount === 0) return;
|
|
@@ -1965,7 +2100,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
1965
2100
|
start: { x: region.leftX, y: ruleY },
|
|
1966
2101
|
end: { x: region.leftX + region.width, y: ruleY },
|
|
1967
2102
|
thickness: 2.5,
|
|
1968
|
-
color:
|
|
2103
|
+
color: state.theme.rule
|
|
1969
2104
|
});
|
|
1970
2105
|
state.y += 4;
|
|
1971
2106
|
break;
|
|
@@ -1998,6 +2133,39 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
1998
2133
|
renderTable(state, node, region);
|
|
1999
2134
|
break;
|
|
2000
2135
|
}
|
|
2136
|
+
case "imageNode": {
|
|
2137
|
+
const src = node.attrs?.src || "";
|
|
2138
|
+
const image = src ? state.images.get(src) : void 0;
|
|
2139
|
+
if (!image) break;
|
|
2140
|
+
const natW = image.width;
|
|
2141
|
+
const natH = image.height;
|
|
2142
|
+
let w = parseFloat(node.attrs?.width || "") || 0;
|
|
2143
|
+
let h = parseFloat(node.attrs?.height || "") || 0;
|
|
2144
|
+
if (w && !h) h = natH / natW * w;
|
|
2145
|
+
else if (h && !w) w = natW / natH * h;
|
|
2146
|
+
else if (!w && !h) {
|
|
2147
|
+
w = natW;
|
|
2148
|
+
h = natH;
|
|
2149
|
+
}
|
|
2150
|
+
if (w > region.width) {
|
|
2151
|
+
h = h * (region.width / w);
|
|
2152
|
+
w = region.width;
|
|
2153
|
+
}
|
|
2154
|
+
const maxH = CONTENT_HEIGHT * 0.6;
|
|
2155
|
+
if (h > maxH) {
|
|
2156
|
+
w = w * (maxH / h);
|
|
2157
|
+
h = maxH;
|
|
2158
|
+
}
|
|
2159
|
+
ensureSpace(state, h + 4);
|
|
2160
|
+
const align = node.attrs?.align || "left";
|
|
2161
|
+
let x = region.leftX;
|
|
2162
|
+
if (align === "center") x = region.leftX + (region.width - w) / 2;
|
|
2163
|
+
else if (align === "right") x = region.leftX + region.width - w;
|
|
2164
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - h;
|
|
2165
|
+
state.currentPage.drawImage(image, { x, y: pdfY, width: w, height: h });
|
|
2166
|
+
state.y += h + 4;
|
|
2167
|
+
break;
|
|
2168
|
+
}
|
|
2001
2169
|
// ─── Layout directives ─────────────────────────────────────────
|
|
2002
2170
|
case "panel": {
|
|
2003
2171
|
const title = node.attrs?.title || "";
|
|
@@ -2034,8 +2202,8 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2034
2202
|
y: titleBarY,
|
|
2035
2203
|
width: region.width,
|
|
2036
2204
|
height: titleHeight,
|
|
2037
|
-
color: isDarkHeader ?
|
|
2038
|
-
borderColor:
|
|
2205
|
+
color: isDarkHeader ? state.theme.panelDarkHeaderBg : rgb(0.93, 0.93, 0.93),
|
|
2206
|
+
borderColor: state.theme.panelBorder,
|
|
2039
2207
|
borderWidth: 0.75
|
|
2040
2208
|
});
|
|
2041
2209
|
startPage.drawText(title, {
|
|
@@ -2043,7 +2211,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2043
2211
|
y: titleBarY + 4,
|
|
2044
2212
|
size: titleFontSize,
|
|
2045
2213
|
font: state.fonts.bold,
|
|
2046
|
-
color: isDarkHeader ?
|
|
2214
|
+
color: isDarkHeader ? state.theme.panelDarkHeaderText : rgb(0, 0, 0)
|
|
2047
2215
|
});
|
|
2048
2216
|
}
|
|
2049
2217
|
if (border !== "none") {
|
|
@@ -2056,7 +2224,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2056
2224
|
y: borderY,
|
|
2057
2225
|
width: region.width,
|
|
2058
2226
|
height: panelHeight,
|
|
2059
|
-
borderColor:
|
|
2227
|
+
borderColor: state.theme.panelBorder,
|
|
2060
2228
|
borderWidth: 0.75,
|
|
2061
2229
|
borderDashArray
|
|
2062
2230
|
});
|
|
@@ -2068,7 +2236,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2068
2236
|
y: startBorderBottom,
|
|
2069
2237
|
width: region.width,
|
|
2070
2238
|
height: startPanelHeight,
|
|
2071
|
-
borderColor:
|
|
2239
|
+
borderColor: state.theme.panelBorder,
|
|
2072
2240
|
borderWidth: 0.75,
|
|
2073
2241
|
borderDashArray
|
|
2074
2242
|
});
|
|
@@ -2080,7 +2248,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2080
2248
|
y: contBorderY,
|
|
2081
2249
|
width: region.width,
|
|
2082
2250
|
height: contHeight,
|
|
2083
|
-
borderColor:
|
|
2251
|
+
borderColor: state.theme.panelBorder,
|
|
2084
2252
|
borderWidth: 0.75,
|
|
2085
2253
|
borderDashArray
|
|
2086
2254
|
});
|
|
@@ -2384,6 +2552,24 @@ async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
|
2384
2552
|
PDFString$1.of(field.label)
|
|
2385
2553
|
);
|
|
2386
2554
|
}
|
|
2555
|
+
const initialsFontSize = Math.min(
|
|
2556
|
+
72,
|
|
2557
|
+
Math.max(8, Math.round(position.height * 0.55))
|
|
2558
|
+
);
|
|
2559
|
+
try {
|
|
2560
|
+
initialsField.setFontSize(initialsFontSize);
|
|
2561
|
+
const acro = initialsField;
|
|
2562
|
+
const daRef = acro.acroField.dict.get(PDFName$1.of("DA"));
|
|
2563
|
+
if (daRef) {
|
|
2564
|
+
const daStr = daRef.value;
|
|
2565
|
+
if (typeof daStr === "string") {
|
|
2566
|
+
const newDa = daStr.replace(/\/Helv\b/, "/TiBo").replace(/\/HeBo\b/, "/TiBo");
|
|
2567
|
+
acro.acroField.dict.set(PDFName$1.of("DA"), PDFString$1.of(newDa));
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
} catch (fontError) {
|
|
2571
|
+
console.warn(`Failed to set initials font:`, fontError);
|
|
2572
|
+
}
|
|
2387
2573
|
initialsField.enableReadOnly();
|
|
2388
2574
|
if (field.required) initialsField.enableRequired();
|
|
2389
2575
|
break;
|
|
@@ -2494,7 +2680,8 @@ async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
|
2494
2680
|
return warnings;
|
|
2495
2681
|
}
|
|
2496
2682
|
async function generatePdfFromContent(content, options = {}) {
|
|
2497
|
-
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [] } = options;
|
|
2683
|
+
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [], header, footer } = options;
|
|
2684
|
+
const theme = resolveTheme(options.theme);
|
|
2498
2685
|
const pdfDoc = await PDFDocument.create();
|
|
2499
2686
|
const firstPage = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
2500
2687
|
const fonts = {
|
|
@@ -2504,6 +2691,7 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2504
2691
|
boldItalic: await pdfDoc.embedFont(StandardFonts.HelveticaBoldOblique)
|
|
2505
2692
|
};
|
|
2506
2693
|
const watermarkNodes = collectWatermarkNodes(content);
|
|
2694
|
+
const { images, warnings: imageWarnings } = await preloadImages(pdfDoc, content);
|
|
2507
2695
|
const state = {
|
|
2508
2696
|
currentPage: firstPage,
|
|
2509
2697
|
pageIndex: 0,
|
|
@@ -2512,7 +2700,9 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2512
2700
|
pdfDoc,
|
|
2513
2701
|
fieldPositions: /* @__PURE__ */ new Map(),
|
|
2514
2702
|
drawFieldPlaceholders,
|
|
2515
|
-
watermarkNodes
|
|
2703
|
+
watermarkNodes,
|
|
2704
|
+
theme,
|
|
2705
|
+
images
|
|
2516
2706
|
};
|
|
2517
2707
|
renderWatermarksOnPage(firstPage, fonts, watermarkNodes);
|
|
2518
2708
|
if (content.content) {
|
|
@@ -2523,6 +2713,8 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2523
2713
|
const allPages = pdfDoc.getPages();
|
|
2524
2714
|
const totalPages = allPages.length;
|
|
2525
2715
|
const pageNumFontSize = 9;
|
|
2716
|
+
const hfFontSize = 8;
|
|
2717
|
+
const hfColor = rgb(0.45, 0.45, 0.45);
|
|
2526
2718
|
for (let i = 0; i < totalPages; i++) {
|
|
2527
2719
|
const page = allPages[i];
|
|
2528
2720
|
const label = `Page ${i + 1} of ${totalPages}`;
|
|
@@ -2534,6 +2726,43 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2534
2726
|
font: fonts.bold,
|
|
2535
2727
|
color: rgb(0, 0, 0)
|
|
2536
2728
|
});
|
|
2729
|
+
if (header && (header.left || header.right) && !(header.skipFirstPage && i === 0)) {
|
|
2730
|
+
const headerBaseY = PAGE_HEIGHT - MARGIN + 10;
|
|
2731
|
+
if (header.left) {
|
|
2732
|
+
page.drawText(header.left, {
|
|
2733
|
+
x: MARGIN,
|
|
2734
|
+
y: headerBaseY,
|
|
2735
|
+
size: pageNumFontSize,
|
|
2736
|
+
font: fonts.bold,
|
|
2737
|
+
color: theme.pageHeaderText
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
if (header.right) {
|
|
2741
|
+
const rightWidth = fonts.regular.widthOfTextAtSize(header.right, hfFontSize);
|
|
2742
|
+
page.drawText(header.right, {
|
|
2743
|
+
x: PAGE_WIDTH - MARGIN - rightWidth,
|
|
2744
|
+
y: headerBaseY,
|
|
2745
|
+
size: hfFontSize,
|
|
2746
|
+
font: fonts.regular,
|
|
2747
|
+
color: hfColor
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
page.drawLine({
|
|
2751
|
+
start: { x: MARGIN, y: headerBaseY - 6 },
|
|
2752
|
+
end: { x: PAGE_WIDTH - MARGIN, y: headerBaseY - 6 },
|
|
2753
|
+
thickness: 0.75,
|
|
2754
|
+
color: theme.pageHeaderRule
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
if (footer?.left) {
|
|
2758
|
+
page.drawText(footer.left, {
|
|
2759
|
+
x: MARGIN,
|
|
2760
|
+
y: MARGIN / 2 - pageNumFontSize / 2,
|
|
2761
|
+
size: hfFontSize,
|
|
2762
|
+
font: fonts.regular,
|
|
2763
|
+
color: hfColor
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2537
2766
|
}
|
|
2538
2767
|
let fieldWarnings;
|
|
2539
2768
|
if (embedFormFields && fields.length > 0) {
|
|
@@ -2544,25 +2773,30 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2544
2773
|
return {
|
|
2545
2774
|
pdfBytes,
|
|
2546
2775
|
fieldPositions: state.fieldPositions,
|
|
2547
|
-
fieldWarnings
|
|
2776
|
+
fieldWarnings,
|
|
2777
|
+
imageWarnings: imageWarnings.length > 0 ? imageWarnings : void 0
|
|
2548
2778
|
};
|
|
2549
2779
|
}
|
|
2550
2780
|
|
|
2551
2781
|
// src/utils/template-pipeline.ts
|
|
2552
|
-
async function generatePdfFromTiptap(content, values) {
|
|
2782
|
+
async function generatePdfFromTiptap(content, values, options = {}) {
|
|
2553
2783
|
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
2554
2784
|
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
2555
2785
|
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
2556
2786
|
const fields = extractFieldsFromContent(replaced);
|
|
2557
2787
|
const result = await generatePdfFromContent(replaced, {
|
|
2558
2788
|
embedFormFields: fields.length > 0,
|
|
2559
|
-
fields
|
|
2789
|
+
fields,
|
|
2790
|
+
theme: options.theme,
|
|
2791
|
+
header: options.header,
|
|
2792
|
+
footer: options.footer,
|
|
2793
|
+
drawFieldPlaceholders: options.drawFieldPlaceholders
|
|
2560
2794
|
});
|
|
2561
|
-
return { pdfBytes: result.pdfBytes };
|
|
2795
|
+
return { pdfBytes: result.pdfBytes, imageWarnings: result.imageWarnings };
|
|
2562
2796
|
}
|
|
2563
|
-
async function generatePdfFromMarkdown(markdown, values) {
|
|
2797
|
+
async function generatePdfFromMarkdown(markdown, values, options = {}) {
|
|
2564
2798
|
const content = markdownToTiptap(markdown);
|
|
2565
|
-
return generatePdfFromTiptap(content, values);
|
|
2799
|
+
return generatePdfFromTiptap(content, values, options);
|
|
2566
2800
|
}
|
|
2567
2801
|
|
|
2568
2802
|
// src/utils/markdown-writer.ts
|
|
@@ -2582,6 +2816,20 @@ function serializeFieldToken(attrs) {
|
|
|
2582
2816
|
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
2583
2817
|
return `{{${parts.join("|")}}}`;
|
|
2584
2818
|
}
|
|
2819
|
+
function serializeImageNode(attrs) {
|
|
2820
|
+
const hasExtras = attrs.varName || attrs.width || attrs.height || attrs.align;
|
|
2821
|
+
if (!hasExtras && attrs.src) {
|
|
2822
|
+
return ``;
|
|
2823
|
+
}
|
|
2824
|
+
const parts = ["image"];
|
|
2825
|
+
if (attrs.varName) parts.push(`var:${attrs.varName}`);
|
|
2826
|
+
else if (attrs.src) parts.push(`src:${attrs.src}`);
|
|
2827
|
+
if (attrs.alt) parts.push(`alt:${attrs.alt}`);
|
|
2828
|
+
if (attrs.width) parts.push(`width:${attrs.width}`);
|
|
2829
|
+
if (attrs.height) parts.push(`height:${attrs.height}`);
|
|
2830
|
+
if (attrs.align) parts.push(`align:${attrs.align}`);
|
|
2831
|
+
return `{{${parts.join("|")}}}`;
|
|
2832
|
+
}
|
|
2585
2833
|
function serializeVariableToken(attrs) {
|
|
2586
2834
|
const parts = ["var"];
|
|
2587
2835
|
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
@@ -2599,6 +2847,10 @@ function serializeInline(content) {
|
|
|
2599
2847
|
result += serializeFieldToken(node.attrs || {});
|
|
2600
2848
|
continue;
|
|
2601
2849
|
}
|
|
2850
|
+
if (node.type === "imageNode") {
|
|
2851
|
+
result += serializeImageNode(node.attrs || {});
|
|
2852
|
+
continue;
|
|
2853
|
+
}
|
|
2602
2854
|
if (node.type === "variableNode") {
|
|
2603
2855
|
let token = serializeVariableToken(node.attrs || {});
|
|
2604
2856
|
const varMarks = node.marks || [];
|
|
@@ -2705,6 +2957,9 @@ ${text}
|
|
|
2705
2957
|
case "tableCell": {
|
|
2706
2958
|
return (node.content || []).map(serializeBlock).join("");
|
|
2707
2959
|
}
|
|
2960
|
+
case "imageNode": {
|
|
2961
|
+
return serializeImageNode(node.attrs || {});
|
|
2962
|
+
}
|
|
2708
2963
|
case "watermark": {
|
|
2709
2964
|
const attrs = node.attrs || {};
|
|
2710
2965
|
const parts = [];
|
|
@@ -4768,8 +5023,8 @@ function validateMarkdown(markdown) {
|
|
|
4768
5023
|
errors.push(`Malformed token at line ${lineNum}: missing | delimiter`);
|
|
4769
5024
|
} else {
|
|
4770
5025
|
const tokenType = tokenContent.slice(0, pipeIdx);
|
|
4771
|
-
if (tokenType !== "field" && tokenType !== "var") {
|
|
4772
|
-
errors.push(`Unknown token type "${tokenType}" at line ${lineNum} (expected "field" or "
|
|
5026
|
+
if (tokenType !== "field" && tokenType !== "var" && tokenType !== "image") {
|
|
5027
|
+
errors.push(`Unknown token type "${tokenType}" at line ${lineNum} (expected "field", "var" or "image")`);
|
|
4773
5028
|
} else {
|
|
4774
5029
|
const body = tokenContent.slice(pipeIdx + 1);
|
|
4775
5030
|
const pairs = body.split("|");
|
|
@@ -4783,6 +5038,11 @@ function validateMarkdown(markdown) {
|
|
|
4783
5038
|
if (!hasName) {
|
|
4784
5039
|
errors.push(`Variable token at line ${lineNum} is missing required "name" attribute`);
|
|
4785
5040
|
}
|
|
5041
|
+
} else if (tokenType === "image") {
|
|
5042
|
+
const hasSource = pairs.some((p) => p.startsWith("src:") || p.startsWith("var:"));
|
|
5043
|
+
if (!hasSource) {
|
|
5044
|
+
errors.push(`Image token at line ${lineNum} needs a "src" or "var" attribute`);
|
|
5045
|
+
}
|
|
4786
5046
|
}
|
|
4787
5047
|
}
|
|
4788
5048
|
}
|