@signiphi/pdf-compose 0.1.0-beta.7 → 0.1.0-beta.9
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/README.md +12 -5
- package/dist/components/GeneratePanel.d.ts.map +1 -1
- package/dist/extensions/variable-node.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +522 -206
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +522 -207
- 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 +21 -3
- package/dist/utils/template-pipeline.d.ts.map +1 -1
- package/dist/utils/variable-helpers.d.ts +28 -0
- 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
|
@@ -219,7 +219,8 @@ var VariableNode = Node.create({
|
|
|
219
219
|
varLabel: { default: "" },
|
|
220
220
|
varDefault: { default: "" },
|
|
221
221
|
format: { default: "" },
|
|
222
|
-
suppressZero: { default: "" }
|
|
222
|
+
suppressZero: { default: "" },
|
|
223
|
+
block: { default: "" }
|
|
223
224
|
};
|
|
224
225
|
},
|
|
225
226
|
parseHTML() {
|
|
@@ -526,183 +527,8 @@ function extractFieldsFromContent(content) {
|
|
|
526
527
|
}
|
|
527
528
|
return fields;
|
|
528
529
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
function extractVariablesFromContent(content) {
|
|
532
|
-
const seen = /* @__PURE__ */ new Map();
|
|
533
|
-
function walk(node) {
|
|
534
|
-
if (node.type === "variableNode" && node.attrs) {
|
|
535
|
-
const varName = node.attrs.varName;
|
|
536
|
-
if (varName && !seen.has(varName)) {
|
|
537
|
-
seen.set(varName, {
|
|
538
|
-
varName,
|
|
539
|
-
varLabel: node.attrs.varLabel || varName,
|
|
540
|
-
varDefault: node.attrs.varDefault || ""
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
if (node.content) {
|
|
545
|
-
node.content.forEach(walk);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
if (content.content) {
|
|
549
|
-
content.content.forEach(walk);
|
|
550
|
-
}
|
|
551
|
-
return Array.from(seen.values());
|
|
552
|
-
}
|
|
553
|
-
function applyFormat(value, format) {
|
|
554
|
-
if (format === "phone") {
|
|
555
|
-
const digits = value.replace(/\D/g, "");
|
|
556
|
-
if (digits.length === 10) {
|
|
557
|
-
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)} - ${digits.slice(6)}`;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
return value;
|
|
561
|
-
}
|
|
562
|
-
function replaceVariablesInContent(content, values) {
|
|
563
|
-
function walkNode(node) {
|
|
564
|
-
if (node.type === "variableNode" && node.attrs) {
|
|
565
|
-
const varName = node.attrs.varName;
|
|
566
|
-
let replacement = values[varName] || node.attrs.varDefault || "";
|
|
567
|
-
const format = node.attrs.format;
|
|
568
|
-
if (format && replacement) {
|
|
569
|
-
replacement = applyFormat(replacement, format);
|
|
570
|
-
}
|
|
571
|
-
const textNode = { type: "text", text: replacement };
|
|
572
|
-
if (node.marks && node.marks.length > 0) {
|
|
573
|
-
textNode.marks = node.marks;
|
|
574
|
-
}
|
|
575
|
-
return textNode;
|
|
576
|
-
}
|
|
577
|
-
if (node.content) {
|
|
578
|
-
return { ...node, content: node.content.map(walkNode) };
|
|
579
|
-
}
|
|
580
|
-
return node;
|
|
581
|
-
}
|
|
582
|
-
return walkNode(content);
|
|
583
|
-
}
|
|
584
|
-
function labelToVarName(label) {
|
|
585
|
-
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
586
|
-
}
|
|
587
|
-
function isZeroLike(value) {
|
|
588
|
-
if (!value || value.trim() === "") return true;
|
|
589
|
-
const cleaned = value.replace(/[$,\s]/g, "");
|
|
590
|
-
return /^-?0+(\.0+)?$/.test(cleaned);
|
|
591
|
-
}
|
|
592
|
-
function suppressZeroContent(content, values) {
|
|
593
|
-
function hasSuppressedVar(node) {
|
|
594
|
-
if (node.type === "variableNode" && node.attrs?.suppressZero === "true") {
|
|
595
|
-
const varName = node.attrs.varName;
|
|
596
|
-
const val = values[varName] || node.attrs.varDefault || "";
|
|
597
|
-
return isZeroLike(val);
|
|
598
|
-
}
|
|
599
|
-
return (node.content || []).some(hasSuppressedVar);
|
|
600
|
-
}
|
|
601
|
-
function walkNode(node) {
|
|
602
|
-
if (node.type === "table" && node.content) {
|
|
603
|
-
const filtered = node.content.filter((row) => {
|
|
604
|
-
if (row.type !== "tableRow") return true;
|
|
605
|
-
const isHeader = row.content?.[0]?.type === "tableHeader";
|
|
606
|
-
if (isHeader) return true;
|
|
607
|
-
return !hasSuppressedVar(row);
|
|
608
|
-
});
|
|
609
|
-
const bodyRows = filtered.filter(
|
|
610
|
-
(r) => r.type === "tableRow" && r.content?.[0]?.type !== "tableHeader"
|
|
611
|
-
);
|
|
612
|
-
if (bodyRows.length === 0) return null;
|
|
613
|
-
return { ...node, content: filtered };
|
|
614
|
-
}
|
|
615
|
-
if (node.type === "paragraph" && hasSuppressedVar(node)) {
|
|
616
|
-
return null;
|
|
617
|
-
}
|
|
618
|
-
if (node.content) {
|
|
619
|
-
const filtered = node.content.map((child) => walkNode(child)).filter((c) => c !== null);
|
|
620
|
-
return { ...node, content: filtered };
|
|
621
|
-
}
|
|
622
|
-
return node;
|
|
623
|
-
}
|
|
624
|
-
return walkNode(content) || content;
|
|
625
|
-
}
|
|
626
|
-
function expandRepeatContent(content, values) {
|
|
627
|
-
const enrichedValues = { ...values };
|
|
628
|
-
function cloneNode(node, dataKey, index) {
|
|
629
|
-
if (node.type === "variableNode" && node.attrs?.varName) {
|
|
630
|
-
const oldName = node.attrs.varName;
|
|
631
|
-
const bareName = oldName.replace(/^operation_/, "");
|
|
632
|
-
const newName = `${dataKey}_${index}_${bareName}`;
|
|
633
|
-
return { ...node, attrs: { ...node.attrs, varName: newName } };
|
|
634
|
-
}
|
|
635
|
-
if (node.content) {
|
|
636
|
-
return { ...node, content: node.content.map((n) => cloneNode(n, dataKey, index)) };
|
|
637
|
-
}
|
|
638
|
-
return { ...node };
|
|
639
|
-
}
|
|
640
|
-
function walkNode(node) {
|
|
641
|
-
if (node.type === "repeatBlock" && node.attrs?.data) {
|
|
642
|
-
const dataKey = node.attrs.data;
|
|
643
|
-
const arrayJson = values[dataKey];
|
|
644
|
-
if (!arrayJson) return [];
|
|
645
|
-
let items;
|
|
646
|
-
try {
|
|
647
|
-
items = JSON.parse(arrayJson);
|
|
648
|
-
} catch {
|
|
649
|
-
return [];
|
|
650
|
-
}
|
|
651
|
-
const sums = /* @__PURE__ */ new Map();
|
|
652
|
-
for (const item of items) {
|
|
653
|
-
for (const [key, val] of Object.entries(item)) {
|
|
654
|
-
const num = parseFloat(val);
|
|
655
|
-
if (!isNaN(num)) {
|
|
656
|
-
sums.set(key, (sums.get(key) || 0) + num);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
const woKeysByNorm = /* @__PURE__ */ new Map();
|
|
661
|
-
for (const k of Object.keys(enrichedValues)) {
|
|
662
|
-
if (k.startsWith("workorder_")) {
|
|
663
|
-
const norm = k.replace(/_/g, "");
|
|
664
|
-
woKeysByNorm.set(norm, k);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
for (const [field, total] of sums) {
|
|
668
|
-
const formatted = total % 1 === 0 ? total.toFixed(2) : total.toFixed(2);
|
|
669
|
-
const directKey = `workorder_${field}`;
|
|
670
|
-
enrichedValues[directKey] = formatted;
|
|
671
|
-
const norm = `workorder${field}`.replace(/_/g, "");
|
|
672
|
-
const existing = woKeysByNorm.get(norm);
|
|
673
|
-
if (existing && existing !== directKey) {
|
|
674
|
-
enrichedValues[existing] = formatted;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
const expanded = [];
|
|
678
|
-
for (let i = 0; i < items.length; i++) {
|
|
679
|
-
const item = items[i];
|
|
680
|
-
for (const [key, val] of Object.entries(item)) {
|
|
681
|
-
enrichedValues[`${dataKey}_${i}_${key}`] = String(val);
|
|
682
|
-
}
|
|
683
|
-
const cloned = (node.content || []).map((n) => cloneNode(n, dataKey, i));
|
|
684
|
-
expanded.push(...cloned);
|
|
685
|
-
}
|
|
686
|
-
return expanded;
|
|
687
|
-
}
|
|
688
|
-
if (node.content) {
|
|
689
|
-
const newContent = [];
|
|
690
|
-
for (const child of node.content) {
|
|
691
|
-
const result2 = walkNode(child);
|
|
692
|
-
if (Array.isArray(result2)) newContent.push(...result2);
|
|
693
|
-
else newContent.push(result2);
|
|
694
|
-
}
|
|
695
|
-
return { ...node, content: newContent };
|
|
696
|
-
}
|
|
697
|
-
return node;
|
|
698
|
-
}
|
|
699
|
-
const result = walkNode(content);
|
|
700
|
-
return {
|
|
701
|
-
content: Array.isArray(result) ? { ...content, content: result } : result,
|
|
702
|
-
values: enrichedValues
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
var TOKEN_REGEX = /\{\{(field|var)\|([^}]+)\}\}/g;
|
|
530
|
+
var TOKEN_REGEX = /\{\{(field|var|image)\|([^}]+)\}\}/g;
|
|
531
|
+
var BLOCK_IMAGE_REGEX = /^!\[([^\]]*)\]\(([^)\s]+)\)$/;
|
|
706
532
|
var DIRECTIVE_OPEN = /^:::(panel|columns|col|watermark|repeat|subtotals)(?:\{([^}]*)\})?$/;
|
|
707
533
|
var DIRECTIVE_CLOSE = /^:::$/;
|
|
708
534
|
function parseDirectiveAttrs(str) {
|
|
@@ -840,6 +666,9 @@ function parseVariableToken(tokenBody) {
|
|
|
840
666
|
case "format":
|
|
841
667
|
attrs.format = value;
|
|
842
668
|
break;
|
|
669
|
+
case "block":
|
|
670
|
+
attrs.block = value === "true" ? "true" : "";
|
|
671
|
+
break;
|
|
843
672
|
case "_marks":
|
|
844
673
|
attrs._marks = value;
|
|
845
674
|
break;
|
|
@@ -847,6 +676,44 @@ function parseVariableToken(tokenBody) {
|
|
|
847
676
|
}
|
|
848
677
|
return attrs;
|
|
849
678
|
}
|
|
679
|
+
function parseImageToken(tokenBody) {
|
|
680
|
+
const attrs = {
|
|
681
|
+
src: "",
|
|
682
|
+
varName: "",
|
|
683
|
+
alt: "",
|
|
684
|
+
width: "",
|
|
685
|
+
height: "",
|
|
686
|
+
align: ""
|
|
687
|
+
};
|
|
688
|
+
const pairs = tokenBody.split("|");
|
|
689
|
+
for (const pair of pairs) {
|
|
690
|
+
const colonIdx = pair.indexOf(":");
|
|
691
|
+
if (colonIdx === -1) continue;
|
|
692
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
693
|
+
const value = pair.slice(colonIdx + 1).trim();
|
|
694
|
+
switch (key) {
|
|
695
|
+
case "src":
|
|
696
|
+
attrs.src = value;
|
|
697
|
+
break;
|
|
698
|
+
case "var":
|
|
699
|
+
attrs.varName = value;
|
|
700
|
+
break;
|
|
701
|
+
case "alt":
|
|
702
|
+
attrs.alt = value;
|
|
703
|
+
break;
|
|
704
|
+
case "width":
|
|
705
|
+
attrs.width = value;
|
|
706
|
+
break;
|
|
707
|
+
case "height":
|
|
708
|
+
attrs.height = value;
|
|
709
|
+
break;
|
|
710
|
+
case "align":
|
|
711
|
+
attrs.align = value;
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return attrs;
|
|
716
|
+
}
|
|
850
717
|
function parseInline(text) {
|
|
851
718
|
text = preprocessMarkedTokens(text);
|
|
852
719
|
const nodes = [];
|
|
@@ -870,6 +737,8 @@ function parseInline(text) {
|
|
|
870
737
|
const node = { type: "variableNode", attrs };
|
|
871
738
|
if (marks.length > 0) node.marks = marks;
|
|
872
739
|
nodes.push(node);
|
|
740
|
+
} else if (match[1] === "image") {
|
|
741
|
+
nodes.push({ type: "imageNode", attrs: parseImageToken(match[2]) });
|
|
873
742
|
}
|
|
874
743
|
lastIndex = match.index + match[0].length;
|
|
875
744
|
}
|
|
@@ -1183,10 +1052,24 @@ function parseBlocks(lines, startIdx, stopCondition) {
|
|
|
1183
1052
|
i++;
|
|
1184
1053
|
continue;
|
|
1185
1054
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
content
|
|
1189
|
-
|
|
1055
|
+
const imageMatch = line.trim().match(BLOCK_IMAGE_REGEX);
|
|
1056
|
+
if (imageMatch) {
|
|
1057
|
+
content.push({
|
|
1058
|
+
type: "imageNode",
|
|
1059
|
+
attrs: { src: imageMatch[2], varName: "", alt: imageMatch[1], width: "", height: "", align: "" }
|
|
1060
|
+
});
|
|
1061
|
+
i++;
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
const inlineNodes = parseInline(line);
|
|
1065
|
+
const nonWhitespace = inlineNodes.filter(
|
|
1066
|
+
(n) => !(n.type === "text" && !(n.text || "").trim())
|
|
1067
|
+
);
|
|
1068
|
+
if (nonWhitespace.length === 1 && nonWhitespace[0].type === "imageNode") {
|
|
1069
|
+
content.push(nonWhitespace[0]);
|
|
1070
|
+
} else {
|
|
1071
|
+
content.push({ type: "paragraph", content: inlineNodes });
|
|
1072
|
+
}
|
|
1190
1073
|
i++;
|
|
1191
1074
|
}
|
|
1192
1075
|
return { blocks: content, nextIdx: i };
|
|
@@ -1197,6 +1080,233 @@ function markdownToTiptap(markdown) {
|
|
|
1197
1080
|
return { type: "doc", content: blocks };
|
|
1198
1081
|
}
|
|
1199
1082
|
|
|
1083
|
+
// src/utils/variable-helpers.ts
|
|
1084
|
+
function extractVariablesFromContent(content) {
|
|
1085
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1086
|
+
function walk(node) {
|
|
1087
|
+
if (node.type === "variableNode" && node.attrs) {
|
|
1088
|
+
const varName = node.attrs.varName;
|
|
1089
|
+
if (varName && !seen.has(varName)) {
|
|
1090
|
+
seen.set(varName, {
|
|
1091
|
+
varName,
|
|
1092
|
+
varLabel: node.attrs.varLabel || varName,
|
|
1093
|
+
varDefault: node.attrs.varDefault || ""
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
if (node.content) {
|
|
1098
|
+
node.content.forEach(walk);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (content.content) {
|
|
1102
|
+
content.content.forEach(walk);
|
|
1103
|
+
}
|
|
1104
|
+
return Array.from(seen.values());
|
|
1105
|
+
}
|
|
1106
|
+
function applyFormat(value, format) {
|
|
1107
|
+
if (format === "phone") {
|
|
1108
|
+
const digits = value.replace(/\D/g, "");
|
|
1109
|
+
if (digits.length === 10) {
|
|
1110
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)} - ${digits.slice(6)}`;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return value;
|
|
1114
|
+
}
|
|
1115
|
+
function replaceVariablesInContent(content, values) {
|
|
1116
|
+
function walkNode(node) {
|
|
1117
|
+
if (node.type === "variableNode" && node.attrs) {
|
|
1118
|
+
const varName = node.attrs.varName;
|
|
1119
|
+
let replacement = values[varName] || node.attrs.varDefault || "";
|
|
1120
|
+
const format = node.attrs.format;
|
|
1121
|
+
if (format && replacement) {
|
|
1122
|
+
replacement = applyFormat(replacement, format);
|
|
1123
|
+
}
|
|
1124
|
+
const textNode = { type: "text", text: replacement };
|
|
1125
|
+
if (node.marks && node.marks.length > 0) {
|
|
1126
|
+
textNode.marks = node.marks;
|
|
1127
|
+
}
|
|
1128
|
+
return textNode;
|
|
1129
|
+
}
|
|
1130
|
+
if (node.type === "imageNode" && node.attrs?.varName) {
|
|
1131
|
+
const varName = node.attrs.varName;
|
|
1132
|
+
const resolved = values[varName] || node.attrs.src || "";
|
|
1133
|
+
return { ...node, attrs: { ...node.attrs, src: resolved } };
|
|
1134
|
+
}
|
|
1135
|
+
if (node.content) {
|
|
1136
|
+
return { ...node, content: node.content.map(walkNode) };
|
|
1137
|
+
}
|
|
1138
|
+
return node;
|
|
1139
|
+
}
|
|
1140
|
+
return walkNode(content);
|
|
1141
|
+
}
|
|
1142
|
+
function expandBlockVariables(content, values) {
|
|
1143
|
+
function soleVariableOf(node) {
|
|
1144
|
+
if (node.type !== "paragraph" || !node.content) return null;
|
|
1145
|
+
const meaningful = node.content.filter(
|
|
1146
|
+
(c) => !(c.type === "text" && !(c.text || "").trim())
|
|
1147
|
+
);
|
|
1148
|
+
if (meaningful.length === 1 && meaningful[0].type === "variableNode") {
|
|
1149
|
+
return meaningful[0];
|
|
1150
|
+
}
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
function stripUnsafeNodes(node) {
|
|
1154
|
+
if (!node.content) return node;
|
|
1155
|
+
return {
|
|
1156
|
+
...node,
|
|
1157
|
+
content: node.content.filter((c) => c.type !== "fieldNode" && c.type !== "watermark").map(stripUnsafeNodes)
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
function isCellSafe(blocks) {
|
|
1161
|
+
return blocks.every((b) => b.type === "paragraph" || b.type === "heading");
|
|
1162
|
+
}
|
|
1163
|
+
function walkNode(node, inCell) {
|
|
1164
|
+
if (!node.content) return node;
|
|
1165
|
+
const childInCell = inCell || node.type === "tableCell" || node.type === "tableHeader";
|
|
1166
|
+
const newContent = [];
|
|
1167
|
+
for (const child of node.content) {
|
|
1168
|
+
const varNode = soleVariableOf(child);
|
|
1169
|
+
if (varNode?.attrs) {
|
|
1170
|
+
const varName = varNode.attrs.varName;
|
|
1171
|
+
const value = values[varName] || varNode.attrs.varDefault || "";
|
|
1172
|
+
const isBlock = varNode.attrs.block === "true" || value.includes("\n");
|
|
1173
|
+
if (isBlock) {
|
|
1174
|
+
const parsed = stripUnsafeNodes(markdownToTiptap(value));
|
|
1175
|
+
const blocks = parsed.content || [];
|
|
1176
|
+
if (!childInCell || isCellSafe(blocks)) {
|
|
1177
|
+
newContent.push(...blocks);
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
newContent.push(walkNode(child, childInCell));
|
|
1183
|
+
}
|
|
1184
|
+
return { ...node, content: newContent };
|
|
1185
|
+
}
|
|
1186
|
+
return walkNode(content, false);
|
|
1187
|
+
}
|
|
1188
|
+
function labelToVarName(label) {
|
|
1189
|
+
return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
1190
|
+
}
|
|
1191
|
+
function isZeroLike(value) {
|
|
1192
|
+
if (!value || value.trim() === "") return true;
|
|
1193
|
+
const cleaned = value.replace(/[$,\s]/g, "");
|
|
1194
|
+
return /^-?0+(\.0+)?$/.test(cleaned);
|
|
1195
|
+
}
|
|
1196
|
+
function suppressZeroContent(content, values) {
|
|
1197
|
+
function hasSuppressedVar(node) {
|
|
1198
|
+
if (node.type === "variableNode" && node.attrs?.suppressZero === "true") {
|
|
1199
|
+
const varName = node.attrs.varName;
|
|
1200
|
+
const val = values[varName] || node.attrs.varDefault || "";
|
|
1201
|
+
return isZeroLike(val);
|
|
1202
|
+
}
|
|
1203
|
+
return (node.content || []).some(hasSuppressedVar);
|
|
1204
|
+
}
|
|
1205
|
+
function walkNode(node) {
|
|
1206
|
+
if (node.type === "table" && node.content) {
|
|
1207
|
+
const filtered = node.content.filter((row) => {
|
|
1208
|
+
if (row.type !== "tableRow") return true;
|
|
1209
|
+
const isHeader = row.content?.[0]?.type === "tableHeader";
|
|
1210
|
+
if (isHeader) return true;
|
|
1211
|
+
return !hasSuppressedVar(row);
|
|
1212
|
+
});
|
|
1213
|
+
const bodyRows = filtered.filter(
|
|
1214
|
+
(r) => r.type === "tableRow" && r.content?.[0]?.type !== "tableHeader"
|
|
1215
|
+
);
|
|
1216
|
+
if (bodyRows.length === 0) return null;
|
|
1217
|
+
return { ...node, content: filtered };
|
|
1218
|
+
}
|
|
1219
|
+
if (node.type === "paragraph" && hasSuppressedVar(node)) {
|
|
1220
|
+
return null;
|
|
1221
|
+
}
|
|
1222
|
+
if (node.content) {
|
|
1223
|
+
const filtered = node.content.map((child) => walkNode(child)).filter((c) => c !== null);
|
|
1224
|
+
return { ...node, content: filtered };
|
|
1225
|
+
}
|
|
1226
|
+
return node;
|
|
1227
|
+
}
|
|
1228
|
+
return walkNode(content) || content;
|
|
1229
|
+
}
|
|
1230
|
+
function expandRepeatContent(content, values) {
|
|
1231
|
+
const enrichedValues = { ...values };
|
|
1232
|
+
function cloneNode(node, dataKey, index) {
|
|
1233
|
+
if (node.type === "variableNode" && node.attrs?.varName) {
|
|
1234
|
+
const oldName = node.attrs.varName;
|
|
1235
|
+
const bareName = oldName.replace(/^operation_/, "");
|
|
1236
|
+
const newName = `${dataKey}_${index}_${bareName}`;
|
|
1237
|
+
return { ...node, attrs: { ...node.attrs, varName: newName } };
|
|
1238
|
+
}
|
|
1239
|
+
if (node.content) {
|
|
1240
|
+
return { ...node, content: node.content.map((n) => cloneNode(n, dataKey, index)) };
|
|
1241
|
+
}
|
|
1242
|
+
return { ...node };
|
|
1243
|
+
}
|
|
1244
|
+
function walkNode(node) {
|
|
1245
|
+
if (node.type === "repeatBlock" && node.attrs?.data) {
|
|
1246
|
+
const dataKey = node.attrs.data;
|
|
1247
|
+
const arrayJson = values[dataKey];
|
|
1248
|
+
if (!arrayJson) return [];
|
|
1249
|
+
let items;
|
|
1250
|
+
try {
|
|
1251
|
+
items = JSON.parse(arrayJson);
|
|
1252
|
+
} catch {
|
|
1253
|
+
return [];
|
|
1254
|
+
}
|
|
1255
|
+
const sums = /* @__PURE__ */ new Map();
|
|
1256
|
+
for (const item of items) {
|
|
1257
|
+
for (const [key, val] of Object.entries(item)) {
|
|
1258
|
+
const num = parseFloat(val);
|
|
1259
|
+
if (!isNaN(num)) {
|
|
1260
|
+
sums.set(key, (sums.get(key) || 0) + num);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
const woKeysByNorm = /* @__PURE__ */ new Map();
|
|
1265
|
+
for (const k of Object.keys(enrichedValues)) {
|
|
1266
|
+
if (k.startsWith("workorder_")) {
|
|
1267
|
+
const norm = k.replace(/_/g, "");
|
|
1268
|
+
woKeysByNorm.set(norm, k);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
for (const [field, total] of sums) {
|
|
1272
|
+
const formatted = total % 1 === 0 ? total.toFixed(2) : total.toFixed(2);
|
|
1273
|
+
const directKey = `workorder_${field}`;
|
|
1274
|
+
enrichedValues[directKey] = formatted;
|
|
1275
|
+
const norm = `workorder${field}`.replace(/_/g, "");
|
|
1276
|
+
const existing = woKeysByNorm.get(norm);
|
|
1277
|
+
if (existing && existing !== directKey) {
|
|
1278
|
+
enrichedValues[existing] = formatted;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
const expanded = [];
|
|
1282
|
+
for (let i = 0; i < items.length; i++) {
|
|
1283
|
+
const item = items[i];
|
|
1284
|
+
for (const [key, val] of Object.entries(item)) {
|
|
1285
|
+
enrichedValues[`${dataKey}_${i}_${key}`] = String(val);
|
|
1286
|
+
}
|
|
1287
|
+
const cloned = (node.content || []).map((n) => cloneNode(n, dataKey, i));
|
|
1288
|
+
expanded.push(...cloned);
|
|
1289
|
+
}
|
|
1290
|
+
return expanded;
|
|
1291
|
+
}
|
|
1292
|
+
if (node.content) {
|
|
1293
|
+
const newContent = [];
|
|
1294
|
+
for (const child of node.content) {
|
|
1295
|
+
const result2 = walkNode(child);
|
|
1296
|
+
if (Array.isArray(result2)) newContent.push(...result2);
|
|
1297
|
+
else newContent.push(result2);
|
|
1298
|
+
}
|
|
1299
|
+
return { ...node, content: newContent };
|
|
1300
|
+
}
|
|
1301
|
+
return node;
|
|
1302
|
+
}
|
|
1303
|
+
const result = walkNode(content);
|
|
1304
|
+
return {
|
|
1305
|
+
content: Array.isArray(result) ? { ...content, content: result } : result,
|
|
1306
|
+
values: enrichedValues
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1200
1310
|
// src/utils/error-helpers.ts
|
|
1201
1311
|
function getErrorMessage(error) {
|
|
1202
1312
|
if (error instanceof Error) return error.message;
|
|
@@ -1337,6 +1447,34 @@ var CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
|
|
|
1337
1447
|
var CONTENT_HEIGHT = PAGE_HEIGHT - 2 * MARGIN;
|
|
1338
1448
|
var LINE_HEIGHT_FACTOR = 1.4;
|
|
1339
1449
|
var BODY_FONT_SIZE = 10;
|
|
1450
|
+
function hexToRgbColor(hex) {
|
|
1451
|
+
if (!hex) return null;
|
|
1452
|
+
const m = hex.trim().match(/^#?([0-9a-fA-F]{6})$/);
|
|
1453
|
+
if (!m) return null;
|
|
1454
|
+
const n = parseInt(m[1], 16);
|
|
1455
|
+
return rgb((n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255);
|
|
1456
|
+
}
|
|
1457
|
+
function contrastTextColor(fill) {
|
|
1458
|
+
const c = fill;
|
|
1459
|
+
const luminance = 0.299 * c.red + 0.587 * c.green + 0.114 * c.blue;
|
|
1460
|
+
return luminance > 0.6 ? rgb(0, 0, 0) : rgb(1, 1, 1);
|
|
1461
|
+
}
|
|
1462
|
+
function resolveTheme(theme) {
|
|
1463
|
+
const primary = hexToRgbColor(theme?.primaryColor);
|
|
1464
|
+
const accent = hexToRgbColor(theme?.accentColor);
|
|
1465
|
+
const border = hexToRgbColor(theme?.borderColor);
|
|
1466
|
+
return {
|
|
1467
|
+
tableHeaderBg: primary ?? rgb(0.3, 0.3, 0.3),
|
|
1468
|
+
tableHeaderText: primary ? contrastTextColor(primary) : rgb(1, 1, 1),
|
|
1469
|
+
tableBorder: border ?? rgb(0.75, 0.75, 0.75),
|
|
1470
|
+
panelDarkHeaderBg: primary ?? rgb(0.25, 0.25, 0.25),
|
|
1471
|
+
panelDarkHeaderText: primary ? contrastTextColor(primary) : rgb(1, 1, 1),
|
|
1472
|
+
panelBorder: border ?? rgb(0.6, 0.6, 0.6),
|
|
1473
|
+
rule: primary ?? rgb(0, 0, 0),
|
|
1474
|
+
pageHeaderText: primary ?? rgb(0, 0, 0),
|
|
1475
|
+
pageHeaderRule: accent ?? primary ?? rgb(0.75, 0.75, 0.75)
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1340
1478
|
var DEFAULT_REGION = { leftX: MARGIN, width: CONTENT_WIDTH };
|
|
1341
1479
|
var FIELD_DISPLAY = {
|
|
1342
1480
|
["text" /* TEXT */]: { label: "Text", width: 120, height: 30 },
|
|
@@ -1368,6 +1506,53 @@ function getFieldDimensions(fieldType, attrs, font, fontSize) {
|
|
|
1368
1506
|
const height = textLineHeight + 4;
|
|
1369
1507
|
return { width, height };
|
|
1370
1508
|
}
|
|
1509
|
+
async function loadImageBytes(src) {
|
|
1510
|
+
if (src.startsWith("data:")) {
|
|
1511
|
+
const comma = src.indexOf(",");
|
|
1512
|
+
if (comma === -1) throw new Error("malformed data URL");
|
|
1513
|
+
const meta = src.slice(0, comma);
|
|
1514
|
+
if (!/;base64$/i.test(meta)) throw new Error("data URL must be base64-encoded");
|
|
1515
|
+
const bin = atob(src.slice(comma + 1));
|
|
1516
|
+
const bytes = new Uint8Array(bin.length);
|
|
1517
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
1518
|
+
return bytes;
|
|
1519
|
+
}
|
|
1520
|
+
const res = await fetch(src);
|
|
1521
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1522
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
1523
|
+
}
|
|
1524
|
+
function describeImageSrc(src) {
|
|
1525
|
+
return src.length > 80 ? `${src.slice(0, 77)}...` : src;
|
|
1526
|
+
}
|
|
1527
|
+
async function preloadImages(pdfDoc, content) {
|
|
1528
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
1529
|
+
(function walk(node) {
|
|
1530
|
+
if (node.type === "imageNode") {
|
|
1531
|
+
const src = node.attrs?.src || "";
|
|
1532
|
+
if (src) srcs.add(src);
|
|
1533
|
+
}
|
|
1534
|
+
for (const child of node.content || []) walk(child);
|
|
1535
|
+
})(content);
|
|
1536
|
+
const images = /* @__PURE__ */ new Map();
|
|
1537
|
+
const warnings = [];
|
|
1538
|
+
for (const src of srcs) {
|
|
1539
|
+
try {
|
|
1540
|
+
const bytes = await loadImageBytes(src);
|
|
1541
|
+
const isPng = bytes.length > 4 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
|
|
1542
|
+
const isJpeg = bytes.length > 2 && bytes[0] === 255 && bytes[1] === 216;
|
|
1543
|
+
if (isPng) {
|
|
1544
|
+
images.set(src, await pdfDoc.embedPng(bytes));
|
|
1545
|
+
} else if (isJpeg) {
|
|
1546
|
+
images.set(src, await pdfDoc.embedJpg(bytes));
|
|
1547
|
+
} else {
|
|
1548
|
+
warnings.push(`Image skipped (unsupported format \u2014 only PNG/JPEG embed): ${describeImageSrc(src)}`);
|
|
1549
|
+
}
|
|
1550
|
+
} catch (err) {
|
|
1551
|
+
warnings.push(`Image skipped (${getErrorMessage(err)}): ${describeImageSrc(src)}`);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return { images, warnings };
|
|
1555
|
+
}
|
|
1371
1556
|
function ensureSpace(state, neededHeight) {
|
|
1372
1557
|
if (state.y + neededHeight > CONTENT_HEIGHT) {
|
|
1373
1558
|
const newPage = state.pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
@@ -1715,9 +1900,9 @@ function renderTable(state, node, region = DEFAULT_REGION) {
|
|
|
1715
1900
|
const cellPadding = borderless ? 2 : 4;
|
|
1716
1901
|
const lineHeight = fontSize * LINE_HEIGHT_FACTOR;
|
|
1717
1902
|
const rowPadding = cellPadding * 2;
|
|
1718
|
-
const borderColor =
|
|
1719
|
-
const headerBg =
|
|
1720
|
-
const headerTextColor =
|
|
1903
|
+
const borderColor = state.theme.tableBorder;
|
|
1904
|
+
const headerBg = state.theme.tableHeaderBg;
|
|
1905
|
+
const headerTextColor = state.theme.tableHeaderText;
|
|
1721
1906
|
const firstRow = tableRows[0];
|
|
1722
1907
|
const colCount = firstRow.content?.length || 0;
|
|
1723
1908
|
if (colCount === 0) return;
|
|
@@ -1965,7 +2150,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
1965
2150
|
start: { x: region.leftX, y: ruleY },
|
|
1966
2151
|
end: { x: region.leftX + region.width, y: ruleY },
|
|
1967
2152
|
thickness: 2.5,
|
|
1968
|
-
color:
|
|
2153
|
+
color: state.theme.rule
|
|
1969
2154
|
});
|
|
1970
2155
|
state.y += 4;
|
|
1971
2156
|
break;
|
|
@@ -1998,6 +2183,39 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
1998
2183
|
renderTable(state, node, region);
|
|
1999
2184
|
break;
|
|
2000
2185
|
}
|
|
2186
|
+
case "imageNode": {
|
|
2187
|
+
const src = node.attrs?.src || "";
|
|
2188
|
+
const image = src ? state.images.get(src) : void 0;
|
|
2189
|
+
if (!image) break;
|
|
2190
|
+
const natW = image.width;
|
|
2191
|
+
const natH = image.height;
|
|
2192
|
+
let w = parseFloat(node.attrs?.width || "") || 0;
|
|
2193
|
+
let h = parseFloat(node.attrs?.height || "") || 0;
|
|
2194
|
+
if (w && !h) h = natH / natW * w;
|
|
2195
|
+
else if (h && !w) w = natW / natH * h;
|
|
2196
|
+
else if (!w && !h) {
|
|
2197
|
+
w = natW;
|
|
2198
|
+
h = natH;
|
|
2199
|
+
}
|
|
2200
|
+
if (w > region.width) {
|
|
2201
|
+
h = h * (region.width / w);
|
|
2202
|
+
w = region.width;
|
|
2203
|
+
}
|
|
2204
|
+
const maxH = CONTENT_HEIGHT * 0.6;
|
|
2205
|
+
if (h > maxH) {
|
|
2206
|
+
w = w * (maxH / h);
|
|
2207
|
+
h = maxH;
|
|
2208
|
+
}
|
|
2209
|
+
ensureSpace(state, h + 4);
|
|
2210
|
+
const align = node.attrs?.align || "left";
|
|
2211
|
+
let x = region.leftX;
|
|
2212
|
+
if (align === "center") x = region.leftX + (region.width - w) / 2;
|
|
2213
|
+
else if (align === "right") x = region.leftX + region.width - w;
|
|
2214
|
+
const pdfY = PAGE_HEIGHT - MARGIN - state.y - h;
|
|
2215
|
+
state.currentPage.drawImage(image, { x, y: pdfY, width: w, height: h });
|
|
2216
|
+
state.y += h + 4;
|
|
2217
|
+
break;
|
|
2218
|
+
}
|
|
2001
2219
|
// ─── Layout directives ─────────────────────────────────────────
|
|
2002
2220
|
case "panel": {
|
|
2003
2221
|
const title = node.attrs?.title || "";
|
|
@@ -2034,8 +2252,8 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2034
2252
|
y: titleBarY,
|
|
2035
2253
|
width: region.width,
|
|
2036
2254
|
height: titleHeight,
|
|
2037
|
-
color: isDarkHeader ?
|
|
2038
|
-
borderColor:
|
|
2255
|
+
color: isDarkHeader ? state.theme.panelDarkHeaderBg : rgb(0.93, 0.93, 0.93),
|
|
2256
|
+
borderColor: state.theme.panelBorder,
|
|
2039
2257
|
borderWidth: 0.75
|
|
2040
2258
|
});
|
|
2041
2259
|
startPage.drawText(title, {
|
|
@@ -2043,7 +2261,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2043
2261
|
y: titleBarY + 4,
|
|
2044
2262
|
size: titleFontSize,
|
|
2045
2263
|
font: state.fonts.bold,
|
|
2046
|
-
color: isDarkHeader ?
|
|
2264
|
+
color: isDarkHeader ? state.theme.panelDarkHeaderText : rgb(0, 0, 0)
|
|
2047
2265
|
});
|
|
2048
2266
|
}
|
|
2049
2267
|
if (border !== "none") {
|
|
@@ -2056,7 +2274,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2056
2274
|
y: borderY,
|
|
2057
2275
|
width: region.width,
|
|
2058
2276
|
height: panelHeight,
|
|
2059
|
-
borderColor:
|
|
2277
|
+
borderColor: state.theme.panelBorder,
|
|
2060
2278
|
borderWidth: 0.75,
|
|
2061
2279
|
borderDashArray
|
|
2062
2280
|
});
|
|
@@ -2068,7 +2286,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2068
2286
|
y: startBorderBottom,
|
|
2069
2287
|
width: region.width,
|
|
2070
2288
|
height: startPanelHeight,
|
|
2071
|
-
borderColor:
|
|
2289
|
+
borderColor: state.theme.panelBorder,
|
|
2072
2290
|
borderWidth: 0.75,
|
|
2073
2291
|
borderDashArray
|
|
2074
2292
|
});
|
|
@@ -2080,7 +2298,7 @@ function processBlock(state, node, region = DEFAULT_REGION) {
|
|
|
2080
2298
|
y: contBorderY,
|
|
2081
2299
|
width: region.width,
|
|
2082
2300
|
height: contHeight,
|
|
2083
|
-
borderColor:
|
|
2301
|
+
borderColor: state.theme.panelBorder,
|
|
2084
2302
|
borderWidth: 0.75,
|
|
2085
2303
|
borderDashArray
|
|
2086
2304
|
});
|
|
@@ -2384,6 +2602,24 @@ async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
|
2384
2602
|
PDFString$1.of(field.label)
|
|
2385
2603
|
);
|
|
2386
2604
|
}
|
|
2605
|
+
const initialsFontSize = Math.min(
|
|
2606
|
+
72,
|
|
2607
|
+
Math.max(8, Math.round(position.height * 0.55))
|
|
2608
|
+
);
|
|
2609
|
+
try {
|
|
2610
|
+
initialsField.setFontSize(initialsFontSize);
|
|
2611
|
+
const acro = initialsField;
|
|
2612
|
+
const daRef = acro.acroField.dict.get(PDFName$1.of("DA"));
|
|
2613
|
+
if (daRef) {
|
|
2614
|
+
const daStr = daRef.value;
|
|
2615
|
+
if (typeof daStr === "string") {
|
|
2616
|
+
const newDa = daStr.replace(/\/Helv\b/, "/TiBo").replace(/\/HeBo\b/, "/TiBo");
|
|
2617
|
+
acro.acroField.dict.set(PDFName$1.of("DA"), PDFString$1.of(newDa));
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
} catch (fontError) {
|
|
2621
|
+
console.warn(`Failed to set initials font:`, fontError);
|
|
2622
|
+
}
|
|
2387
2623
|
initialsField.enableReadOnly();
|
|
2388
2624
|
if (field.required) initialsField.enableRequired();
|
|
2389
2625
|
break;
|
|
@@ -2494,7 +2730,8 @@ async function addFormFields(pdfDoc, fieldPositions, fields) {
|
|
|
2494
2730
|
return warnings;
|
|
2495
2731
|
}
|
|
2496
2732
|
async function generatePdfFromContent(content, options = {}) {
|
|
2497
|
-
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [] } = options;
|
|
2733
|
+
const { drawFieldPlaceholders = false, embedFormFields = false, fields = [], header, footer } = options;
|
|
2734
|
+
const theme = resolveTheme(options.theme);
|
|
2498
2735
|
const pdfDoc = await PDFDocument.create();
|
|
2499
2736
|
const firstPage = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
2500
2737
|
const fonts = {
|
|
@@ -2504,6 +2741,7 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2504
2741
|
boldItalic: await pdfDoc.embedFont(StandardFonts.HelveticaBoldOblique)
|
|
2505
2742
|
};
|
|
2506
2743
|
const watermarkNodes = collectWatermarkNodes(content);
|
|
2744
|
+
const { images, warnings: imageWarnings } = await preloadImages(pdfDoc, content);
|
|
2507
2745
|
const state = {
|
|
2508
2746
|
currentPage: firstPage,
|
|
2509
2747
|
pageIndex: 0,
|
|
@@ -2512,7 +2750,9 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2512
2750
|
pdfDoc,
|
|
2513
2751
|
fieldPositions: /* @__PURE__ */ new Map(),
|
|
2514
2752
|
drawFieldPlaceholders,
|
|
2515
|
-
watermarkNodes
|
|
2753
|
+
watermarkNodes,
|
|
2754
|
+
theme,
|
|
2755
|
+
images
|
|
2516
2756
|
};
|
|
2517
2757
|
renderWatermarksOnPage(firstPage, fonts, watermarkNodes);
|
|
2518
2758
|
if (content.content) {
|
|
@@ -2523,6 +2763,8 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2523
2763
|
const allPages = pdfDoc.getPages();
|
|
2524
2764
|
const totalPages = allPages.length;
|
|
2525
2765
|
const pageNumFontSize = 9;
|
|
2766
|
+
const hfFontSize = 8;
|
|
2767
|
+
const hfColor = rgb(0.45, 0.45, 0.45);
|
|
2526
2768
|
for (let i = 0; i < totalPages; i++) {
|
|
2527
2769
|
const page = allPages[i];
|
|
2528
2770
|
const label = `Page ${i + 1} of ${totalPages}`;
|
|
@@ -2534,6 +2776,43 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2534
2776
|
font: fonts.bold,
|
|
2535
2777
|
color: rgb(0, 0, 0)
|
|
2536
2778
|
});
|
|
2779
|
+
if (header && (header.left || header.right) && !(header.skipFirstPage && i === 0)) {
|
|
2780
|
+
const headerBaseY = PAGE_HEIGHT - MARGIN + 10;
|
|
2781
|
+
if (header.left) {
|
|
2782
|
+
page.drawText(header.left, {
|
|
2783
|
+
x: MARGIN,
|
|
2784
|
+
y: headerBaseY,
|
|
2785
|
+
size: pageNumFontSize,
|
|
2786
|
+
font: fonts.bold,
|
|
2787
|
+
color: theme.pageHeaderText
|
|
2788
|
+
});
|
|
2789
|
+
}
|
|
2790
|
+
if (header.right) {
|
|
2791
|
+
const rightWidth = fonts.regular.widthOfTextAtSize(header.right, hfFontSize);
|
|
2792
|
+
page.drawText(header.right, {
|
|
2793
|
+
x: PAGE_WIDTH - MARGIN - rightWidth,
|
|
2794
|
+
y: headerBaseY,
|
|
2795
|
+
size: hfFontSize,
|
|
2796
|
+
font: fonts.regular,
|
|
2797
|
+
color: hfColor
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2800
|
+
page.drawLine({
|
|
2801
|
+
start: { x: MARGIN, y: headerBaseY - 6 },
|
|
2802
|
+
end: { x: PAGE_WIDTH - MARGIN, y: headerBaseY - 6 },
|
|
2803
|
+
thickness: 0.75,
|
|
2804
|
+
color: theme.pageHeaderRule
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
if (footer?.left) {
|
|
2808
|
+
page.drawText(footer.left, {
|
|
2809
|
+
x: MARGIN,
|
|
2810
|
+
y: MARGIN / 2 - pageNumFontSize / 2,
|
|
2811
|
+
size: hfFontSize,
|
|
2812
|
+
font: fonts.regular,
|
|
2813
|
+
color: hfColor
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
2537
2816
|
}
|
|
2538
2817
|
let fieldWarnings;
|
|
2539
2818
|
if (embedFormFields && fields.length > 0) {
|
|
@@ -2544,25 +2823,31 @@ async function generatePdfFromContent(content, options = {}) {
|
|
|
2544
2823
|
return {
|
|
2545
2824
|
pdfBytes,
|
|
2546
2825
|
fieldPositions: state.fieldPositions,
|
|
2547
|
-
fieldWarnings
|
|
2826
|
+
fieldWarnings,
|
|
2827
|
+
imageWarnings: imageWarnings.length > 0 ? imageWarnings : void 0
|
|
2548
2828
|
};
|
|
2549
2829
|
}
|
|
2550
2830
|
|
|
2551
2831
|
// src/utils/template-pipeline.ts
|
|
2552
|
-
async function generatePdfFromTiptap(content, values) {
|
|
2832
|
+
async function generatePdfFromTiptap(content, values, options = {}) {
|
|
2553
2833
|
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
2554
|
-
const
|
|
2834
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
2835
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
2555
2836
|
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
2556
2837
|
const fields = extractFieldsFromContent(replaced);
|
|
2557
2838
|
const result = await generatePdfFromContent(replaced, {
|
|
2558
2839
|
embedFormFields: fields.length > 0,
|
|
2559
|
-
fields
|
|
2840
|
+
fields,
|
|
2841
|
+
theme: options.theme,
|
|
2842
|
+
header: options.header,
|
|
2843
|
+
footer: options.footer,
|
|
2844
|
+
drawFieldPlaceholders: options.drawFieldPlaceholders
|
|
2560
2845
|
});
|
|
2561
|
-
return { pdfBytes: result.pdfBytes };
|
|
2846
|
+
return { pdfBytes: result.pdfBytes, imageWarnings: result.imageWarnings };
|
|
2562
2847
|
}
|
|
2563
|
-
async function generatePdfFromMarkdown(markdown, values) {
|
|
2848
|
+
async function generatePdfFromMarkdown(markdown, values, options = {}) {
|
|
2564
2849
|
const content = markdownToTiptap(markdown);
|
|
2565
|
-
return generatePdfFromTiptap(content, values);
|
|
2850
|
+
return generatePdfFromTiptap(content, values, options);
|
|
2566
2851
|
}
|
|
2567
2852
|
|
|
2568
2853
|
// src/utils/markdown-writer.ts
|
|
@@ -2582,6 +2867,20 @@ function serializeFieldToken(attrs) {
|
|
|
2582
2867
|
if (attrs.acknowledgements) parts.push(`acks:${btoa(attrs.acknowledgements)}`);
|
|
2583
2868
|
return `{{${parts.join("|")}}}`;
|
|
2584
2869
|
}
|
|
2870
|
+
function serializeImageNode(attrs) {
|
|
2871
|
+
const hasExtras = attrs.varName || attrs.width || attrs.height || attrs.align;
|
|
2872
|
+
if (!hasExtras && attrs.src) {
|
|
2873
|
+
return ``;
|
|
2874
|
+
}
|
|
2875
|
+
const parts = ["image"];
|
|
2876
|
+
if (attrs.varName) parts.push(`var:${attrs.varName}`);
|
|
2877
|
+
else if (attrs.src) parts.push(`src:${attrs.src}`);
|
|
2878
|
+
if (attrs.alt) parts.push(`alt:${attrs.alt}`);
|
|
2879
|
+
if (attrs.width) parts.push(`width:${attrs.width}`);
|
|
2880
|
+
if (attrs.height) parts.push(`height:${attrs.height}`);
|
|
2881
|
+
if (attrs.align) parts.push(`align:${attrs.align}`);
|
|
2882
|
+
return `{{${parts.join("|")}}}`;
|
|
2883
|
+
}
|
|
2585
2884
|
function serializeVariableToken(attrs) {
|
|
2586
2885
|
const parts = ["var"];
|
|
2587
2886
|
if (attrs.varName) parts.push(`name:${attrs.varName}`);
|
|
@@ -2589,6 +2888,7 @@ function serializeVariableToken(attrs) {
|
|
|
2589
2888
|
if (attrs.varDefault) parts.push(`default:${attrs.varDefault}`);
|
|
2590
2889
|
if (attrs.suppressZero === "true") parts.push(`suppress:zero`);
|
|
2591
2890
|
if (attrs.format) parts.push(`format:${attrs.format}`);
|
|
2891
|
+
if (attrs.block === "true") parts.push(`block:true`);
|
|
2592
2892
|
return `{{${parts.join("|")}}}`;
|
|
2593
2893
|
}
|
|
2594
2894
|
function serializeInline(content) {
|
|
@@ -2599,6 +2899,10 @@ function serializeInline(content) {
|
|
|
2599
2899
|
result += serializeFieldToken(node.attrs || {});
|
|
2600
2900
|
continue;
|
|
2601
2901
|
}
|
|
2902
|
+
if (node.type === "imageNode") {
|
|
2903
|
+
result += serializeImageNode(node.attrs || {});
|
|
2904
|
+
continue;
|
|
2905
|
+
}
|
|
2602
2906
|
if (node.type === "variableNode") {
|
|
2603
2907
|
let token = serializeVariableToken(node.attrs || {});
|
|
2604
2908
|
const varMarks = node.marks || [];
|
|
@@ -2705,6 +3009,9 @@ ${text}
|
|
|
2705
3009
|
case "tableCell": {
|
|
2706
3010
|
return (node.content || []).map(serializeBlock).join("");
|
|
2707
3011
|
}
|
|
3012
|
+
case "imageNode": {
|
|
3013
|
+
return serializeImageNode(node.attrs || {});
|
|
3014
|
+
}
|
|
2708
3015
|
case "watermark": {
|
|
2709
3016
|
const attrs = node.attrs || {};
|
|
2710
3017
|
const parts = [];
|
|
@@ -4768,8 +5075,8 @@ function validateMarkdown(markdown) {
|
|
|
4768
5075
|
errors.push(`Malformed token at line ${lineNum}: missing | delimiter`);
|
|
4769
5076
|
} else {
|
|
4770
5077
|
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 "
|
|
5078
|
+
if (tokenType !== "field" && tokenType !== "var" && tokenType !== "image") {
|
|
5079
|
+
errors.push(`Unknown token type "${tokenType}" at line ${lineNum} (expected "field", "var" or "image")`);
|
|
4773
5080
|
} else {
|
|
4774
5081
|
const body = tokenContent.slice(pipeIdx + 1);
|
|
4775
5082
|
const pairs = body.split("|");
|
|
@@ -4783,6 +5090,11 @@ function validateMarkdown(markdown) {
|
|
|
4783
5090
|
if (!hasName) {
|
|
4784
5091
|
errors.push(`Variable token at line ${lineNum} is missing required "name" attribute`);
|
|
4785
5092
|
}
|
|
5093
|
+
} else if (tokenType === "image") {
|
|
5094
|
+
const hasSource = pairs.some((p) => p.startsWith("src:") || p.startsWith("var:"));
|
|
5095
|
+
if (!hasSource) {
|
|
5096
|
+
errors.push(`Image token at line ${lineNum} needs a "src" or "var" attribute`);
|
|
5097
|
+
}
|
|
4786
5098
|
}
|
|
4787
5099
|
}
|
|
4788
5100
|
}
|
|
@@ -5124,7 +5436,8 @@ function GeneratePanel({
|
|
|
5124
5436
|
try {
|
|
5125
5437
|
const content = getActiveContent();
|
|
5126
5438
|
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5127
|
-
const
|
|
5439
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5440
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5128
5441
|
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
5129
5442
|
const fields = extractFieldsFromContent(replaced);
|
|
5130
5443
|
const result = await generatePdfFromContent(replaced);
|
|
@@ -5147,7 +5460,8 @@ function GeneratePanel({
|
|
|
5147
5460
|
try {
|
|
5148
5461
|
const content = getActiveContent();
|
|
5149
5462
|
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, variableValues);
|
|
5150
|
-
const
|
|
5463
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5464
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5151
5465
|
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
5152
5466
|
const fields = extractFieldsFromContent(replaced);
|
|
5153
5467
|
const result = await generatePdfFromContent(replaced, {
|
|
@@ -5229,7 +5543,8 @@ function GeneratePanel({
|
|
|
5229
5543
|
for (let i = 0; i < bulkData.length; i++) {
|
|
5230
5544
|
const values = bulkData[i];
|
|
5231
5545
|
const { content: expanded, values: enrichedValues } = expandRepeatContent(content, values);
|
|
5232
|
-
const
|
|
5546
|
+
const blockExpanded = expandBlockVariables(expanded, enrichedValues);
|
|
5547
|
+
const suppressed = suppressZeroContent(blockExpanded, enrichedValues);
|
|
5233
5548
|
const replaced = replaceVariablesInContent(suppressed, enrichedValues);
|
|
5234
5549
|
const fields = extractFieldsFromContent(replaced);
|
|
5235
5550
|
const result = await generatePdfFromContent(replaced, {
|
|
@@ -7111,6 +7426,6 @@ function parseXmlTemplate(xmlString) {
|
|
|
7111
7426
|
};
|
|
7112
7427
|
}
|
|
7113
7428
|
|
|
7114
|
-
export { DocumentGenerator, EditorPanel, FormFieldType, PreviewPanel, RepeatNode, SubtotalsNode, ThemeProvider, expandRepeatContent, extractVariablesFromContent, generatePdfFromMarkdown, generatePdfFromTiptap, isZeroLike, markdownToTiptap, parseXmlTemplate, suppressZeroContent, tiptapToMarkdown, useDocumentGenerator, useTheme };
|
|
7429
|
+
export { DocumentGenerator, EditorPanel, FormFieldType, PreviewPanel, RepeatNode, SubtotalsNode, ThemeProvider, expandBlockVariables, expandRepeatContent, extractVariablesFromContent, generatePdfFromMarkdown, generatePdfFromTiptap, isZeroLike, markdownToTiptap, parseXmlTemplate, suppressZeroContent, tiptapToMarkdown, useDocumentGenerator, useTheme };
|
|
7115
7430
|
//# sourceMappingURL=index.mjs.map
|
|
7116
7431
|
//# sourceMappingURL=index.mjs.map
|