mirror of
https://github.com/documenso/documenso.git
synced 2026-07-27 18:35:05 +10:00
refactor(pdf): simplify AcroForm import code
This commit is contained in:
@@ -98,9 +98,8 @@ export const EnvelopeEditorFieldsPage = () => {
|
|||||||
const currentEnvelopeItemRevision = currentEnvelopeItem
|
const currentEnvelopeItemRevision = currentEnvelopeItem
|
||||||
? `${currentEnvelopeItem.id}:${currentEnvelopeItem.documentDataId}`
|
? `${currentEnvelopeItem.id}:${currentEnvelopeItem.documentDataId}`
|
||||||
: null;
|
: null;
|
||||||
const currentItemHasAcroForm = currentEnvelopeItemRevision
|
const currentItemHasAcroForm =
|
||||||
? acroFormHasFieldsByItemRevision[currentEnvelopeItemRevision] === true
|
currentEnvelopeItemRevision !== null && acroFormHasFieldsByItemRevision[currentEnvelopeItemRevision] === true;
|
||||||
: false;
|
|
||||||
|
|
||||||
const onAcroFormDetected = useCallback(
|
const onAcroFormDetected = useCallback(
|
||||||
(hasFields: boolean) => {
|
(hasFields: boolean) => {
|
||||||
|
|||||||
@@ -16,12 +16,7 @@ export type EnvelopePdfViewerProps = {
|
|||||||
errorMessage: { title: MessageDescriptor; description: MessageDescriptor } | null;
|
errorMessage: { title: MessageDescriptor; description: MessageDescriptor } | null;
|
||||||
} & Omit<PDFViewerProps, 'data'>;
|
} & Omit<PDFViewerProps, 'data'>;
|
||||||
|
|
||||||
export const EnvelopePdfViewer = ({
|
export const EnvelopePdfViewer = ({ errorMessage, className, ...props }: EnvelopePdfViewerProps) => {
|
||||||
errorMessage,
|
|
||||||
className,
|
|
||||||
onAcroFormDetected,
|
|
||||||
...props
|
|
||||||
}: EnvelopePdfViewerProps) => {
|
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|
||||||
const $el = useRef<HTMLDivElement>(null);
|
const $el = useRef<HTMLDivElement>(null);
|
||||||
@@ -55,7 +50,6 @@ export const EnvelopePdfViewer = ({
|
|||||||
{...props}
|
{...props}
|
||||||
className={cn('h-full w-full max-w-[800px]', className)}
|
className={cn('h-full w-full max-w-[800px]', className)}
|
||||||
data={currentEnvelopeItem.data}
|
data={currentEnvelopeItem.data}
|
||||||
onAcroFormDetected={onAcroFormDetected}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
|||||||
import { UNSAFE_importAcroFormFieldsFromEnvelope } from '@documenso/lib/server-only/envelope-item/import-acroform-fields';
|
import { UNSAFE_importAcroFormFieldsFromEnvelope } from '@documenso/lib/server-only/envelope-item/import-acroform-fields';
|
||||||
import { UNSAFE_replaceEnvelopeItemPdf } from '@documenso/lib/server-only/envelope-item/replace-envelope-item-pdf';
|
import { UNSAFE_replaceEnvelopeItemPdf } from '@documenso/lib/server-only/envelope-item/replace-envelope-item-pdf';
|
||||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||||
|
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||||
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { EnvelopeType, RecipientRole } from '@documenso/prisma/client';
|
import { EnvelopeType, RecipientRole } from '@documenso/prisma/client';
|
||||||
@@ -13,29 +14,61 @@ import type {
|
|||||||
TCreateEnvelopeResponse,
|
TCreateEnvelopeResponse,
|
||||||
} from '@documenso/trpc/server/envelope-router/create-envelope.types';
|
} from '@documenso/trpc/server/envelope-router/create-envelope.types';
|
||||||
import { PDF } from '@libpdf/core';
|
import { PDF } from '@libpdf/core';
|
||||||
import { expect, test } from '@playwright/test';
|
import { type APIRequestContext, expect, test } from '@playwright/test';
|
||||||
|
|
||||||
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
|
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
|
||||||
const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`;
|
const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`;
|
||||||
|
|
||||||
const ACROFORM_FIXTURE = fs.readFileSync(path.join(__dirname, '../../../../assets/acroform-import-test.pdf'));
|
const ACROFORM_FIXTURE = fs.readFileSync(path.join(__dirname, '../../../../assets/acroform-import-test.pdf'));
|
||||||
|
|
||||||
|
const ACROFORM_DOCUMENT_PAYLOAD: TCreateEnvelopePayload = {
|
||||||
|
type: EnvelopeType.DOCUMENT,
|
||||||
|
title: 'AcroForm document',
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
email: 'signer@example.com',
|
||||||
|
name: 'Signer',
|
||||||
|
role: RecipientRole.SIGNER,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_REQUEST_METADATA: ApiRequestMetadata = {
|
||||||
|
requestMetadata: {},
|
||||||
|
source: 'apiV1',
|
||||||
|
auth: 'api',
|
||||||
|
};
|
||||||
|
|
||||||
|
type TestUser = Awaited<ReturnType<typeof seedUser>>['user'];
|
||||||
|
|
||||||
|
const seedUserWithApiToken = async (): Promise<{ token: string; user: TestUser }> => {
|
||||||
|
const { user, team } = await seedUser();
|
||||||
|
const { token } = await createApiToken({
|
||||||
|
userId: user.id,
|
||||||
|
teamId: team.id,
|
||||||
|
tokenName: 'test',
|
||||||
|
expiresIn: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { token, user };
|
||||||
|
};
|
||||||
|
|
||||||
const pdfHasFormFields = async (pdf: Uint8Array): Promise<boolean> => {
|
const pdfHasFormFields = async (pdf: Uint8Array): Promise<boolean> => {
|
||||||
const pdfDoc = await PDF.load(new Uint8Array(pdf));
|
const pdfDoc = await PDF.load(new Uint8Array(pdf));
|
||||||
const form = pdfDoc.getForm();
|
const form = pdfDoc.getForm();
|
||||||
|
|
||||||
return Boolean(form && form.fieldCount > 0);
|
return (form?.fieldCount ?? 0) > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadAcroFormEnvelope = async ({
|
const uploadAcroFormEnvelope = async ({
|
||||||
request,
|
request,
|
||||||
token,
|
token,
|
||||||
payload,
|
payload = ACROFORM_DOCUMENT_PAYLOAD,
|
||||||
}: {
|
}: {
|
||||||
request: import('@playwright/test').APIRequestContext;
|
request: APIRequestContext;
|
||||||
token: string;
|
token: string;
|
||||||
payload: TCreateEnvelopePayload;
|
payload?: TCreateEnvelopePayload;
|
||||||
}) => {
|
}): Promise<TCreateEnvelopeResponse> => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
formData.append('payload', JSON.stringify(payload));
|
formData.append('payload', JSON.stringify(payload));
|
||||||
@@ -66,29 +99,9 @@ test.describe.configure({
|
|||||||
|
|
||||||
test.describe('AcroForm Import', () => {
|
test.describe('AcroForm Import', () => {
|
||||||
test('upload does not create fields and preserves widgets in the stored PDF', async ({ request }) => {
|
test('upload does not create fields and preserves widgets in the stored PDF', async ({ request }) => {
|
||||||
const { user, team } = await seedUser();
|
const { token } = await seedUserWithApiToken();
|
||||||
const { token } = await createApiToken({
|
|
||||||
userId: user.id,
|
|
||||||
teamId: team.id,
|
|
||||||
tokenName: 'test',
|
|
||||||
expiresIn: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await uploadAcroFormEnvelope({
|
const response = await uploadAcroFormEnvelope({ request, token });
|
||||||
request,
|
|
||||||
token,
|
|
||||||
payload: {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'AcroForm document',
|
|
||||||
recipients: [
|
|
||||||
{
|
|
||||||
email: 'signer@example.com',
|
|
||||||
name: 'Signer',
|
|
||||||
role: RecipientRole.SIGNER,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||||
where: { id: response.id },
|
where: { id: response.id },
|
||||||
@@ -106,29 +119,9 @@ test.describe('AcroForm Import', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('replacement preserves widgets in the stored PDF for later import', async ({ request }) => {
|
test('replacement preserves widgets in the stored PDF for later import', async ({ request }) => {
|
||||||
const { user, team } = await seedUser();
|
const { token, user } = await seedUserWithApiToken();
|
||||||
const { token } = await createApiToken({
|
|
||||||
userId: user.id,
|
|
||||||
teamId: team.id,
|
|
||||||
tokenName: 'test',
|
|
||||||
expiresIn: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await uploadAcroFormEnvelope({
|
const response = await uploadAcroFormEnvelope({ request, token });
|
||||||
request,
|
|
||||||
token,
|
|
||||||
payload: {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'AcroForm document',
|
|
||||||
recipients: [
|
|
||||||
{
|
|
||||||
email: 'signer@example.com',
|
|
||||||
name: 'Signer',
|
|
||||||
role: RecipientRole.SIGNER,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const envelope = await loadEnvelopeForImport(response.id);
|
const envelope = await loadEnvelopeForImport(response.id);
|
||||||
const oldDocumentDataId = envelope.envelopeItems[0].documentDataId;
|
const oldDocumentDataId = envelope.envelopeItems[0].documentDataId;
|
||||||
@@ -143,11 +136,7 @@ test.describe('AcroForm Import', () => {
|
|||||||
file: new File([ACROFORM_FIXTURE], 'replacement-acroform.pdf', { type: 'application/pdf' }),
|
file: new File([ACROFORM_FIXTURE], 'replacement-acroform.pdf', { type: 'application/pdf' }),
|
||||||
},
|
},
|
||||||
user,
|
user,
|
||||||
apiRequestMetadata: {
|
apiRequestMetadata: API_REQUEST_METADATA,
|
||||||
requestMetadata: {},
|
|
||||||
source: 'apiV1',
|
|
||||||
auth: 'api',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const after = await prisma.envelope.findUniqueOrThrow({
|
const after = await prisma.envelope.findUniqueOrThrow({
|
||||||
@@ -167,40 +156,16 @@ test.describe('AcroForm Import', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('import creates fields assigned to the signer, flattens the PDF, and emits audit logs', async ({ request }) => {
|
test('import creates fields assigned to the signer, flattens the PDF, and emits audit logs', async ({ request }) => {
|
||||||
const { user, team } = await seedUser();
|
const { token } = await seedUserWithApiToken();
|
||||||
const { token } = await createApiToken({
|
|
||||||
userId: user.id,
|
|
||||||
teamId: team.id,
|
|
||||||
tokenName: 'test',
|
|
||||||
expiresIn: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await uploadAcroFormEnvelope({
|
const response = await uploadAcroFormEnvelope({ request, token });
|
||||||
request,
|
|
||||||
token,
|
|
||||||
payload: {
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
|
||||||
title: 'AcroForm document',
|
|
||||||
recipients: [
|
|
||||||
{
|
|
||||||
email: 'signer@example.com',
|
|
||||||
name: 'Signer',
|
|
||||||
role: RecipientRole.SIGNER,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const envelope = await loadEnvelopeForImport(response.id);
|
const envelope = await loadEnvelopeForImport(response.id);
|
||||||
const oldDocumentDataId = envelope.envelopeItems[0].documentDataId;
|
const oldDocumentDataId = envelope.envelopeItems[0].documentDataId;
|
||||||
|
|
||||||
const result = await UNSAFE_importAcroFormFieldsFromEnvelope({
|
const result = await UNSAFE_importAcroFormFieldsFromEnvelope({
|
||||||
envelope,
|
envelope,
|
||||||
apiRequestMetadata: {
|
apiRequestMetadata: API_REQUEST_METADATA,
|
||||||
requestMetadata: {},
|
|
||||||
source: 'apiV1',
|
|
||||||
auth: 'api',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.fieldsCreated).toBeGreaterThan(0);
|
expect(result.fieldsCreated).toBeGreaterThan(0);
|
||||||
@@ -241,13 +206,7 @@ test.describe('AcroForm Import', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('import creates a placeholder Recipient 1 SIGNER when no recipients exist', async ({ request }) => {
|
test('import creates a placeholder Recipient 1 SIGNER when no recipients exist', async ({ request }) => {
|
||||||
const { user, team } = await seedUser();
|
const { token } = await seedUserWithApiToken();
|
||||||
const { token } = await createApiToken({
|
|
||||||
userId: user.id,
|
|
||||||
teamId: team.id,
|
|
||||||
tokenName: 'test',
|
|
||||||
expiresIn: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await uploadAcroFormEnvelope({
|
const response = await uploadAcroFormEnvelope({
|
||||||
request,
|
request,
|
||||||
@@ -264,11 +223,7 @@ test.describe('AcroForm Import', () => {
|
|||||||
|
|
||||||
await UNSAFE_importAcroFormFieldsFromEnvelope({
|
await UNSAFE_importAcroFormFieldsFromEnvelope({
|
||||||
envelope,
|
envelope,
|
||||||
apiRequestMetadata: {
|
apiRequestMetadata: API_REQUEST_METADATA,
|
||||||
requestMetadata: {},
|
|
||||||
source: 'apiV1',
|
|
||||||
auth: 'api',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const after = await prisma.envelope.findUniqueOrThrow({
|
const after = await prisma.envelope.findUniqueOrThrow({
|
||||||
|
|||||||
@@ -110,11 +110,7 @@ export const UNSAFE_importAcroFormFieldsFromEnvelope = async ({
|
|||||||
extraction,
|
extraction,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (extraction.fields.length === 0) {
|
if (extraction.fields.length === 0 || extraction.hasSignedSignature) {
|
||||||
return base;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extraction.hasSignedSignature) {
|
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +170,7 @@ export const UNSAFE_importAcroFormFieldsFromEnvelope = async ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...signable].sort((a, b) => {
|
return signable.sort((a, b) => {
|
||||||
const aOrder = a.signingOrder ?? Number.MAX_SAFE_INTEGER;
|
const aOrder = a.signingOrder ?? Number.MAX_SAFE_INTEGER;
|
||||||
const bOrder = b.signingOrder ?? Number.MAX_SAFE_INTEGER;
|
const bOrder = b.signingOrder ?? Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ const NAME_NAME_PATTERN = /name/i;
|
|||||||
const INITIALS_NAME_PATTERN = /initial/i;
|
const INITIALS_NAME_PATTERN = /initial/i;
|
||||||
|
|
||||||
const ROW_TOLERANCE_PERCENT = 2;
|
const ROW_TOLERANCE_PERCENT = 2;
|
||||||
|
const ACROFORM_FIELD_SOURCE = 'acroform';
|
||||||
|
|
||||||
export type AcroFormUnsupportedReason =
|
export type AcroFormUnsupportedReason =
|
||||||
| 'unsupported-type'
|
| 'unsupported-type'
|
||||||
@@ -153,25 +154,15 @@ const hasXfa = (pdfDoc: PDF, resolver: RefResolver): boolean => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDateFieldByName = (name: string | null | undefined): boolean => {
|
const isDateFieldByName = (name: string | null | undefined): boolean => !!name && DATE_NAME_PATTERN.test(name);
|
||||||
return name ? DATE_NAME_PATTERN.test(name) : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isNumberFieldByName = (name: string | null | undefined): boolean => {
|
const isNumberFieldByName = (name: string | null | undefined): boolean => !!name && NUMBER_NAME_PATTERN.test(name);
|
||||||
return name ? NUMBER_NAME_PATTERN.test(name) : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isEmailFieldByName = (name: string | null | undefined): boolean => {
|
const isEmailFieldByName = (name: string | null | undefined): boolean => !!name && EMAIL_NAME_PATTERN.test(name);
|
||||||
return name ? EMAIL_NAME_PATTERN.test(name) : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isNameFieldByName = (name: string | null | undefined): boolean => {
|
const isNameFieldByName = (name: string | null | undefined): boolean => !!name && NAME_NAME_PATTERN.test(name);
|
||||||
return name ? NAME_NAME_PATTERN.test(name) : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isInitialsFieldByName = (name: string | null | undefined): boolean => {
|
const isInitialsFieldByName = (name: string | null | undefined): boolean => !!name && INITIALS_NAME_PATTERN.test(name);
|
||||||
return name ? INITIALS_NAME_PATTERN.test(name) : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect AcroForm format actions on a text field dictionary.
|
* Detect AcroForm format actions on a text field dictionary.
|
||||||
@@ -185,13 +176,7 @@ const isInitialsFieldByName = (name: string | null | undefined): boolean => {
|
|||||||
*/
|
*/
|
||||||
const getTextFieldFormatHint = (fieldDict: PdfDict, resolver: RefResolver): 'date' | 'number' | null => {
|
const getTextFieldFormatHint = (fieldDict: PdfDict, resolver: RefResolver): 'date' | 'number' | null => {
|
||||||
try {
|
try {
|
||||||
const aaDict = fieldDict.getDict('AA', resolver);
|
const formatDict = fieldDict.getDict('AA', resolver)?.getDict('F', resolver);
|
||||||
|
|
||||||
if (!aaDict) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatDict = aaDict.getDict('F', resolver);
|
|
||||||
|
|
||||||
if (!formatDict) {
|
if (!formatDict) {
|
||||||
return null;
|
return null;
|
||||||
@@ -416,7 +401,7 @@ const buildSignatureFieldAndMeta = (field: FormFieldWithDict): TFieldAndMeta =>
|
|||||||
...FIELD_SIGNATURE_META_DEFAULT_VALUES,
|
...FIELD_SIGNATURE_META_DEFAULT_VALUES,
|
||||||
required: field.isRequired() || undefined,
|
required: field.isRequired() || undefined,
|
||||||
readOnly: field.isReadOnly() || undefined,
|
readOnly: field.isReadOnly() || undefined,
|
||||||
source: 'acroform' as const,
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -430,14 +415,15 @@ const buildTextFieldAndMeta = (
|
|||||||
const required = field.isRequired() || undefined;
|
const required = field.isRequired() || undefined;
|
||||||
const readOnly = field.isReadOnly() || undefined;
|
const readOnly = field.isReadOnly() || undefined;
|
||||||
|
|
||||||
|
const defaultValue = defaultText && defaultText.length > 0 ? defaultText : undefined;
|
||||||
if (documensoType === FieldType.NUMBER) {
|
if (documensoType === FieldType.NUMBER) {
|
||||||
const fieldMeta: TNumberFieldMeta = {
|
const fieldMeta: TNumberFieldMeta = {
|
||||||
...FIELD_NUMBER_META_DEFAULT_VALUES,
|
...FIELD_NUMBER_META_DEFAULT_VALUES,
|
||||||
label: label ?? FIELD_NUMBER_META_DEFAULT_VALUES.label,
|
label: label ?? FIELD_NUMBER_META_DEFAULT_VALUES.label,
|
||||||
required,
|
required,
|
||||||
readOnly,
|
readOnly,
|
||||||
source: 'acroform' as const,
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
value: defaultText && defaultText.length > 0 ? defaultText : undefined,
|
value: defaultValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
return ZEnvelopeFieldAndMetaSchema.parse({ type: documensoType, fieldMeta });
|
return ZEnvelopeFieldAndMetaSchema.parse({ type: documensoType, fieldMeta });
|
||||||
@@ -449,8 +435,8 @@ const buildTextFieldAndMeta = (
|
|||||||
label: label ?? FIELD_TEXT_META_DEFAULT_VALUES.label,
|
label: label ?? FIELD_TEXT_META_DEFAULT_VALUES.label,
|
||||||
required,
|
required,
|
||||||
readOnly,
|
readOnly,
|
||||||
source: 'acroform' as const,
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
text: defaultText && defaultText.length > 0 ? defaultText : FIELD_TEXT_META_DEFAULT_VALUES.text,
|
text: defaultValue ?? FIELD_TEXT_META_DEFAULT_VALUES.text,
|
||||||
};
|
};
|
||||||
|
|
||||||
return ZEnvelopeFieldAndMetaSchema.parse({ type: documensoType, fieldMeta });
|
return ZEnvelopeFieldAndMetaSchema.parse({ type: documensoType, fieldMeta });
|
||||||
@@ -464,7 +450,7 @@ const buildTextFieldAndMeta = (
|
|||||||
label,
|
label,
|
||||||
required,
|
required,
|
||||||
readOnly,
|
readOnly,
|
||||||
source: 'acroform' as const,
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -477,7 +463,7 @@ const buildTextFieldAndMeta = (
|
|||||||
label,
|
label,
|
||||||
required,
|
required,
|
||||||
readOnly,
|
readOnly,
|
||||||
source: 'acroform' as const,
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -490,7 +476,7 @@ const buildTextFieldAndMeta = (
|
|||||||
label,
|
label,
|
||||||
required,
|
required,
|
||||||
readOnly,
|
readOnly,
|
||||||
source: 'acroform' as const,
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -502,7 +488,7 @@ const buildTextFieldAndMeta = (
|
|||||||
label,
|
label,
|
||||||
required,
|
required,
|
||||||
readOnly,
|
readOnly,
|
||||||
source: 'acroform' as const,
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -520,7 +506,7 @@ const buildCheckboxFieldAndMeta = (
|
|||||||
label: pickLabel(field) ?? FIELD_CHECKBOX_META_DEFAULT_VALUES.label,
|
label: pickLabel(field) ?? FIELD_CHECKBOX_META_DEFAULT_VALUES.label,
|
||||||
required: required || undefined,
|
required: required || undefined,
|
||||||
readOnly: field.isReadOnly() || undefined,
|
readOnly: field.isReadOnly() || undefined,
|
||||||
source: 'acroform' as const,
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
values: [{ id: 1, checked: isChecked, value }],
|
values: [{ id: 1, checked: isChecked, value }],
|
||||||
validationRule: required ? 'at-least' : '',
|
validationRule: required ? 'at-least' : '',
|
||||||
validationLength: required ? 1 : 0,
|
validationLength: required ? 1 : 0,
|
||||||
@@ -555,7 +541,7 @@ const buildRadioFieldAndMeta = (
|
|||||||
label: pickLabel(field) ?? '',
|
label: pickLabel(field) ?? '',
|
||||||
required: field.isRequired() || undefined,
|
required: field.isRequired() || undefined,
|
||||||
readOnly: field.isReadOnly() || undefined,
|
readOnly: field.isReadOnly() || undefined,
|
||||||
source: 'acroform' as const,
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
values,
|
values,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -572,7 +558,7 @@ const buildDropdownFieldAndMeta = (
|
|||||||
label: pickLabel(field) ?? '',
|
label: pickLabel(field) ?? '',
|
||||||
required: field.isRequired() || undefined,
|
required: field.isRequired() || undefined,
|
||||||
readOnly: field.isReadOnly() || undefined,
|
readOnly: field.isReadOnly() || undefined,
|
||||||
source: 'acroform' as const,
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
values: options.length > 0 ? options.map((value) => ({ value })) : FIELD_DROPDOWN_META_DEFAULT_VALUES.values,
|
values: options.length > 0 ? options.map((value) => ({ value })) : FIELD_DROPDOWN_META_DEFAULT_VALUES.values,
|
||||||
defaultValue: defaultValue ?? '',
|
defaultValue: defaultValue ?? '',
|
||||||
};
|
};
|
||||||
@@ -661,6 +647,10 @@ export const extractAcroFormFieldsFromPDF = async (
|
|||||||
const fields: AcroFormFieldImportInfo[] = [];
|
const fields: AcroFormFieldImportInfo[] = [];
|
||||||
const unsupported: AcroFormUnsupportedFieldInfo[] = [];
|
const unsupported: AcroFormUnsupportedFieldInfo[] = [];
|
||||||
let hasSignedSignature = false;
|
let hasSignedSignature = false;
|
||||||
|
const usePdfDefaults = !options.formValuesProvided;
|
||||||
|
const addUnsupported = (fieldName: string, acroFormType: string, reason: AcroFormUnsupportedReason): void => {
|
||||||
|
unsupported.push({ fieldName, acroFormType, reason });
|
||||||
|
};
|
||||||
|
|
||||||
for (const field of formFields) {
|
for (const field of formFields) {
|
||||||
const acroFormType = field.type;
|
const acroFormType = field.type;
|
||||||
@@ -671,11 +661,7 @@ export const extractAcroFormFieldsFromPDF = async (
|
|||||||
acroFormType === 'unknown' ||
|
acroFormType === 'unknown' ||
|
||||||
acroFormType === 'non-terminal'
|
acroFormType === 'non-terminal'
|
||||||
) {
|
) {
|
||||||
unsupported.push({
|
addUnsupported(field.name, acroFormType, 'unsupported-type');
|
||||||
fieldName: field.name,
|
|
||||||
acroFormType,
|
|
||||||
reason: 'unsupported-type',
|
|
||||||
});
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,11 +673,7 @@ export const extractAcroFormFieldsFromPDF = async (
|
|||||||
|
|
||||||
if (typeof sigField.isSigned === 'function' && sigField.isSigned()) {
|
if (typeof sigField.isSigned === 'function' && sigField.isSigned()) {
|
||||||
hasSignedSignature = true;
|
hasSignedSignature = true;
|
||||||
unsupported.push({
|
addUnsupported(field.name, acroFormType, 'signed-signature');
|
||||||
fieldName: field.name,
|
|
||||||
acroFormType,
|
|
||||||
reason: 'signed-signature',
|
|
||||||
});
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -701,38 +683,25 @@ export const extractAcroFormFieldsFromPDF = async (
|
|||||||
const { matched, unmatched } = resolveWidgetPages(widgets, pageByRef);
|
const { matched, unmatched } = resolveWidgetPages(widgets, pageByRef);
|
||||||
|
|
||||||
for (let i = 0; i < unmatched.length; i += 1) {
|
for (let i = 0; i < unmatched.length; i += 1) {
|
||||||
unsupported.push({
|
addUnsupported(field.name, acroFormType, 'no-page-match');
|
||||||
fieldName: field.name,
|
|
||||||
acroFormType,
|
|
||||||
reason: 'no-page-match',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let widgetCounter = 0;
|
let widgetCounter = 0;
|
||||||
|
|
||||||
for (const { widget, pageIndex, page } of matched) {
|
for (const { widget, pageIndex, page } of matched) {
|
||||||
if (widget.isHidden()) {
|
if (widget.isHidden()) {
|
||||||
unsupported.push({
|
addUnsupported(field.name, acroFormType, 'hidden');
|
||||||
fieldName: field.name,
|
|
||||||
acroFormType,
|
|
||||||
reason: 'hidden',
|
|
||||||
});
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { geometry, reason } = resolveGeometry(widget, pageIndex, page);
|
const { geometry, reason } = resolveGeometry(widget, pageIndex, page);
|
||||||
|
|
||||||
if (!geometry) {
|
if (!geometry) {
|
||||||
unsupported.push({
|
addUnsupported(field.name, acroFormType, reason ?? 'zero-size');
|
||||||
fieldName: field.name,
|
|
||||||
acroFormType,
|
|
||||||
reason: reason ?? 'zero-size',
|
|
||||||
});
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fieldAndMeta: TFieldAndMeta;
|
let fieldAndMeta: TFieldAndMeta;
|
||||||
const usePdfDefaults = !options.formValuesProvided;
|
|
||||||
|
|
||||||
if (acroFormType === 'signature') {
|
if (acroFormType === 'signature') {
|
||||||
fieldAndMeta = buildSignatureFieldAndMeta(formField);
|
fieldAndMeta = buildSignatureFieldAndMeta(formField);
|
||||||
@@ -779,16 +748,12 @@ export const extractAcroFormFieldsFromPDF = async (
|
|||||||
const currentSelection = usePdfDefaults ? dropdown.getValue?.() || dropdown.getDefaultValue?.() || '' : '';
|
const currentSelection = usePdfDefaults ? dropdown.getValue?.() || dropdown.getDefaultValue?.() || '' : '';
|
||||||
fieldAndMeta = buildDropdownFieldAndMeta(formField, optionValues, currentSelection || undefined);
|
fieldAndMeta = buildDropdownFieldAndMeta(formField, optionValues, currentSelection || undefined);
|
||||||
} else {
|
} else {
|
||||||
unsupported.push({
|
addUnsupported(field.name, acroFormType, 'unsupported-type');
|
||||||
fieldName: field.name,
|
|
||||||
acroFormType,
|
|
||||||
reason: 'unsupported-type',
|
|
||||||
});
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
fields.push({
|
fields.push({
|
||||||
source: 'acroform',
|
source: ACROFORM_FIELD_SOURCE,
|
||||||
fieldName: field.name,
|
fieldName: field.name,
|
||||||
widgetIndex: widgetCounter,
|
widgetIndex: widgetCounter,
|
||||||
fieldAndMeta,
|
fieldAndMeta,
|
||||||
@@ -845,9 +810,7 @@ export const convertAcroFormFieldsToFieldInputs = (
|
|||||||
recipientResolver: (fieldName: string) => Pick<Recipient, 'id'>,
|
recipientResolver: (fieldName: string) => Pick<Recipient, 'id'>,
|
||||||
envelopeItemId?: string,
|
envelopeItemId?: string,
|
||||||
): FieldToCreate[] => {
|
): FieldToCreate[] => {
|
||||||
const sorted = sortFieldsForCreate(fields);
|
return sortFieldsForCreate(fields).map((f) => {
|
||||||
|
|
||||||
return sorted.map((f) => {
|
|
||||||
const xPercent = (f.x / f.pageWidth) * 100;
|
const xPercent = (f.x / f.pageWidth) * 100;
|
||||||
const yPercent = (f.y / f.pageHeight) * 100;
|
const yPercent = (f.y / f.pageHeight) * 100;
|
||||||
const widthPercent = (f.width / f.pageWidth) * 100;
|
const widthPercent = (f.width / f.pageWidth) * 100;
|
||||||
|
|||||||
Reference in New Issue
Block a user