diff --git a/src/lib/docx-pleading.ts b/src/lib/docx-pleading.ts index 53f8459..a2fd1e9 100644 --- a/src/lib/docx-pleading.ts +++ b/src/lib/docx-pleading.ts @@ -85,7 +85,7 @@ const captionLeftBorders = { const captionRightBorders = { top: { style: BorderStyle.NONE, size: 0, color: "FFFFFF" }, - bottom: { style: BorderStyle.SINGLE, size: 8, color: "000000" }, + bottom: { style: BorderStyle.NONE, size: 0, color: "FFFFFF" }, left: { style: BorderStyle.NONE, size: 0, color: "FFFFFF" }, right: { style: BorderStyle.NONE, size: 0, color: "FFFFFF" }, }; diff --git a/src/lib/pdf-pleading.ts b/src/lib/pdf-pleading.ts index d6a55cc..f368e2f 100644 --- a/src/lib/pdf-pleading.ts +++ b/src/lib/pdf-pleading.ts @@ -62,38 +62,53 @@ function htmlToBlocks(html: string): Block[] { const walkList = (listEl: Element, ordered: boolean, level: number) => { let idx = 1; - listEl.querySelectorAll(":scope > li").forEach((li) => { - const tokens: Token[] = []; - li.childNodes.forEach((child) => { - if (child.nodeType === Node.ELEMENT_NODE && /^(ul|ol)$/i.test((child as Element).tagName)) return; - tokenize(child, {}, tokens); + Array.from(listEl.children) + .filter((child): child is Element => child.tagName.toLowerCase() === "li") + .forEach((li) => { + const tokens: Token[] = []; + li.childNodes.forEach((child) => { + if (child.nodeType === Node.ELEMENT_NODE && /^(ul|ol)$/i.test((child as Element).tagName)) return; + tokenize(child, {}, tokens); + }); + out.push({ kind: "list-item", ordered, level, index: idx++, tokens }); + Array.from(li.children) + .filter((child): child is Element => /^(ul|ol)$/i.test(child.tagName)) + .forEach((nested) => walkList(nested, nested.tagName.toLowerCase() === "ol", level + 1)); }); - out.push({ kind: "list-item", ordered, level, index: idx++, tokens }); - li.querySelectorAll(":scope > ul").forEach((n) => walkList(n as Element, false, level + 1)); - li.querySelectorAll(":scope > ol").forEach((n) => walkList(n as Element, true, level + 1)); - }); }; - Array.from(root.children).forEach((el) => { - const tag = el.tagName.toLowerCase(); - if (tag === "ul") return walkList(el, false, 0); - if (tag === "ol") return walkList(el, true, 0); + const walk = (node: Element) => { + const tag = node.tagName.toLowerCase(); + if (tag === "ul") return walkList(node, false, 0); + if (tag === "ol") return walkList(node, true, 0); if (tag === "blockquote") { const tokens: Token[] = []; - el.childNodes.forEach((c) => tokenize(c, {}, tokens)); - out.push({ kind: "para", align: alignOf(el), tokens, indent: 36 }); + node.childNodes.forEach((c) => tokenize(c, {}, tokens)); + out.push({ kind: "para", align: alignOf(node), tokens, indent: 36 }); return; } if (tag === "h1" || tag === "h2" || tag === "h3") { const tokens: Token[] = []; - el.childNodes.forEach((c) => tokenize(c, { bold: true }, tokens)); - out.push({ kind: "para", align: alignOf(el), tokens }); + node.childNodes.forEach((c) => tokenize(c, { bold: true }, tokens)); + out.push({ kind: "para", align: alignOf(node), tokens }); + return; + } + if (tag === "p") { + const tokens: Token[] = []; + node.childNodes.forEach((c) => tokenize(c, {}, tokens)); + out.push({ kind: "para", align: alignOf(node), tokens }); + return; + } + if (tag === "div" || tag === "section" || tag === "article") { + Array.from(node.children).forEach((child) => walk(child)); return; } const tokens: Token[] = []; - el.childNodes.forEach((c) => tokenize(c, {}, tokens)); - out.push({ kind: "para", align: alignOf(el), tokens }); - }); + node.childNodes.forEach((c) => tokenize(c, {}, tokens)); + if (tokens.length > 0) out.push({ kind: "para", align: alignOf(node), tokens }); + }; + + Array.from(root.children).forEach((el) => walk(el)); return out; } @@ -106,16 +121,24 @@ function layoutTokens(pdf: jsPDF, tokens: Token[], maxWidth: number): Piece[][] }; const lineWidth = (line: Piece[]) => line.reduce((a, p) => a + p.w, 0); - const pushWord = (word: string, style: Style) => { - if (!word) return; - const w = measure(word, style); + const pushPiece = (text: string, style: Style) => { + if (!text) return; const cur = lines[lines.length - 1]; - if (lineWidth(cur) + w > maxWidth && cur.length > 0) { - while (cur.length && /\s+$/.test(cur[cur.length - 1].text)) cur.pop(); - lines.push([{ text: word, style, w }]); - } else { - cur.push({ text: word, style, w }); + const isWhitespace = /^\s+$/.test(text); + if (isWhitespace) { + if (cur.length === 0) return; + cur.push({ text, style, w: measure(text, style) }); + return; } + + const w = measure(text, style); + if (lineWidth(cur) + w > maxWidth && cur.length > 0) { + while (cur.length && /^\s+$/.test(cur[cur.length - 1].text)) cur.pop(); + lines.push([{ text, style, w }]); + return; + } + + cur.push({ text, style, w }); }; for (const tok of tokens) { @@ -123,8 +146,8 @@ function layoutTokens(pdf: jsPDF, tokens: Token[], maxWidth: number): Piece[][] for (const part of parts) { if (part === "\n") { lines.push([]); continue; } if (!part) continue; - const words = part.match(/\S+\s*/g) || [part]; - for (const w of words) pushWord(w, tok.style); + const chunks = part.match(/\s+|[^\s]+/g) || [part]; + for (const chunk of chunks) pushPiece(chunk, tok.style); } } return lines; @@ -140,7 +163,7 @@ function drawLine(pdf: jsPDF, line: Piece[], x: number, y: number, align: "left" if (align === "center") startX = x + (maxWidth - totalW) / 2; else if (align === "right") startX = x + (maxWidth - totalW); else if (align === "justify" && !isLast) { - const spaces = trimmed.filter((p) => /\s$/.test(p.text)).length; + const spaces = trimmed.filter((p) => /^\s+$/.test(p.text)).length; if (spaces > 0) extraSpace = (maxWidth - totalW) / spaces; } @@ -149,13 +172,13 @@ function drawLine(pdf: jsPDF, line: Piece[], x: number, y: number, align: "left" const sz = p.style.superscript ? FONT_SIZE * 0.75 : FONT_SIZE; setFont(pdf, p.style, sz); const drawY = p.style.superscript ? y - 4 : y; - pdf.text(p.text, cx, drawY); + if (!/^\s+$/.test(p.text)) pdf.text(p.text, cx, drawY); if (p.style.underline) { const tw = p.w; pdf.setLineWidth(0.5); pdf.line(cx, y + 1.5, cx + tw, y + 1.5); } - cx += p.w + (/\s$/.test(p.text) ? extraSpace : 0); + cx += p.w + (/^\s+$/.test(p.text) ? extraSpace : 0); } } @@ -197,7 +220,6 @@ export async function downloadPleadingPdf(input: PleadingInput, filename: string const leftColW = CONTENT_W * 0.58; 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()); @@ -230,10 +252,8 @@ export async function downloadPleadingPdf(input: PleadingInput, filename: string const captionEndY = ly; pdf.setLineWidth(0.6); - // Vertical divider between caption columns - pdf.line(MARGIN + leftColW + 12, captionStartY - LINE_H + 4, MARGIN + leftColW + 12, captionEndY); - // Bottom border spanning BOTH caption cells (full content width) - pdf.line(leftX, captionEndY + 2, captionRightEdge, captionEndY + 2); + // Bottom border only under the left caption cell + pdf.line(leftX, captionEndY + 2, MARGIN + leftColW + 12, captionEndY + 2); y = captionEndY + LINE_H; diff --git a/src/routes/documents.pleading.new.tsx b/src/routes/documents.pleading.new.tsx index 2749521..3bd20e5 100644 --- a/src/routes/documents.pleading.new.tsx +++ b/src/routes/documents.pleading.new.tsx @@ -304,8 +304,8 @@ function PleadingNewPage() {