fix(12-02): draw text fill values at placed text field box coordinates
- Bug 3: text type fields previously drew nothing at field coordinates; textFillData values were only stamped at top of page 1 via Strategy B, making them invisible to the agent inspecting the placed boxes - Sort placed text fields by page asc / y desc (reading order) and assign textFillData entries sequentially to each field box position - Text drawn at field.x+4, field.y+4 with font size capped 6–11pt to fit within the field height; dark near-black color for legibility - fieldConsumedKeys set tracks which entries were rendered at field coords; Strategy B only stamps remaining entries not consumed by a field box - TypeScript compiles clean; zero errors
This commit is contained in:
@@ -68,11 +68,55 @@ export async function preparePdf(
|
||||
hasAcroForm = false;
|
||||
}
|
||||
|
||||
// Bug 3 fix: assign textFillData values to placed `text` type field boxes in reading
|
||||
// order (page asc, y desc within page = top-to-bottom in PDF user space). Each text
|
||||
// field box consumes one textFillData entry so the value is rendered AT the correct
|
||||
// position on the document rather than only via Strategy B at the top of page 1.
|
||||
//
|
||||
// Build an ordered list of textFillData entries not already handled by AcroForm (Strategy A).
|
||||
const remainingEntries: Array<[string, string]> = Object.entries(textFields).filter(
|
||||
([key]) => !acroFilledKeys.has(key),
|
||||
);
|
||||
|
||||
// Sort placed `text` fields by page asc, then y desc (topmost field first in reading order).
|
||||
const textFields_sorted = sigFields
|
||||
.filter((f) => getFieldType(f) === 'text')
|
||||
.sort((a, b) => a.page !== b.page ? a.page - b.page : b.y - a.y);
|
||||
|
||||
// Track which textFillData keys are consumed by field-box rendering so Strategy B
|
||||
// only stamps any leftover entries (keys that had no corresponding placed text field).
|
||||
const fieldConsumedKeys = new Set<string>();
|
||||
|
||||
textFields_sorted.forEach((field, idx) => {
|
||||
const entry = remainingEntries[idx];
|
||||
if (!entry) return; // More text fields than textFillData entries — nothing to draw
|
||||
|
||||
const [key, value] = entry;
|
||||
if (!value) return; // Skip blank values — leave the box visually empty
|
||||
|
||||
const page = pages[field.page - 1];
|
||||
if (!page) return;
|
||||
|
||||
// Draw value text at the field's bottom-left corner with 4pt padding.
|
||||
// Font size is capped to fit within the field height (min 6pt, max 11pt).
|
||||
const fontSize = Math.max(6, Math.min(11, field.height - 4));
|
||||
page.drawText(value, {
|
||||
x: field.x + 4,
|
||||
y: field.y + 4,
|
||||
size: fontSize,
|
||||
font: helvetica,
|
||||
color: rgb(0.05, 0.05, 0.05),
|
||||
});
|
||||
|
||||
fieldConsumedKeys.add(key);
|
||||
});
|
||||
|
||||
// Strategy B: stamp any un-filled text field entries directly onto page 1.
|
||||
// This guarantees that text fill data is always reflected in the output PDF
|
||||
// even when the source PDF has no AcroForm fields (e.g. scanned/flat PDFs).
|
||||
const unstampedEntries = Object.entries(textFields).filter(
|
||||
([key]) => !acroFilledKeys.has(key),
|
||||
// Only entries NOT consumed by field-box rendering are stamped here.
|
||||
const unstampedEntries = remainingEntries.filter(
|
||||
([key]) => !fieldConsumedKeys.has(key),
|
||||
);
|
||||
|
||||
if (unstampedEntries.length > 0) {
|
||||
@@ -147,7 +191,8 @@ export async function preparePdf(
|
||||
// No placeholder drawn — actual signing date stamped at POST time in route.ts
|
||||
|
||||
} else if (fieldType === 'text') {
|
||||
// No marker drawn — text content is provided via textFillData (separate pipeline)
|
||||
// Text value drawn at field coordinates above (Bug 3 fix — textFields_sorted loop).
|
||||
// No additional marker drawn here.
|
||||
|
||||
} else if (fieldType === 'agent-signature') {
|
||||
if (agentSigImage) {
|
||||
|
||||
Reference in New Issue
Block a user