Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
co-authored by
renee-png
parent
99bb03c9dc
commit
00f16ca400
@@ -529,6 +529,84 @@ export function CustomFormBuilder() {
|
||||
doc.setFontSize(fontSize);
|
||||
const lineH = fontSize * 1.4;
|
||||
|
||||
// Group runs into visual lines (split where applied text contains "\n").
|
||||
const runsToLines = (runs: InlineRun[]): InlineRun[][] => {
|
||||
const lines: InlineRun[][] = [[]];
|
||||
for (const r of runs) {
|
||||
const parts = applyVariables(r.text, ctx).split("\n");
|
||||
parts.forEach((part, i) => {
|
||||
if (i > 0) lines.push([]);
|
||||
if (part) lines[lines.length - 1].push({ ...r, text: part });
|
||||
});
|
||||
}
|
||||
return lines;
|
||||
};
|
||||
|
||||
// Draw inline runs on one visual line, honoring word-wrap and per-run font style.
|
||||
const drawRunsLine = (
|
||||
runs: InlineRun[],
|
||||
startX: number,
|
||||
startY: number,
|
||||
widthLimit: number,
|
||||
align: "left" | "center" | "right" | "justify",
|
||||
): number => {
|
||||
type Token = { text: string; bold: boolean; italic: boolean; underline: boolean; isSpace: boolean };
|
||||
const tokens: Token[] = [];
|
||||
for (const r of runs) {
|
||||
const bold = !!r.bold;
|
||||
const italic = !!r.italic;
|
||||
const underline = !!r.underline;
|
||||
const parts = r.text.split(/(\s+)/);
|
||||
for (const p of parts) {
|
||||
if (!p) continue;
|
||||
tokens.push({ text: p, bold, italic, underline, isSpace: /^\s+$/.test(p) });
|
||||
}
|
||||
}
|
||||
const styleFor = (t: Token) =>
|
||||
t.bold && t.italic ? "bolditalic" : t.bold ? "bold" : t.italic ? "italic" : "normal";
|
||||
const widthOf = (t: Token) => {
|
||||
doc.setFont(pdfFont, styleFor(t));
|
||||
return doc.getTextWidth(t.text);
|
||||
};
|
||||
// Wrap into lines
|
||||
const lines: Token[][] = [[]];
|
||||
let cur = 0;
|
||||
for (const t of tokens) {
|
||||
const w = widthOf(t);
|
||||
if (cur + w > widthLimit && lines[lines.length - 1].length > 0) {
|
||||
lines.push([]);
|
||||
cur = 0;
|
||||
if (t.isSpace) continue;
|
||||
}
|
||||
lines[lines.length - 1].push(t);
|
||||
cur += w;
|
||||
}
|
||||
let yy = startY;
|
||||
for (const ln of lines) {
|
||||
while (ln.length && ln[ln.length - 1].isSpace) ln.pop();
|
||||
if (yy > pageH - margin) {
|
||||
doc.addPage();
|
||||
yy = margin;
|
||||
}
|
||||
const lineW = ln.reduce((a, t) => a + widthOf(t), 0);
|
||||
let x = startX;
|
||||
if (align === "center") x = startX + (widthLimit - lineW) / 2;
|
||||
else if (align === "right") x = startX + (widthLimit - lineW);
|
||||
for (const t of ln) {
|
||||
doc.setFont(pdfFont, styleFor(t));
|
||||
doc.text(t.text, x, yy);
|
||||
if (t.underline && !t.isSpace) {
|
||||
const w = widthOf(t);
|
||||
doc.setLineWidth(0.5);
|
||||
doc.line(x, yy + 1.5, x + w, yy + 1.5);
|
||||
}
|
||||
x += widthOf(t);
|
||||
}
|
||||
yy += lineH;
|
||||
}
|
||||
return yy;
|
||||
};
|
||||
|
||||
const drawTablePdf = (rows: TableCellSeg[][]) => {
|
||||
if (!rows.length) return;
|
||||
const cols = Math.max(...rows.map((r) => r.length));
|
||||
@@ -536,11 +614,19 @@ export function CustomFormBuilder() {
|
||||
const cellPad = 4;
|
||||
for (let ri = 0; ri < rows.length; ri++) {
|
||||
const row = rows[ri];
|
||||
// Compute row height based on tallest cell
|
||||
const cellLines = row.map((c) =>
|
||||
doc.splitTextToSize(applyVariables(c.text, ctx) || " ", colW - cellPad * 2),
|
||||
);
|
||||
const rowH = Math.max(...cellLines.map((l) => l.length)) * lineH + cellPad * 2;
|
||||
// Estimate row height by wrapping each cell's visual lines as plain text.
|
||||
const cellLineCounts = row.map((c) => {
|
||||
const ls = runsToLines(c?.runs ?? []);
|
||||
let total = 0;
|
||||
for (const lineRuns of ls) {
|
||||
doc.setFont(pdfFont, "normal");
|
||||
const flat = lineRuns.map((r) => r.text).join("");
|
||||
const wrapped = doc.splitTextToSize(flat || " ", colW - cellPad * 2);
|
||||
total += Math.max(1, wrapped.length);
|
||||
}
|
||||
return Math.max(1, total);
|
||||
});
|
||||
const rowH = Math.max(...cellLineCounts) * lineH + cellPad * 2;
|
||||
if (y + rowH > pageH - margin) {
|
||||
doc.addPage();
|
||||
y = margin;
|
||||
@@ -555,13 +641,11 @@ export function CustomFormBuilder() {
|
||||
if (borders.b) doc.line(x, y + rowH, x + colW, y + rowH);
|
||||
if (borders.l) doc.line(x, y, x, y + rowH);
|
||||
if (borders.r) doc.line(x + colW, y, x + colW, y + rowH);
|
||||
const isHeader = cell?.isHeader ?? false;
|
||||
doc.setFont(pdfFont, isHeader ? "bold" : "normal");
|
||||
const lines = cellLines[ci] ?? [""];
|
||||
// Render runs — no auto-bold for headers; user controls formatting.
|
||||
const visualLines = runsToLines(cell?.runs ?? []);
|
||||
let ty = y + cellPad + lineH * 0.8;
|
||||
for (const ln of lines) {
|
||||
doc.text(ln, x + cellPad, ty);
|
||||
ty += lineH;
|
||||
for (const lineRuns of visualLines) {
|
||||
ty = drawRunsLine(lineRuns, x + cellPad, ty, colW - cellPad * 2, "left");
|
||||
}
|
||||
}
|
||||
y += rowH;
|
||||
@@ -574,24 +658,21 @@ export function CustomFormBuilder() {
|
||||
drawTablePdf(seg.rows);
|
||||
continue;
|
||||
}
|
||||
const text = applyVariables(seg.text, ctx) || " ";
|
||||
doc.setFont(pdfFont, seg.heading && seg.heading > 0 ? "bold" : "normal");
|
||||
const indentPx = seg.indent || 0;
|
||||
const indentPt = indentPx * 0.75; // px → pt
|
||||
const segMaxW = maxW - indentPt;
|
||||
const lines = doc.splitTextToSize(text, segMaxW);
|
||||
for (const line of lines) {
|
||||
if (y > pageH - margin) {
|
||||
doc.addPage();
|
||||
y = margin;
|
||||
}
|
||||
let x = margin + indentPt;
|
||||
if (seg.align === "center") x = pageW / 2;
|
||||
else if (seg.align === "right") x = pageW - margin;
|
||||
doc.text(line, x, y, {
|
||||
align: seg.align === "center" ? "center" : seg.align === "right" ? "right" : "left",
|
||||
});
|
||||
const startX = margin + indentPt;
|
||||
const visualLines = runsToLines(seg.runs);
|
||||
if (visualLines.length === 0 || (visualLines.length === 1 && visualLines[0].length === 0)) {
|
||||
y += lineH;
|
||||
} else {
|
||||
for (const lineRuns of visualLines) {
|
||||
if (lineRuns.length === 0) {
|
||||
y += lineH;
|
||||
continue;
|
||||
}
|
||||
y = drawRunsLine(lineRuns, startX, y, segMaxW, seg.align);
|
||||
}
|
||||
}
|
||||
y += seg.caption ? 2 : 4;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user