@signiphi/pdf-compose 0.1.0-beta.6 → 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 +286 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +286 -26
- package/dist/index.mjs.map +1 -1
- package/dist/styles/index.css +3 -0
- package/dist/styles/index.d.ts +1 -0
- 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 +51 -1
- package/dist/utils/pdf-generator.d.ts.map +1 -1
- package/dist/utils/template-pipeline.d.ts +20 -3
- package/dist/utils/template-pipeline.d.ts.map +1 -1
- package/dist/utils/variable-helpers.d.ts.map +1 -1
- package/package.json +12 -6
- package/src/styles/index.d.ts +1 -0
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ export type { Acknowledgement, DocumentGeneratorField, DocumentGeneratorVariable
|
|
|
6
6
|
export { FormFieldType } from './types';
|
|
7
7
|
export { suppressZeroContent, isZeroLike, expandRepeatContent, extractVariablesFromContent } from './utils/variable-helpers';
|
|
8
8
|
export { generatePdfFromMarkdown, generatePdfFromTiptap } from './utils/template-pipeline';
|
|
9
|
-
export type { HeadlessPdfResult } from './utils/template-pipeline';
|
|
9
|
+
export type { HeadlessPdfResult, HeadlessPdfOptions } from './utils/template-pipeline';
|
|
10
|
+
export type { PdfTheme, PdfPageHeader, PdfPageFooter } from './utils/pdf-generator';
|
|
10
11
|
export { markdownToTiptap } from './utils/markdown-parser';
|
|
11
12
|
export { tiptapToMarkdown } from './utils/markdown-writer';
|
|
12
13
|
export { RepeatNode } from './extensions/repeat-node';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAGpE,YAAY,EACV,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,6BAA6B,EAC7B,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,OAAO,GACR,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGxC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAG7H,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC3F,YAAY,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAGpE,YAAY,EACV,eAAe,EACf,sBAAsB,EACtB,yBAAyB,EACzB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,6BAA6B,EAC7B,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,OAAO,GACR,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGxC,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAG7H,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC3F,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AACvF,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGpF,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAG1E,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAClE,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -608,6 +608,11 @@ function replaceVariablesInContent(content, values) {
|
|
|
608
608
|
}
|
|
609
609
|
return textNode;
|
|
610
610
|
}
|
|
611
|
+
if (node.type === "imageNode" && node.attrs?.varName) {
|
|
612
|
+
const varName = node.attrs.varName;
|
|
613
|
+
const resolved = values[varName] || node.attrs.src || "";
|
|
614
|
+
return { ...node, attrs: { ...node.attrs, src: resolved } };
|
|
615
|
+
}
|
|
611
616
|
if (node.content) {
|
|
612
617
|
return { ...node, content: node.content.map(walkNode) };
|
|
613
618
|
}
|
|
@@ -736,7 +741,8 @@ function expandRepeatContent(content, values) {
|
|
|
736
741
|
values: enrichedValues
|
|
737
742
|
};
|
|
738
743
|
}
|
|
739
|
-
var TOKEN_REGEX = /\{\{(field|var)\|([^}]+)\}\}/g;
|
|
744
|
+
var TOKEN_REGEX = /\{\{(field|var|image)\|([^}]+)\}\}/g;
|
|
745
|
+
var BLOCK_IMAGE_REGEX = /^!\[([^\]]*)\]\(([^)\s]+)\)$/;
|
|
740
746
|
var DIRECTIVE_OPEN = /^:::(panel|columns|col|watermark|repeat|subtotals)(?:\{([^}]*)\})?$/;
|
|
741
747
|
var DIRECTIVE_CLOSE = /^:::$/;
|
|
742
748
|
function parseDirectiveAttrs(str) {
|
|
@@ -881,6 +887,44 @@ function parseVariableToken(tokenBody) {
|
|
|
881
887
|
}
|
|
882
888
|
return attrs;
|
|
883
889
|
}
|
|
890
|
+
function parseImageToken(tokenBody) {
|
|
891
|
+
const attrs = {
|
|
892
|
+
src: "",
|
|
893
|
+
varName: "",
|
|
894
|
+
alt: "",
|
|
895
|
+
width: "",
|
|
896
|
+
height: "",
|
|
897
|
+
align: ""
|
|
898
|
+
};
|
|
899
|
+
const pairs = tokenBody.split("|");
|
|
900
|
+
for (const pair of pairs) {
|
|
901
|
+
const colonIdx = pair.indexOf(":");
|
|
902
|
+
if (colonIdx === -1) continue;
|
|
903
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
904
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
905
|
+
switch (key) {
|
|
906
|
+
case "src":
|
|
907
|
+
attrs.src = value;
|
|
908
|
+
break;
|
|
909
|
+
case "var":
|
|
910
|
+
attrs.varName = value;
|
|
911
|
+
break;
|
|
912
|
+
case "alt":
|
|
913
|
+
attrs.alt = value;
|
|
914
|
+
break;
|
|
915
|
+
case "width":
|
|
916
|
+
attrs.width = value;
|
|
917
|
+
break;
|
|
918
|
+
case "height":
|
|
919
|
+
attrs.height = value;
|
|
920
|
+
break;
|
|
921
|
+
case "align":
|
|
922
|
+
attrs.align = value;
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return attrs;
|
|
927
|
+
}
|
|
884
928
|
function parseInline(text) {
|
|
885
929
|
text = preprocessMarkedTokens(text);
|
|
886
930
|
const nodes = [];
|
|
@@ -904,6 +948,8 @@ function parseInline(text) {
|
|
|
904
948
|
const node = { type: "variableNode", attrs };
|
|
905
949
|
if (marks.length > 0) node.marks = marks;
|
|
906
950
|
nodes.push(node);
|
|
951
|
+
} else if (match[1] === "image") {
|
|
952
|
+
nodes.push({ type: "imageNode", attrs: parseImageToken(match[2]) });
|
|
907
953
|
}
|
|
908
954
|
lastIndex = match.index + match[0].length;
|
|
909
955
|
}
|
|
@@ -1217,10 +1263,24 @@ function parseBlocks(lines, startIdx, stopCondition) {
|
|
|
1217
1263
|
i++;
|
|
1218
1264
|
continue;
|
|
1219
1265
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
content
|
|
1223
|
-
|
|
1266
|
+
const imageMatch = line.trim().match(BLOCK_IMAGE_REGEX);
|
|
1267
|
+
if (imageMatch) {
|
|
1268
|
+
content.push({
|
|
1269
|
+
type: "imageNode",
|
|
1270
|
+
attrs: { src: imageMatch[2], varName: "", alt: imageMatch[1], width: "", height: "", align: "" }
|
|
1271
|
+
});
|
|
1272
|
+
i++;
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
const inlineNodes = parseInline(line);
|
|
1276
|
+
const nonWhitespace = inlineNodes.filter(
|
|
1277
|
+
(n) => !(n.type === "text" && !(n.text || "").trim())
|
|
1278
|
+
);
|
|
1279
|
+
if (nonWhitespace.length === 1 && nonWhitespace[0].type === "imageNode") {
|
|
1280
|
+
content.push(nonWhitespace[0]);
|
|
1281
|
+
} else {
|
|
1282
|
+
content.push({ type: "paragraph", content: inlineNodes });
|
|
1283
|
+
}
|
|
1224
1284
|
i++;
|
|
1225
1285
|
}
|
|
1226
1286
|
return { blocks: content, nextIdx: i };
|
|
@@ -1371,6 +1431,34 @@ var CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
|
|
|
1371
1431
|
var CONTENT_HEIGHT = PAGE_HEIGHT - 2 * MARGIN;
|
|
1372
1432
|
var LINE_HEIGHT_FACTOR = 1.4;
|
|
1373
1433
|
var BODY_FONT_SIZE = 10;
|
|
1434
|
+
function hexToRgbColor(hex) {
|
|
1435
|
+
if (!hex) return null;
|
|
1436
|
+
const m = hex.trim().match(/^#?([0-9a-fA-F]{6})$/);
|
|
1437
|
+
if (!m) return null;
|
|
1438
|
+
const n = parseInt(m[1], 16);
|
|
1439
|
+
return pdfLib.rgb((n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255);
|
|
1440
|
+
}
|
|
1441
|
+
function contrastTextColor(fill) {
|
|
1442
|
+
const c = fill;
|
|
1443
|
+
const luminance = 0.299 * c.red + 0.587 * c.green + 0.114 * c.blue;
|
|
1444
|
+
return luminance > 0.6 ? pdfLib.rgb(0, 0, 0) : pdfLib.rgb(1, 1, 1);
|
|
1445
|
+
}
|
|
1446
|
+
function resolveTheme(theme) {
|
|
1447
|
+
const primary = hexToRgbColor(theme?.primaryColor);
|
|
1448
|
+
const accent = hexToRgbColor(theme?.accentColor);
|
|
1449
|
+
const border = hexToRgbColor(theme?.borderColor);
|
|
1450
|
+
return {
|
|
1451
|
+
tableHeaderBg: primary ?? pdfLib.rgb(0.3, 0.3, 0.3),
|
|
1452
|
+
tableHeaderText: primary ? contrastTextColor(primary) : pdfLib.rgb(1, 1, 1),
|
|
1453
|
+
tableBorder: border ?? pdfLib.rgb(0.75, 0.75, 0.75),
|
|
1454
|
+
panelDarkHeaderBg: primary ?? pdfLib.rgb(0.25, 0.25, 0.25),
|
|
1455
|
+
panelDarkHeaderText: primary ? contrastTextColor(primary) : pdfLib.rgb(1, 1, 1),
|
|
1456
|
+
panelBorder: border ?? pdfLib.rgb(0.6, 0.6, 0.6),
|
|
1457
|
+
rule: primary ?? pdfLib.rgb(0, 0, 0),
|
|
1458
|
+
pageHeaderText: primary ?? pdfLib.rgb(0, 0, 0),
|
|
1459
|
+
pageHeaderRule: accent ?? primary ?? pdfLib.rgb(0.75, 0.75, 0.75)
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1374
1462
|
var DEFAULT_REGION = { leftX: MARGIN, width: CONTENT_WIDTH };
|
|
1375
1463
|
var FIELD_DISPLAY = {
|
|
1376
1464
|
["text" /* TEXT */]: { label: "Text", width: 120, height: 30 },
|
|
@@ -1402,6 +1490,53 @@ function getFieldDimensions(fieldType, attrs, font, fontSize) {
|
|
|
1402
1490
|
const height = textLineHeight + 4;
|
|
1403
1491
|
return { width, height };
|
|
1404
1492
|
}
|
|
1493
|
+
async function loadImageBytes(src) {
|
|
1494
|
+
if (src.startsWith("data:")) {
|
|
1495
|
+
const comma = src.indexOf(",");
|
|
1496
|
+
if (comma === -1) throw new Error("malformed data URL");
|
|
1497
|
+
const meta = src.slice(0, comma);
|
|
1498
|
+
if (!/;base64$/i.test(meta)) throw new Error("data URL must be base64-encoded");
|
|
1499
|
+
const bin = atob(src.slice(comma + 1));
|
|
1500
|
+
const bytes = new Uint8Array(bin.length);
|
|
1501
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
1502
|
+
return bytes;
|
|
1503
|
+
}
|
|
1504
|
+
const res = await fetch(src);
|
|
1505
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1506
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
1507
|
+
}
|
|
1508
|
+
function describeImageSrc(src) {
|
|
1509
|
+
return src.length > 80 ? `${src.slice(0, 77)}...` : src;
|
|
1510
|
+
}
|
|
1511
|
+
async function preloadImages(pdfDoc, content) {
|
|
1512
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
1513
|
+
(function walk(node) {
|
|
1514
|
+
if (node.type === "imageNode") {
|
|
1515
|
+
const src = node.attrs?.src || "";
|
|
1516
|
+
if (src) srcs.add(src);
|
|
1517
|
+
}
|
|
1518
|
+
for (const child of node.content || []) walk(child);
|
|
1519
|
+
})(content);
|
|
1520
|
+
const images = /* @__PURE__ */ new Map();
|
|
1521
|
+
const warnings = [];
|
|
1522
|
+
for (const src of srcs) {
|
|
1523
|
+
try {
|
|
1524
|
+
const bytes = await loadImageBytes(src);
|
|
1525
|
+
const isPng = bytes.length > 4 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
|
|
1526
|
+
const isJpeg = bytes.length > 2 && bytes[0] === 255 && bytes[1] === 216;
|
|
1527
|
+
if (isPng) {
|
|
1528
|
+
images.set(src, await pdfDoc.embedPng(bytes));
|
|
1529
|
+
} else if (isJpeg) {
|
|
1530
|
+
images.set(src, await pdfDoc.embedJpg(bytes));
|
|
1531
|
+
} else {
|
|
1532
|
+
warnings.push(`Image skipped (unsupported format \u2014 only PNG/JPEG embed): ${describeImageSrc(src)}`);
|
|
1533
|
+
}
|
|
1534
|
+
} catch (err) {
|
|
1535
|
+
warnings.push(`Image skipped (${getErrorMessage(err)}): ${describeImageSrc(src)}`);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
return { images, warnings };
|
|
1539
|
+
}
|
|
1405
1540
|
function ensureSpace(state, neededHeight) {
|
|
1406
1541
|
if (state.y + neededHeight > CONTENT_HEIGHT) {
|
|
1407
1542
|
const newPage = state.pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
@@ -1749,9 +1884,9 @@ function renderTable(state, node, region = DEFAULT_REGION) {
|
|
|
1749
1884
|
const cellPadding = borderless ? 2 : 4;
|
|
1750
1885
|
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1751
1886
|
const rowPadding = cellPadding * 2;
|
|
1752
|
-
const borderColor =
|
|
1753
|
-
const headerBg =
|
|
1754
|
-
const headerTextColor =
|
|
1887
|
+
const borderColor = state.theme.tableBorder;
|
|
1888
|
+
const headerBg = state.theme.tableHeaderBg;
|
|
1889
|
+
const headerTextColor = state.theme.tableHeaderText;
|
|
1755
1890
|
const firstRow = tableRows[0];
|
|
1756
1891
|
const colCount = firstRow.content?.length || 0;
|
|
1757
1892
|
if (colCount === 0) return;
|
|
@@ -1999,7 +2134,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
1999
2134
|
start: { x: region.leftX, y: ruleY },
|
|
2000
2135
|
end: { x: region.leftX + region.width, y: ruleY },
|
|
2001
2136
|
thickness: 2.5,
|
|
2002
|
-
color:
|
|
2137
|
+
color: state.theme.rule
|
|
2003
2138
|
});
|
|
2004
2139
|
state.y += 4;
|
|
2005
2140
|
break;
|
|
@@ -2032,6 +2167,39 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2032
2167
|
renderTable(state, node, region);
|
|
2033
2168
|
break;
|
|
2034
2169
|
}
|
|
2170
|
+
case "imageNode": {
|
|
2171
|
+
const src = node.attrs?.src || "";
|
|
2172
|
+
const image = src ? state.images.get(src) : void 0;
|
|
2173
|
+
if (!image) break;
|
|
2174
|
+
const natW = image.width;
|
|
2175
|
+
const natH = image.height;
|
|
2176
|
+
let w = parseFloat(node.attrs?.width || "") || 0;
|
|
2177
|
+
let h = parseFloat(node.attrs?.height || "") || 0;
|
|
2178
|
+
if (w && !h) h = natH / natW * w;
|
|
2179
|
+
else if (h && !w) w = natW / natH * h;
|
|
2180
|
+
else if (!w && !h) {
|
|
2181
|
+
w = natW;
|
|
2182
|
+
h = natH;
|
|
2183
|
+
}
|
|
2184
|
+
if (w > region.width) {
|
|
2185
|
+
h = h * (region.width / w);
|
|
2186
|
+
w = region.width;
|
|
2187
|
+
}
|
|
2188
|
+
const maxH = CONTENT_HEIGHT * 0.6;
|
|
2189
|
+
if (h > maxH) {
|
|
2190
|
+
w = w * (maxH / h);
|
|
2191
|
+
h = maxH;
|
|
2192
|
+
}
|
|
2193
|
+
ensureSpace(state, h + 4);
|
|
2194
|
+
const align = node.attrs?.align || "left";
|
|
2195
|
+
let x = region.leftX;
|
|
2196
|
+
if (align === "center") x = region.leftX + (region.width - w) / 2;
|
|
2197
|
+
else if (align === "right") x = region.leftX + region.width - w;
|
|
2198
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - h;
|
|
2199
|
+
state.currentPage.drawImage(image, { x, y: pdfY, width: w, height: h });
|
|
2200
|
+
state.y += h + 4;
|
|
2201
|
+
break;
|
|
2202
|
+
}
|
|
2035
2203
|
// ─── Layout directives ─────────────────────────────────────────
|
|
2036
2204
|
case "panel": {
|
|
2037
2205
|
const title = node.attrs?.title || "";
|
|
@@ -2068,8 +2236,8 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2068
2236
|
y: titleBarY,
|
|
2069
2237
|
width: region.width,
|
|
2070
2238
|
height: titleHeight,
|
|
2071
|
-
color: isDarkHeader ?
|
|
2072
|
-
borderColor:
|
|
2239
|
+
color: isDarkHeader ? state.theme.panelDarkHeaderBg : pdfLib.rgb(0.93, 0.93, 0.93),
|
|
2240
|
+
borderColor: state.theme.panelBorder,
|
|
2073
2241
|
borderWidth: 0.75
|
|
2074
2242
|
});
|
|
2075
2243
|
startPage.drawText(title, {
|
|
@@ -2077,7 +2245,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2077
2245
|
y: titleBarY + 4,
|
|
2078
2246
|
size: titleFontSize,
|
|
2079
2247
|
font: state.fonts.bold,
|
|
2080
|
-
color: isDarkHeader ?
|
|
2248
|
+
color: isDarkHeader ? state.theme.panelDarkHeaderText : pdfLib.rgb(0, 0, 0)
|
|
2081
2249
|
});
|
|
2082
2250
|
}
|
|
2083
2251
|
if (border !== "none") {
|
|
@@ -2090,7 +2258,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2090
2258
|
y: borderY,
|
|
2091
2259
|
width: region.width,
|
|
2092
2260
|
height: panelHeight,
|
|
2093
|
-
borderColor:
|
|
2261
|
+
borderColor: state.theme.panelBorder,
|
|
2094
2262
|
borderWidth: 0.75,
|
|
2095
2263
|
borderDashArray
|
|
2096
2264
|
});
|
|
@@ -2102,7 +2270,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2102
2270
|
y: startBorderBottom,
|
|
2103
2271
|
width: region.width,
|
|
2104
2272
|
height: startPanelHeight,
|
|
2105
|
-
borderColor:
|
|
2273
|
+
borderColor: state.theme.panelBorder,
|
|
2106
2274
|
borderWidth: 0.75,
|
|
2107
2275
|
borderDashArray
|
|
2108
2276
|
});
|
|
@@ -2114,7 +2282,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2114
2282
|
y: contBorderY,
|
|
2115
2283
|
width: region.width,
|
|
2116
2284
|
height: contHeight,
|
|
2117
|
-
borderColor:
|
|
2285
|
+
borderColor: state.theme.panelBorder,
|
|
2118
2286
|
borderWidth: 0.75,
|
|
2119
2287
|
borderDashArray
|
|
2120
2288
|
});
|
|
@@ -2418,6 +2586,24 @@ async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
|
2418
2586
|
pdfLib.PDFString.of(field.label)
|
|
2419
2587
|
);
|
|
2420
2588
|
}
|
|
2589
|
+
const initialsFontSize = Math.min(
|
|
2590
|
+
72,
|
|
2591
|
+
Math.max(8, Math.round(position.height * 0.55))
|
|
2592
|
+
);
|
|
2593
|
+
try {
|
|
2594
|
+
initialsField.setFontSize(initialsFontSize);
|
|
2595
|
+
const acro = initialsField;
|
|
2596
|
+
const daRef = acro.acroField.dict.get(pdfLib.PDFName.of("DA"));
|
|
2597
|
+
if (daRef) {
|
|
2598
|
+
const daStr = daRef.value;
|
|
2599
|
+
if (typeof daStr === "string") {
|
|
2600
|
+
const newDa = daStr.replace(/\/Helv\b/, "/TiBo").replace(/\/HeBo\b/, "/TiBo");
|
|
2601
|
+
acro.acroField.dict.set(pdfLib.PDFName.of("DA"), pdfLib.PDFString.of(newDa));
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
} catch (fontError) {
|
|
2605
|
+
console.warn(`Failed to set initials font:`, fontError);
|
|
2606
|
+
}
|
|
2421
2607
|
initialsField.enableReadOnly();
|
|
2422
2608
|
if (field.required) initialsField.enableRequired();
|
|
2423
2609
|
break;
|
|
@@ -2528,7 +2714,8 @@ async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
|
2528
2714
|
return warnings;
|
|
2529
2715
|
}
|
|
2530
2716
|
async function generatePdfFromContent(content, options = {}) {
|
|
2531
|
-
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [] } = options;
|
|
2717
|
+
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [], header, footer } = options;
|
|
2718
|
+
const theme = resolveTheme(options.theme);
|
|
2532
2719
|
const pdfDoc = await pdfLib.PDFDocument.create();
|
|
2533
2720
|
const firstPage = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
2534
2721
|
const fonts = {
|
|
@@ -2538,6 +2725,7 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2538
2725
|
boldItalic: await pdfDoc.embedFont(pdfLib.StandardFonts.HelveticaBoldOblique)
|
|
2539
2726
|
};
|
|
2540
2727
|
const watermarkNodes = collectWatermarkNodes(content);
|
|
2728
|
+
const { images, warnings: imageWarnings } = await preloadImages(pdfDoc, content);
|
|
2541
2729
|
const state = {
|
|
2542
2730
|
currentPage: firstPage,
|
|
2543
2731
|
pageIndex: 0,
|
|
@@ -2546,7 +2734,9 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2546
2734
|
pdfDoc,
|
|
2547
2735
|
fieldPositions: /* @__PURE__ */ new Map(),
|
|
2548
2736
|
drawFieldPlaceholders,
|
|
2549
|
-
watermarkNodes
|
|
2737
|
+
watermarkNodes,
|
|
2738
|
+
theme,
|
|
2739
|
+
images
|
|
2550
2740
|
};
|
|
2551
2741
|
renderWatermarksOnPage(firstPage, fonts, watermarkNodes);
|
|
2552
2742
|
if (content.content) {
|
|
@@ -2557,6 +2747,8 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2557
2747
|
const allPages = pdfDoc.getPages();
|
|
2558
2748
|
const totalPages = allPages.length;
|
|
2559
2749
|
const pageNumFontSize = 9;
|
|
2750
|
+
const hfFontSize = 8;
|
|
2751
|
+
const hfColor = pdfLib.rgb(0.45, 0.45, 0.45);
|
|
2560
2752
|
for (let i = 0; i < totalPages; i++) {
|
|
2561
2753
|
const page = allPages[i];
|
|
2562
2754
|
const label = `Page ${i + 1} of ${totalPages}`;
|
|
@@ -2568,35 +2760,77 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2568
2760
|
font: fonts.bold,
|
|
2569
2761
|
color: pdfLib.rgb(0, 0, 0)
|
|
2570
2762
|
});
|
|
2763
|
+
if (header && (header.left || header.right) && !(header.skipFirstPage && i === 0)) {
|
|
2764
|
+
const headerBaseY = PAGE_HEIGHT - MARGIN + 10;
|
|
2765
|
+
if (header.left) {
|
|
2766
|
+
page.drawText(header.left, {
|
|
2767
|
+
x: MARGIN,
|
|
2768
|
+
y: headerBaseY,
|
|
2769
|
+
size: pageNumFontSize,
|
|
2770
|
+
font: fonts.bold,
|
|
2771
|
+
color: theme.pageHeaderText
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
if (header.right) {
|
|
2775
|
+
const rightWidth = fonts.regular.widthOfTextAtSize(header.right, hfFontSize);
|
|
2776
|
+
page.drawText(header.right, {
|
|
2777
|
+
x: PAGE_WIDTH - MARGIN - rightWidth,
|
|
2778
|
+
y: headerBaseY,
|
|
2779
|
+
size: hfFontSize,
|
|
2780
|
+
font: fonts.regular,
|
|
2781
|
+
color: hfColor
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
page.drawLine({
|
|
2785
|
+
start: { x: MARGIN, y: headerBaseY - 6 },
|
|
2786
|
+
end: { x: PAGE_WIDTH - MARGIN, y: headerBaseY - 6 },
|
|
2787
|
+
thickness: 0.75,
|
|
2788
|
+
color: theme.pageHeaderRule
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
if (footer?.left) {
|
|
2792
|
+
page.drawText(footer.left, {
|
|
2793
|
+
x: MARGIN,
|
|
2794
|
+
y: MARGIN / 2 - pageNumFontSize / 2,
|
|
2795
|
+
size: hfFontSize,
|
|
2796
|
+
font: fonts.regular,
|
|
2797
|
+
color: hfColor
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2571
2800
|
}
|
|
2572
2801
|
let fieldWarnings;
|
|
2573
2802
|
if (embedFormFields && fields.length > 0) {
|
|
2574
2803
|
const warnings = await addFormFields(pdfDoc, state.fieldPositions, fields);
|
|
2575
2804
|
if (warnings.length > 0) fieldWarnings = warnings;
|
|
2576
2805
|
}
|
|
2577
|
-
const pdfBytes = await pdfDoc.save({ useObjectStreams: false });
|
|
2806
|
+
const pdfBytes = new Uint8Array(await pdfDoc.save({ useObjectStreams: false }));
|
|
2578
2807
|
return {
|
|
2579
2808
|
pdfBytes,
|
|
2580
2809
|
fieldPositions: state.fieldPositions,
|
|
2581
|
-
fieldWarnings
|
|
2810
|
+
fieldWarnings,
|
|
2811
|
+
imageWarnings: imageWarnings.length > 0 ? imageWarnings : void 0
|
|
2582
2812
|
};
|
|
2583
2813
|
}
|
|
2584
2814
|
|
|
2585
2815
|
// src/utils/template-pipeline.ts
|
|
2586
|
-
async function generatePdfFromTiptap(content, values) {
|
|
2816
|
+
async function generatePdfFromTiptap(content, values, options = {}) {
|
|
2587
2817
|
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
2588
2818
|
const suppressed = suppressZeroContent(expanded, enrichedValues);
|
|
2589
2819
|
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
2590
2820
|
const fields = extractFieldsFromContent(replaced);
|
|
2591
2821
|
const result = await generatePdfFromContent(replaced, {
|
|
2592
2822
|
embedFormFields: fields.length > 0,
|
|
2593
|
-
fields
|
|
2823
|
+
fields,
|
|
2824
|
+
theme: options.theme,
|
|
2825
|
+
header: options.header,
|
|
2826
|
+
footer: options.footer,
|
|
2827
|
+
drawFieldPlaceholders: options.drawFieldPlaceholders
|
|
2594
2828
|
});
|
|
2595
|
-
return { pdfBytes: result.pdfBytes };
|
|
2829
|
+
return { pdfBytes: result.pdfBytes, imageWarnings: result.imageWarnings };
|
|
2596
2830
|
}
|
|
2597
|
-
async function generatePdfFromMarkdown(markdown, values) {
|
|
2831
|
+
async function generatePdfFromMarkdown(markdown, values, options = {}) {
|
|
2598
2832
|
const content = markdownToTiptap(markdown);
|
|
2599
|
-
return generatePdfFromTiptap(content, values);
|
|
2833
|
+
return generatePdfFromTiptap(content, values, options);
|
|
2600
2834
|
}
|
|
2601
2835
|
|
|
2602
2836
|
// src/utils/markdown-writer.ts
|
|
@@ -2616,6 +2850,20 @@ function serializeFieldToken(attrs) {
|
|
|
2616
2850
|
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
2617
2851
|
return `{{${parts.join("|")}}}`;
|
|
2618
2852
|
}
|
|
2853
|
+
function serializeImageNode(attrs) {
|
|
2854
|
+
const hasExtras = attrs.varName || attrs.width || attrs.height || attrs.align;
|
|
2855
|
+
if (!hasExtras && attrs.src) {
|
|
2856
|
+
return ``;
|
|
2857
|
+
}
|
|
2858
|
+
const parts = ["image"];
|
|
2859
|
+
if (attrs.varName) parts.push(`var:${attrs.varName}`);
|
|
2860
|
+
else if (attrs.src) parts.push(`src:${attrs.src}`);
|
|
2861
|
+
if (attrs.alt) parts.push(`alt:${attrs.alt}`);
|
|
2862
|
+
if (attrs.width) parts.push(`width:${attrs.width}`);
|
|
2863
|
+
if (attrs.height) parts.push(`height:${attrs.height}`);
|
|
2864
|
+
if (attrs.align) parts.push(`align:${attrs.align}`);
|
|
2865
|
+
return `{{${parts.join("|")}}}`;
|
|
2866
|
+
}
|
|
2619
2867
|
function serializeVariableToken(attrs) {
|
|
2620
2868
|
const parts = ["var"];
|
|
2621
2869
|
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
@@ -2633,6 +2881,10 @@ function serializeInline(content) {
|
|
|
2633
2881
|
result += serializeFieldToken(node.attrs || {});
|
|
2634
2882
|
continue;
|
|
2635
2883
|
}
|
|
2884
|
+
if (node.type === "imageNode") {
|
|
2885
|
+
result += serializeImageNode(node.attrs || {});
|
|
2886
|
+
continue;
|
|
2887
|
+
}
|
|
2636
2888
|
if (node.type === "variableNode") {
|
|
2637
2889
|
let token = serializeVariableToken(node.attrs || {});
|
|
2638
2890
|
const varMarks = node.marks || [];
|
|
@@ -2739,6 +2991,9 @@ ${text}
|
|
|
2739
2991
|
case "tableCell": {
|
|
2740
2992
|
return (node.content || []).map(serializeBlock).join("");
|
|
2741
2993
|
}
|
|
2994
|
+
case "imageNode": {
|
|
2995
|
+
return serializeImageNode(node.attrs || {});
|
|
2996
|
+
}
|
|
2742
2997
|
case "watermark": {
|
|
2743
2998
|
const attrs = node.attrs || {};
|
|
2744
2999
|
const parts = [];
|
|
@@ -4802,8 +5057,8 @@ function validateMarkdown(markdown) {
|
|
|
4802
5057
|
errors.push(`Malformed token at line ${lineNum}: missing | delimiter`);
|
|
4803
5058
|
} else {
|
|
4804
5059
|
const tokenType = tokenContent.slice(0, pipeIdx);
|
|
4805
|
-
if (tokenType !== "field" && tokenType !== "var") {
|
|
4806
|
-
errors.push(`Unknown token type "${tokenType}" at line ${lineNum} (expected "field" or "
|
|
5060
|
+
if (tokenType !== "field" && tokenType !== "var" && tokenType !== "image") {
|
|
5061
|
+
errors.push(`Unknown token type "${tokenType}" at line ${lineNum} (expected "field", "var" or "image")`);
|
|
4807
5062
|
} else {
|
|
4808
5063
|
const body = tokenContent.slice(pipeIdx + 1);
|
|
4809
5064
|
const pairs = body.split("|");
|
|
@@ -4817,6 +5072,11 @@ function validateMarkdown(markdown) {
|
|
|
4817
5072
|
if (!hasName) {
|
|
4818
5073
|
errors.push(`Variable token at line ${lineNum} is missing required "name" attribute`);
|
|
4819
5074
|
}
|
|
5075
|
+
} else if (tokenType === "image") {
|
|
5076
|
+
const hasSource = pairs.some((p) => p.startsWith("src:") || p.startsWith("var:"));
|
|
5077
|
+
if (!hasSource) {
|
|
5078
|
+
errors.push(`Image token at line ${lineNum} needs a "src" or "var" attribute`);
|
|
5079
|
+
}
|
|
4820
5080
|
}
|
|
4821
5081
|
}
|
|
4822
5082
|
}
|