Anonymous
Как отличить текстовое поле от текстового поля при извлечении полей формы PDF и предотвратить возврат к текстовому полю
Сообщение
Anonymous » 19 дек 2025, 10:50
Я работаю над серверной частью Node.js, TypeScript и Express, которая:
Извлекает поля формы из PDF-файла
Преобразует их в наложенные объекты
Использует эти наложения для программного заполнения PDF-файлов
Проблема
Мои извлеченные поля PDF содержат поля символов (несколько небольших полей для ввода фиксированной длины), но они всегда возвращаются как «type»: «textfield».
Я хочу, чтобы поля символов обрабатывались как «type»: «textbox», сохраняя при этом обычный текстовый ввод как текстовое поле.
В настоящее время любой неизвестный тип возвращается к текстовому полю.
Текущий тип Определения
Код: Выделить всё
export type OverlayType = 'textfield' | 'checkbox' | 'textarea' | 'radio';
const ALLOWED_TYPES = ["textfield", "textarea", "checkbox", "radio"] as const;
const isAllowedType = (s: string): s is OverlayType =>
(ALLOWED_TYPES as readonly string[]).includes(s);
const coerceType = (t: any): OverlayType => {
const s = typeof t === "string" ? t.toLowerCase().trim() : "";
return isAllowedType(s) ? (s as OverlayType) : "textfield";
};
В этом коде это должно быть решено
Код: Выделить всё
public static async downloadPdfFromData(req: Request, res: Response) {
try {
const { pdfId,name,formData, formId } = req.body;
console.log('PDF ID:', pdfId, 'Name:', name);
if (!pdfId || !name) {
return res.status(400).json({
message: 'PDF ID and name are required.',
});
}
//Get the overlays directly from the forms collection
// Create Basic Auth header value
const authHeader = 'Basic ' + Buffer.from(`${RESTHEART_CREDENTIALS_NAME}:${RESTHEART_CREDENTIALS_PASS}`).toString('base64');
const organizationName = req.headers['x-organization-name'];
const formColl = `${organizationName}_forms`
console.log(`${API_NOSQL_URL}/${formColl}/${formId}?keys={"components":1,"_id":0}`);
const projectResponse = await axios.get(`${API_NOSQL_URL}/${formColl}/${formId}?keys={"components":1,"_id":0}`, {
headers: { 'Authorization': authHeader }
});
// Fetch the PDF from S3
const bucketName = S3_BUCKET_NAME_PDF_FORMS;
const key = `pdf-forms/${pdfId}/${name}`;
const s3Response = await s3Client.send(new GetObjectCommand({ Bucket: bucketName, Key: key }));
const s3Stream = s3Response.Body;
// const overlays: Overlay[]= JSON.parse(projectResponse.data.components) as Overlay[];
// data.components is already an array, not a JSON string
if (!Array.isArray(projectResponse.data.components)) {
throw new Error("Did not return components array");
}
const overlays: Overlay[] = projectResponse.data.components
.filter((c: any) => c && c.overlay && c.key && c.type)
.map((c: any) => ({
type: coerceType(c.type),
key: c.key,
label: c.label ?? "",
overlay: {
width: Number(c.overlay.width),
height: Number(c.overlay.height),
top: Number(c.overlay.top),
left: Number(c.overlay.left),
page: Number(c.overlay.page),
},
input: Boolean(c.input),
tableView: Boolean(c.tableView),
...(c.value !== undefined ? { value: String(c.value) } : {}),
}));
console.log('Overlays:', overlays);
//console.log('formdata:', formData);
//Get the overlays and form data from request body
let configPdfSize = {
method: 'get',
url: POSTGREST + '/pdf_forms?id=eq.'+ pdfId,
headers: {
'Content-Type': 'application/json'
}
};
const applicationDetailsPdfSize = await axios.request(configPdfSize);
if (!applicationDetailsPdfSize.data || applicationDetailsPdfSize.data.length === 0) {
return res.status(404).json({ message: 'PDF Page Size not found in database.' });
}
const pdfPageSize = applicationDetailsPdfSize.data[0].pdf_page_sizes;
if (!pdfPageSize) {
return res.status(500).json({ message: 'pdfPageSize not found in DB record.' });
}
const pdfPageSizeObj: Record = JSON.parse(pdfPageSize);
// console.log('pdfPageSizeObj',pdfPageSizeObj);
const filledPdfBuffer = await fillPdfAuto(
s3Response.Body,
overlays,
typeof formData === "string" ? JSON.parse(formData) : formData,
pdfPageSizeObj,
{
fontSize: 10,
minFontSize: 6,
textOverflow: "shrink",
padding: 2,
// For flattened PDFs (if you know html page px sizes from pdf2htmlex), pass them here:
// pagePixelSize: { 1:{w:1089.36,h:1410}, 2:{w:1089.36,h:1410}, 3:{w:1089.36,h:1410} },
pxPerPt: 96/72, // safe default for image PDFs
tinyBoxCenter: true // helps tiny boxes
}
);
//console.log('Filled PDF Buffer Length:', filledPdfBuffer.length);
//fs.writeFileSync('FilledForm-Flat.pdf', filledPdfBuffer);
// 3️⃣ Send as download
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${pdfId}-final.pdf"`);
res.send(filledPdfBuffer);
//console.log(JSON.stringify(components, null, 2));
//return res.status(200).json(components);
} catch (error: any) {
console.error('Error extracting text from PDF:', error.message);
return res.status(500).json({
message: 'An error occurred while extracting text from PDF.',
error: error.message,
});
}
}
Код сопоставления наложения
Код: Выделить всё
const overlays: Overlay[] = projectResponse.data.components
.filter((c: any) => c && c.overlay && c.key && c.type)
.map((c: any) => ({
type: coerceType(c.type),
key: c.key,
label: c.label ?? "",
overlay: {
width: Number(c.overlay.width),
height: Number(c.overlay.height),
top: Number(c.overlay.top),
left: Number(c.overlay.left),
page: Number(c.overlay.page),
},
input: Boolean(c.input),
tableView: Boolean(c.tableView),
}));
Пример вывода (текущий)
Код: Выделить всё
{
type: 'textfield',
key: 'textboxes4',
label: 'Character Boxes',
overlay: { width: 361.393, height: 27.6432, top: 1154, left: 594, page: 1 },
{
type: 'textfield',
key: 'textboxes3-ext',
label: 'Text Field',
overlay: { width: 253.698, height: 21.8229, top: 1010, left: 282, page: 1 },
input: true,
tableView: true
}
Ожидаемый результат
Код: Выделить всё
{
type: 'characterbox', (this is what i needed)
key: 'textboxes4',
label: 'Character Boxes',
overlay: { width: 361.393, height: 27.6432, top: 1154, left: 594, page: 1 },
{
type: 'textfield',
key: 'textboxes3-ext',
label: 'Text Field',
overlay: { width: 253.698, height: 21.8229, top: 1010, left: 282, page: 1 },
input: true,
tableView: true
}
Что мне нужно
Рекомендации по поддержке пользовательского типа () наряду с существующими типами
Как надежно обнаружить блоки символов:
по метке (, "box")
или по размерам наложения (широкий + короткий по высоте)
[*]Как избежать молчаливого приведения всего к текстовому полю
Подробнее здесь:
https://stackoverflow.com/questions/798 ... s-and-prev
1766130637
Anonymous
Я работаю над серверной частью Node.js, TypeScript и Express, которая: [list] [*]Извлекает поля формы из PDF-файла [*]Преобразует их в наложенные объекты [*]Использует эти наложения для программного заполнения PDF-файлов [/list] [b]Проблема[/b] Мои извлеченные поля PDF содержат поля символов (несколько небольших полей для ввода фиксированной длины), но они всегда возвращаются как «type»: «textfield». Я хочу, чтобы поля символов обрабатывались как «type»: «textbox», сохраняя при этом обычный текстовый ввод как текстовое поле. В настоящее время любой неизвестный тип возвращается к текстовому полю. [b]Текущий тип Определения[/b] [code]export type OverlayType = 'textfield' | 'checkbox' | 'textarea' | 'radio'; const ALLOWED_TYPES = ["textfield", "textarea", "checkbox", "radio"] as const; const isAllowedType = (s: string): s is OverlayType => (ALLOWED_TYPES as readonly string[]).includes(s); const coerceType = (t: any): OverlayType => { const s = typeof t === "string" ? t.toLowerCase().trim() : ""; return isAllowedType(s) ? (s as OverlayType) : "textfield"; }; [/code] В этом коде это должно быть решено [code] public static async downloadPdfFromData(req: Request, res: Response) { try { const { pdfId,name,formData, formId } = req.body; console.log('PDF ID:', pdfId, 'Name:', name); if (!pdfId || !name) { return res.status(400).json({ message: 'PDF ID and name are required.', }); } //Get the overlays directly from the forms collection // Create Basic Auth header value const authHeader = 'Basic ' + Buffer.from(`${RESTHEART_CREDENTIALS_NAME}:${RESTHEART_CREDENTIALS_PASS}`).toString('base64'); const organizationName = req.headers['x-organization-name']; const formColl = `${organizationName}_forms` console.log(`${API_NOSQL_URL}/${formColl}/${formId}?keys={"components":1,"_id":0}`); const projectResponse = await axios.get(`${API_NOSQL_URL}/${formColl}/${formId}?keys={"components":1,"_id":0}`, { headers: { 'Authorization': authHeader } }); // Fetch the PDF from S3 const bucketName = S3_BUCKET_NAME_PDF_FORMS; const key = `pdf-forms/${pdfId}/${name}`; const s3Response = await s3Client.send(new GetObjectCommand({ Bucket: bucketName, Key: key })); const s3Stream = s3Response.Body; // const overlays: Overlay[]= JSON.parse(projectResponse.data.components) as Overlay[]; // data.components is already an array, not a JSON string if (!Array.isArray(projectResponse.data.components)) { throw new Error("Did not return components array"); } const overlays: Overlay[] = projectResponse.data.components .filter((c: any) => c && c.overlay && c.key && c.type) .map((c: any) => ({ type: coerceType(c.type), key: c.key, label: c.label ?? "", overlay: { width: Number(c.overlay.width), height: Number(c.overlay.height), top: Number(c.overlay.top), left: Number(c.overlay.left), page: Number(c.overlay.page), }, input: Boolean(c.input), tableView: Boolean(c.tableView), ...(c.value !== undefined ? { value: String(c.value) } : {}), })); console.log('Overlays:', overlays); //console.log('formdata:', formData); //Get the overlays and form data from request body let configPdfSize = { method: 'get', url: POSTGREST + '/pdf_forms?id=eq.'+ pdfId, headers: { 'Content-Type': 'application/json' } }; const applicationDetailsPdfSize = await axios.request(configPdfSize); if (!applicationDetailsPdfSize.data || applicationDetailsPdfSize.data.length === 0) { return res.status(404).json({ message: 'PDF Page Size not found in database.' }); } const pdfPageSize = applicationDetailsPdfSize.data[0].pdf_page_sizes; if (!pdfPageSize) { return res.status(500).json({ message: 'pdfPageSize not found in DB record.' }); } const pdfPageSizeObj: Record = JSON.parse(pdfPageSize); // console.log('pdfPageSizeObj',pdfPageSizeObj); const filledPdfBuffer = await fillPdfAuto( s3Response.Body, overlays, typeof formData === "string" ? JSON.parse(formData) : formData, pdfPageSizeObj, { fontSize: 10, minFontSize: 6, textOverflow: "shrink", padding: 2, // For flattened PDFs (if you know html page px sizes from pdf2htmlex), pass them here: // pagePixelSize: { 1:{w:1089.36,h:1410}, 2:{w:1089.36,h:1410}, 3:{w:1089.36,h:1410} }, pxPerPt: 96/72, // safe default for image PDFs tinyBoxCenter: true // helps tiny boxes } ); //console.log('Filled PDF Buffer Length:', filledPdfBuffer.length); //fs.writeFileSync('FilledForm-Flat.pdf', filledPdfBuffer); // 3️⃣ Send as download res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', `attachment; filename="${pdfId}-final.pdf"`); res.send(filledPdfBuffer); //console.log(JSON.stringify(components, null, 2)); //return res.status(200).json(components); } catch (error: any) { console.error('Error extracting text from PDF:', error.message); return res.status(500).json({ message: 'An error occurred while extracting text from PDF.', error: error.message, }); } } [/code] [b]Код сопоставления наложения[/b] [code]const overlays: Overlay[] = projectResponse.data.components .filter((c: any) => c && c.overlay && c.key && c.type) .map((c: any) => ({ type: coerceType(c.type), key: c.key, label: c.label ?? "", overlay: { width: Number(c.overlay.width), height: Number(c.overlay.height), top: Number(c.overlay.top), left: Number(c.overlay.left), page: Number(c.overlay.page), }, input: Boolean(c.input), tableView: Boolean(c.tableView), })); [/code] [b]Пример вывода (текущий)[/b] [code]{ type: 'textfield', key: 'textboxes4', label: 'Character Boxes', overlay: { width: 361.393, height: 27.6432, top: 1154, left: 594, page: 1 }, { type: 'textfield', key: 'textboxes3-ext', label: 'Text Field', overlay: { width: 253.698, height: 21.8229, top: 1010, left: 282, page: 1 }, input: true, tableView: true } [/code] [b]Ожидаемый результат[/b] [code]{ type: 'characterbox', (this is what i needed) key: 'textboxes4', label: 'Character Boxes', overlay: { width: 361.393, height: 27.6432, top: 1154, left: 594, page: 1 }, { type: 'textfield', key: 'textboxes3-ext', label: 'Text Field', overlay: { width: 253.698, height: 21.8229, top: 1010, left: 282, page: 1 }, input: true, tableView: true } [/code] [b]Что мне нужно[/b] [list] [*]Рекомендации по поддержке пользовательского типа ([code]textbox[/code]) наряду с существующими типами [*]Как надежно обнаружить блоки символов: по метке ([code]"character"[/code], "box") [*]или по размерам наложения (широкий + короткий по высоте) [/list] [*]Как избежать молчаливого приведения всего к текстовому полю Подробнее здесь: [url]https://stackoverflow.com/questions/79850917/how-to-distinguish-textbox-vs-textfield-when-extracting-pdf-form-fields-and-prev[/url]