chore: some cleanup

This commit is contained in:
Catalin Pit
2025-11-04 11:16:53 +02:00
parent d25565b7d0
commit 13d9ca7a0e

View File

@ -25,7 +25,7 @@ type TextPosition = {
}; };
type CharIndexMapping = { type CharIndexMapping = {
textPosIndex: number; textPositionIndex: number;
}; };
type PlaceholderInfo = { type PlaceholderInfo = {
@ -121,28 +121,26 @@ const parseFieldMeta = (
rawFieldMeta is an object with string keys and string values. rawFieldMeta is an object with string keys and string values.
It contains string values because the PDF parser returns the values as strings. It contains string values because the PDF parser returns the values as strings.
E.g. { required: 'true', fontSize: '12', maxValue: '100', minValue: '0', characterLimit: '100' } E.g. { 'required': 'true', 'fontSize': '12', 'maxValue': '100', 'minValue': '0', 'characterLimit': '100' }
*/ */
const rawFieldMetaEntries = Object.entries(rawFieldMeta); const rawFieldMetaEntries = Object.entries(rawFieldMeta);
for (const entry of rawFieldMetaEntries) { for (const [property, value] of rawFieldMetaEntries) {
const [key, value] = entry; if (property === 'readOnly' || property === 'required') {
parsedFieldMeta[property] = value === 'true';
if (key === 'readOnly' || key === 'required') {
parsedFieldMeta[key] = value === 'true';
} else if ( } else if (
key === 'fontSize' || property === 'fontSize' ||
key === 'maxValue' || property === 'maxValue' ||
key === 'minValue' || property === 'minValue' ||
key === 'characterLimit' property === 'characterLimit'
) { ) {
const numValue = Number(value); const numValue = Number(value);
if (!Number.isNaN(numValue)) { if (!Number.isNaN(numValue)) {
parsedFieldMeta[key] = numValue; parsedFieldMeta[property] = numValue;
} }
} else { } else {
parsedFieldMeta[key] = value; parsedFieldMeta[property] = value;
} }
} }
@ -177,16 +175,20 @@ export const extractPlaceholdersFromPDF = async (pdf: Buffer): Promise<Placehold
page.Texts.forEach((text) => { page.Texts.forEach((text) => {
/* /*
R is an array that contains objects with each character. R is an array of objects containing each character, its position and styling information.
The decodedText contains only the character, without any other information. The decodedText stores the characters, without any other information.
textPositions stores each character and its position on the page. textPositions stores each character and its position on the page.
*/ */
const decodedText = text.R.map((run) => decodeURIComponent(run.T)).join(''); const decodedText = text.R.map((run) => decodeURIComponent(run.T)).join('');
/*
For each character in the decodedText, we store its position in the textPositions array.
This allows us to quickly find the position of a character in the textPositions array by its index.
*/
for (let i = 0; i < decodedText.length; i++) { for (let i = 0; i < decodedText.length; i++) {
charIndexToTextPos.push({ charIndexToTextPos.push({
textPosIndex: textPositions.length, textPositionIndex: textPositions.length,
}); });
} }
@ -202,6 +204,16 @@ export const extractPlaceholdersFromPDF = async (pdf: Buffer): Promise<Placehold
const placeholderMatches = pageText.matchAll(/{{([^}]+)}}/g); const placeholderMatches = pageText.matchAll(/{{([^}]+)}}/g);
/*
A placeholder match has the following format:
[
'{{fieldType,recipient,fieldMeta}}',
'fieldType,recipient,fieldMeta',
'index: <number>',
'input: <pdf-text>'
]
*/
for (const placeholderMatch of placeholderMatches) { for (const placeholderMatch of placeholderMatches) {
const placeholder = placeholderMatch[0]; const placeholder = placeholderMatch[0];
const placeholderData = placeholderMatch[1].split(',').map((part) => part.trim()); const placeholderData = placeholderMatch[1].split(',').map((part) => part.trim());
@ -221,28 +233,29 @@ export const extractPlaceholdersFromPDF = async (pdf: Buffer): Promise<Placehold
/* /*
Find the position of where the placeholder starts in the text Find the position of where the placeholder starts in the text
Then find the position of where the placeholder ends in the text by adding the length of the placeholder to the index of the placeholder. Then find the position of where the placeholder ends in the text
by adding the length of the placeholder to the index of the placeholder.
*/ */
const matchIndex = placeholderMatch.index; if (placeholderMatch.index === undefined) {
console.error('Placeholder match index is undefined for placeholder', placeholder);
if (matchIndex === undefined) {
continue; continue;
} }
const placeholderLength = placeholder.length; const placeholderLength = placeholder.length;
const placeholderEndIndex = matchIndex + placeholderLength; const placeholderEndIndex = placeholderMatch.index + placeholderLength;
const startCharInfo = charIndexToTextPos[matchIndex]; const startCharacterIndex = charIndexToTextPos[placeholderMatch.index];
const endCharInfo = charIndexToTextPos[placeholderEndIndex - 1]; const endCharacterIndex = charIndexToTextPos[placeholderEndIndex - 1];
if (!startCharInfo || !endCharInfo) { if (!startCharacterIndex || !endCharacterIndex) {
console.error('Could not find text position for placeholder', placeholder); console.error('Could not find text position for placeholder', placeholder);
return; return;
} }
const startTextPos = textPositions[startCharInfo.textPosIndex]; const startTextPos = textPositions[startCharacterIndex.textPositionIndex];
const endTextPos = textPositions[endCharInfo.textPosIndex]; const endTextPos = textPositions[endCharacterIndex.textPositionIndex];
/* /*
PDF2JSON coordinates - these are in "page units" (relative coordinates) PDF2JSON coordinates - these are in "page units" (relative coordinates)
@ -419,9 +432,9 @@ export const insertFieldsFromPlaceholdersInPDF = async (
}, },
}); });
const existingEmails = new Set(existingRecipients.map((r) => r.email.toLowerCase())); const existingEmails = existingRecipients.map((r) => r.email);
const recipientsToCreateFiltered = recipientsToCreate.filter( const recipientsToCreateFiltered = recipientsToCreate.filter(
(r) => !existingEmails.has(r.email.toLowerCase()), (recipient) => !existingEmails.includes(recipient.email),
); );
let createdRecipients: Pick<Recipient, 'id' | 'email'>[] = existingRecipients; let createdRecipients: Pick<Recipient, 'id' | 'email'>[] = existingRecipients;
@ -473,8 +486,7 @@ export const insertFieldsFromPlaceholdersInPDF = async (
const heightPercent = (placeholder.height / placeholder.pageHeight) * 100; const heightPercent = (placeholder.height / placeholder.pageHeight) * 100;
const { email } = extractRecipientPlaceholder(placeholder.recipient); const { email } = extractRecipientPlaceholder(placeholder.recipient);
const normalizedEmail = email.toLowerCase(); const recipient = createdRecipients.find((r) => r.email === email);
const recipient = createdRecipients.find((r) => r.email.toLowerCase() === normalizedEmail);
if (!recipient) { if (!recipient) {
throw new AppError(AppErrorCode.INVALID_BODY, { throw new AppError(AppErrorCode.INVALID_BODY, {