Fixed caption borders and body

X-Lovable-Edit-ID: edt-233ca2ec-d347-4b10-a205-e7fd13ac61b9
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-18 18:53:57 +00:00
co-authored by renee-png
3 changed files with 53 additions and 26 deletions
+14 -5
View File
@@ -83,6 +83,13 @@ const captionLeftBorders = {
right: { style: BorderStyle.SINGLE, size: 8, color: "000000" },
};
const captionRightBorders = {
top: { style: BorderStyle.NONE, size: 0, color: "FFFFFF" },
bottom: { style: BorderStyle.SINGLE, size: 8, color: "000000" },
left: { style: BorderStyle.NONE, size: 0, color: "FFFFFF" },
right: { style: BorderStyle.NONE, size: 0, color: "FFFFFF" },
};
function run(text: string, opts: { bold?: boolean; italic?: boolean; underline?: boolean; superscript?: boolean; size?: number } = {}) {
return new TextRun({
text,
@@ -95,9 +102,10 @@ function run(text: string, opts: { bold?: boolean; italic?: boolean; underline?:
});
}
function p(text: string, opts: { bold?: boolean; align?: (typeof AlignmentType)[keyof typeof AlignmentType] } = {}) {
function p(text: string, opts: { bold?: boolean; align?: (typeof AlignmentType)[keyof typeof AlignmentType]; indent?: number } = {}) {
return new Paragraph({
alignment: opts.align,
indent: opts.indent ? { left: opts.indent } : undefined,
children: [run(text, { bold: opts.bold })],
});
}
@@ -263,19 +271,20 @@ export function buildPleadingDoc(input: PleadingInput): Document {
const plaintiffLines = (input.plaintiffs || "").split("\n").filter((l) => l.trim().length > 0);
const defendantLines = (input.defendants || "").split("\n").filter((l) => l.trim().length > 0);
// Left side of caption — all bold
// Left side of caption — all bold; "Plaintiff(s)," / "Defendant(s)." indented
const LABEL_INDENT = 720; // 0.5"
const leftCells: Paragraph[] = [];
plaintiffLines.forEach((l) => leftCells.push(p(l, { bold: true })));
if (plaintiffLines.length === 0) leftCells.push(p("", { bold: true }));
leftCells.push(emptyP());
leftCells.push(p("Plaintiff(s),", { bold: true }));
leftCells.push(p("Plaintiff(s),", { bold: true, indent: LABEL_INDENT }));
leftCells.push(emptyP());
leftCells.push(p("v.", { bold: true }));
leftCells.push(emptyP());
defendantLines.forEach((l) => leftCells.push(p(l, { bold: true })));
if (defendantLines.length === 0) leftCells.push(p("", { bold: true }));
leftCells.push(emptyP());
leftCells.push(p("Defendant(s).", { bold: true }));
leftCells.push(p("Defendant(s).", { bold: true, indent: LABEL_INDENT }));
const rightCells: Paragraph[] = [
p(`CASE NO.: ${input.caseNumber || ""}`, { bold: true }),
@@ -296,7 +305,7 @@ export function buildPleadingDoc(input: PleadingInput): Document {
}),
new TableCell({
width: { size: 3960, type: WidthType.DXA },
borders: noBorder,
borders: captionRightBorders,
margins: { top: 80, bottom: 80, left: 200, right: 0 },
children: rightCells,
}),
+34 -16
View File
@@ -195,28 +195,35 @@ export async function downloadPleadingPdf(input: PleadingInput, filename: string
y += LINE_H * 1.5;
const leftColW = CONTENT_W * 0.58;
const rightColW = CONTENT_W - leftColW - 24;
const leftX = MARGIN;
const rightX = MARGIN + leftColW + 24;
const captionRightEdge = MARGIN + CONTENT_W;
const labelIndent = 24; // ~1/3" indent for "Plaintiff(s)," / "Defendant(s)."
const plaintiffLines = (input.plaintiffs || "").split("\n").filter((l) => l.trim());
const defendantLines = (input.defendants || "").split("\n").filter((l) => l.trim());
setFont(pdf, { bold: true });
const captionLeft: string[] = [];
if (plaintiffLines.length) captionLeft.push(...plaintiffLines); else captionLeft.push(" ");
captionLeft.push("");
captionLeft.push("Plaintiff(s),");
captionLeft.push("");
captionLeft.push("v.");
captionLeft.push("");
if (defendantLines.length) captionLeft.push(...defendantLines); else captionLeft.push(" ");
captionLeft.push("");
captionLeft.push("Defendant(s).");
type CaptionRow = { text: string; indent?: number };
const captionLeft: CaptionRow[] = [];
if (plaintiffLines.length) plaintiffLines.forEach((l) => captionLeft.push({ text: l }));
else captionLeft.push({ text: " " });
captionLeft.push({ text: "" });
captionLeft.push({ text: "Plaintiff(s),", indent: labelIndent });
captionLeft.push({ text: "" });
captionLeft.push({ text: "v." });
captionLeft.push({ text: "" });
if (defendantLines.length) defendantLines.forEach((l) => captionLeft.push({ text: l }));
else captionLeft.push({ text: " " });
captionLeft.push({ text: "" });
captionLeft.push({ text: "Defendant(s).", indent: labelIndent });
const captionStartY = y;
let ly = y;
captionLeft.forEach((line) => { pdf.text(line, leftX, ly); ly += LINE_H; });
captionLeft.forEach((row) => {
pdf.text(row.text, leftX + (row.indent || 0), ly);
ly += LINE_H;
});
setFont(pdf, { bold: true });
pdf.text(`CASE NO.: ${input.caseNumber || ""}`, rightX, y);
@@ -225,9 +232,8 @@ export async function downloadPleadingPdf(input: PleadingInput, filename: string
pdf.setLineWidth(0.6);
// Vertical divider between caption columns
pdf.line(MARGIN + leftColW + 12, captionStartY - LINE_H + 4, MARGIN + leftColW + 12, captionEndY);
// Bottom border under the left caption cell
pdf.line(leftX, captionEndY + 2, MARGIN + leftColW + 12, captionEndY + 2);
void rightColW;
// Bottom border spanning BOTH caption cells (full content width)
pdf.line(leftX, captionEndY + 2, captionRightEdge, captionEndY + 2);
y = captionEndY + LINE_H;
@@ -238,10 +244,21 @@ export async function downloadPleadingPdf(input: PleadingInput, filename: string
y += LINE_H * 1.5;
}
const blocks = input.bodyHtml && input.bodyHtml.trim()
setFont(pdf, {}); // reset to non-bold for body
const blocks: Block[] = input.bodyHtml && input.bodyHtml.trim()
? htmlToBlocks(input.bodyHtml)
: (input.body || "").split("\n").map<Block>((line) => ({ kind: "para", align: "left", tokens: [{ text: line, style: {} }] }));
if (blocks.length === 0 && input.bodyHtml) {
// Fallback: strip HTML tags and render as plain text paragraphs
const plain = input.bodyHtml
.replace(/<\/?(p|div|h[1-6]|li|br)[^>]*>/gi, "\n")
.replace(/<[^>]+>/g, "");
plain.split(/\n+/).filter((l) => l.trim()).forEach((line) => {
blocks.push({ kind: "para", align: "left", tokens: [{ text: line, style: {} }] });
});
}
for (const block of blocks) {
if (block.kind === "spacer" || block.kind === "rule") { y += LINE_H; continue; }
@@ -270,6 +287,7 @@ export async function downloadPleadingPdf(input: PleadingInput, filename: string
if (lines.length === 1 && lines[0].length === 0) { y += LINE_H; continue; }
lines.forEach((line, i) => {
ensureSpace(LINE_H);
setFont(pdf, {}); // reset per-line so prior block styling never bleeds in
drawLine(pdf, line, x, y, block.align, maxW, i === lines.length - 1);
y += LINE_H;
});
+5 -5
View File
@@ -304,15 +304,15 @@ function PleadingNewPage() {
<div>{headerLine2}</div>
</div>
<div className="mt-6 flex">
<div className="flex-1 pr-4 border-r border-b border-black font-bold">
<div className="mt-6 border-b border-black flex">
<div className="flex-1 pr-4 border-r border-black font-bold pb-2">
<div className="whitespace-pre-line min-h-[1.5em]">{plaintiffs || " "}</div>
<div className="mt-3">Plaintiff(s),</div>
<div className="mt-3 pl-8">Plaintiff(s),</div>
<div className="mt-3">v.</div>
<div className="mt-3 whitespace-pre-line min-h-[1.5em]">{defendants || " "}</div>
<div className="mt-3">Defendant(s).</div>
<div className="mt-3 pl-8">Defendant(s).</div>
</div>
<div className="w-[40%] pl-4">
<div className="w-[40%] pl-4 pb-2">
<div className="font-bold">CASE NO.: {caseNumber}</div>
</div>
</div>