Skip to content

Commit ad02959

Browse files
committed
add support for radio button
1 parent c8b2a2f commit ad02959

22 files changed

Lines changed: 1299 additions & 152 deletions

File tree

packages/engines/src/lib/orchestrator/pdf-engine.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,48 @@ export class PdfEngine<T = Blob> implements IPdfEngine<T> {
843843
);
844844
}
845845

846+
renameWidgetField(
847+
doc: PdfDocumentObject,
848+
page: PdfPageObject,
849+
annotation: PdfWidgetAnnoObject,
850+
name: string,
851+
): PdfTask<boolean> {
852+
return this.workerQueue.enqueue(
853+
{
854+
execute: () => this.executor.renameWidgetField(doc, page, annotation, name),
855+
meta: { docId: doc.id, pageIndex: page.index, operation: 'renameWidgetField' },
856+
},
857+
{ priority: Priority.MEDIUM },
858+
);
859+
}
860+
861+
shareWidgetField(
862+
doc: PdfDocumentObject,
863+
sourcePage: PdfPageObject,
864+
sourceAnnotation: PdfWidgetAnnoObject,
865+
targetPage: PdfPageObject,
866+
targetAnnotation: PdfWidgetAnnoObject,
867+
): PdfTask<boolean> {
868+
return this.workerQueue.enqueue(
869+
{
870+
execute: () =>
871+
this.executor.shareWidgetField(
872+
doc,
873+
sourcePage,
874+
sourceAnnotation,
875+
targetPage,
876+
targetAnnotation,
877+
),
878+
meta: {
879+
docId: doc.id,
880+
pageIndex: sourcePage.index,
881+
operation: 'shareWidgetField',
882+
},
883+
},
884+
{ priority: Priority.MEDIUM },
885+
);
886+
}
887+
846888
regenerateWidgetAppearances(
847889
doc: PdfDocumentObject,
848890
page: PdfPageObject,

packages/engines/src/lib/orchestrator/remote-executor.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ type MessageType =
104104
| 'getPageWidgetJavaScriptActions'
105105
| 'setFormFieldValue'
106106
| 'setFormFieldState'
107+
| 'renameWidgetField'
108+
| 'shareWidgetField'
107109
| 'regenerateWidgetAppearances'
108110
| 'flattenPage'
109111
| 'extractPages'
@@ -500,6 +502,31 @@ export class RemoteExecutor implements IPdfiumExecutor {
500502
return this.send<boolean>('setFormFieldState', [doc, page, annotation, field]);
501503
}
502504

505+
renameWidgetField(
506+
doc: PdfDocumentObject,
507+
page: PdfPageObject,
508+
annotation: PdfWidgetAnnoObject,
509+
name: string,
510+
): PdfTask<boolean> {
511+
return this.send<boolean>('renameWidgetField', [doc, page, annotation, name]);
512+
}
513+
514+
shareWidgetField(
515+
doc: PdfDocumentObject,
516+
sourcePage: PdfPageObject,
517+
sourceAnnotation: PdfWidgetAnnoObject,
518+
targetPage: PdfPageObject,
519+
targetAnnotation: PdfWidgetAnnoObject,
520+
): PdfTask<boolean> {
521+
return this.send<boolean>('shareWidgetField', [
522+
doc,
523+
sourcePage,
524+
sourceAnnotation,
525+
targetPage,
526+
targetAnnotation,
527+
]);
528+
}
529+
503530
regenerateWidgetAppearances(
504531
doc: PdfDocumentObject,
505532
page: PdfPageObject,

packages/engines/src/lib/pdfium/engine.ts

Lines changed: 203 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,7 +1349,8 @@ export class PdfiumNative implements IPdfiumExecutor {
13491349
isSucceed = this.addTextFieldContent(widgetFormHandle, annotationPtr, widget);
13501350
break;
13511351
case PDF_FORM_FIELD_TYPE.CHECKBOX:
1352-
isSucceed = this.addCheckboxContent(widgetFormHandle, annotationPtr, widget);
1352+
case PDF_FORM_FIELD_TYPE.RADIOBUTTON:
1353+
isSucceed = this.addToggleFieldContent(widgetFormHandle, annotationPtr, widget);
13531354
break;
13541355
case PDF_FORM_FIELD_TYPE.COMBOBOX:
13551356
case PDF_FORM_FIELD_TYPE.LISTBOX:
@@ -1628,7 +1629,8 @@ export class PdfiumNative implements IPdfiumExecutor {
16281629
ok = this.addTextFieldContent(formHandle, annotPtr, widget);
16291630
break;
16301631
case PDF_FORM_FIELD_TYPE.CHECKBOX:
1631-
ok = this.addCheckboxContent(formHandle, annotPtr, widget);
1632+
case PDF_FORM_FIELD_TYPE.RADIOBUTTON:
1633+
ok = this.addToggleFieldContent(formHandle, annotPtr, widget);
16321634
break;
16331635
case PDF_FORM_FIELD_TYPE.COMBOBOX:
16341636
case PDF_FORM_FIELD_TYPE.LISTBOX:
@@ -2326,6 +2328,186 @@ export class PdfiumNative implements IPdfiumExecutor {
23262328
}
23272329
}
23282330

2331+
renameWidgetField(
2332+
doc: PdfDocumentObject,
2333+
page: PdfPageObject,
2334+
annotation: PdfWidgetAnnoObject,
2335+
name: string,
2336+
): PdfTask<boolean> {
2337+
this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'RenameWidgetField', doc, annotation, name);
2338+
this.logger.perf(
2339+
LOG_SOURCE,
2340+
LOG_CATEGORY,
2341+
`RenameWidgetField`,
2342+
'Begin',
2343+
`${doc.id}-${annotation.id}`,
2344+
);
2345+
2346+
const ctx = this.cache.getContext(doc.id);
2347+
if (!ctx) {
2348+
this.logger.perf(
2349+
LOG_SOURCE,
2350+
LOG_CATEGORY,
2351+
`RenameWidgetField`,
2352+
'End',
2353+
`${doc.id}-${annotation.id}`,
2354+
);
2355+
return PdfTaskHelper.reject({
2356+
code: PdfErrorCode.DocNotOpen,
2357+
message: 'document does not open',
2358+
});
2359+
}
2360+
2361+
const pageCtx = ctx.acquirePage(page.index);
2362+
try {
2363+
return pageCtx.withFormHandle((formHandle) => {
2364+
const annotationPtr = this.getAnnotationByName(pageCtx.pagePtr, annotation.id);
2365+
if (!annotationPtr) {
2366+
return PdfTaskHelper.reject({
2367+
code: PdfErrorCode.NotFound,
2368+
message: 'annotation not found',
2369+
});
2370+
}
2371+
2372+
try {
2373+
const ok = this.withWString(name, (namePtr) =>
2374+
this.pdfiumModule.EPDFAnnot_SetFormFieldName(formHandle, annotationPtr, namePtr),
2375+
);
2376+
if (!ok) {
2377+
return PdfTaskHelper.reject({
2378+
code: PdfErrorCode.CantSetAnnotString,
2379+
message: 'failed to rename widget field',
2380+
});
2381+
}
2382+
2383+
this.logger.perf(
2384+
LOG_SOURCE,
2385+
LOG_CATEGORY,
2386+
`RenameWidgetField`,
2387+
'End',
2388+
`${doc.id}-${annotation.id}`,
2389+
);
2390+
return PdfTaskHelper.resolve<boolean>(true);
2391+
} finally {
2392+
this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
2393+
}
2394+
});
2395+
} finally {
2396+
pageCtx.release();
2397+
}
2398+
}
2399+
2400+
shareWidgetField(
2401+
doc: PdfDocumentObject,
2402+
sourcePage: PdfPageObject,
2403+
sourceAnnotation: PdfWidgetAnnoObject,
2404+
targetPage: PdfPageObject,
2405+
targetAnnotation: PdfWidgetAnnoObject,
2406+
): PdfTask<boolean> {
2407+
this.logger.debug(
2408+
LOG_SOURCE,
2409+
LOG_CATEGORY,
2410+
'ShareWidgetField',
2411+
doc,
2412+
sourceAnnotation,
2413+
targetAnnotation,
2414+
);
2415+
this.logger.perf(
2416+
LOG_SOURCE,
2417+
LOG_CATEGORY,
2418+
`ShareWidgetField`,
2419+
'Begin',
2420+
`${doc.id}-${sourceAnnotation.id}-${targetAnnotation.id}`,
2421+
);
2422+
2423+
const ctx = this.cache.getContext(doc.id);
2424+
if (!ctx) {
2425+
this.logger.perf(
2426+
LOG_SOURCE,
2427+
LOG_CATEGORY,
2428+
`ShareWidgetField`,
2429+
'End',
2430+
`${doc.id}-${sourceAnnotation.id}-${targetAnnotation.id}`,
2431+
);
2432+
return PdfTaskHelper.reject({
2433+
code: PdfErrorCode.DocNotOpen,
2434+
message: 'document does not open',
2435+
});
2436+
}
2437+
2438+
const sourcePageCtx = ctx.acquirePage(sourcePage.index);
2439+
const targetPageCtx =
2440+
targetPage.index === sourcePage.index ? sourcePageCtx : ctx.acquirePage(targetPage.index);
2441+
2442+
try {
2443+
return sourcePageCtx.withFormHandle((formHandle) => {
2444+
let targetPageLoaded = false;
2445+
if (targetPageCtx !== sourcePageCtx) {
2446+
this.pdfiumModule.FORM_OnAfterLoadPage(targetPageCtx.pagePtr, formHandle);
2447+
targetPageLoaded = true;
2448+
}
2449+
2450+
const sourceAnnotationPtr = this.getAnnotationByName(
2451+
sourcePageCtx.pagePtr,
2452+
sourceAnnotation.id,
2453+
);
2454+
const targetAnnotationPtr = this.getAnnotationByName(
2455+
targetPageCtx.pagePtr,
2456+
targetAnnotation.id,
2457+
);
2458+
if (!sourceAnnotationPtr || !targetAnnotationPtr) {
2459+
if (sourceAnnotationPtr) {
2460+
this.pdfiumModule.FPDFPage_CloseAnnot(sourceAnnotationPtr);
2461+
}
2462+
if (targetAnnotationPtr) {
2463+
this.pdfiumModule.FPDFPage_CloseAnnot(targetAnnotationPtr);
2464+
}
2465+
if (targetPageLoaded) {
2466+
this.pdfiumModule.FORM_OnBeforeClosePage(targetPageCtx.pagePtr, formHandle);
2467+
}
2468+
return PdfTaskHelper.reject({
2469+
code: PdfErrorCode.NotFound,
2470+
message: 'annotation not found',
2471+
});
2472+
}
2473+
2474+
try {
2475+
const ok = this.pdfiumModule.EPDFAnnot_ShareFormField(
2476+
formHandle,
2477+
sourceAnnotationPtr,
2478+
targetAnnotationPtr,
2479+
);
2480+
if (!ok) {
2481+
return PdfTaskHelper.reject({
2482+
code: PdfErrorCode.Unknown,
2483+
message: 'failed to share widget field',
2484+
});
2485+
}
2486+
2487+
this.logger.perf(
2488+
LOG_SOURCE,
2489+
LOG_CATEGORY,
2490+
`ShareWidgetField`,
2491+
'End',
2492+
`${doc.id}-${sourceAnnotation.id}-${targetAnnotation.id}`,
2493+
);
2494+
return PdfTaskHelper.resolve<boolean>(true);
2495+
} finally {
2496+
this.pdfiumModule.FPDFPage_CloseAnnot(sourceAnnotationPtr);
2497+
this.pdfiumModule.FPDFPage_CloseAnnot(targetAnnotationPtr);
2498+
if (targetPageLoaded) {
2499+
this.pdfiumModule.FORM_OnBeforeClosePage(targetPageCtx.pagePtr, formHandle);
2500+
}
2501+
}
2502+
});
2503+
} finally {
2504+
sourcePageCtx.release();
2505+
if (targetPageCtx !== sourcePageCtx) {
2506+
targetPageCtx.release();
2507+
}
2508+
}
2509+
}
2510+
23292511
/**
23302512
* {@inheritDoc @embedpdf/models!PdfEngine.flattenPage}
23312513
*
@@ -3092,7 +3274,7 @@ export class PdfiumNative implements IPdfiumExecutor {
30923274
return true;
30933275
}
30943276

3095-
private addCheckboxContent(
3277+
private addToggleFieldContent(
30963278
formHandle: number,
30973279
annotationPtr: number,
30983280
annotation: PdfWidgetAnnoObject,
@@ -3120,9 +3302,12 @@ export class PdfiumNative implements IPdfiumExecutor {
31203302
this.clearMKColor(annotationPtr, 1);
31213303
}
31223304

3123-
// 3. Form field flags
3124-
const userFlags = annotation.field.flag ?? PDF_FORM_FIELD_FLAG.NONE;
3125-
this.pdfiumModule.FPDFAnnot_SetFormFieldFlags(formHandle, annotationPtr, userFlags);
3305+
// 3. Form field flags (preserve structural bits for radio buttons)
3306+
let finalFlags = annotation.field.flag ?? PDF_FORM_FIELD_FLAG.NONE;
3307+
if (annotation.field.type === PDF_FORM_FIELD_TYPE.RADIOBUTTON) {
3308+
finalFlags |= PDF_FORM_FIELD_FLAG.BUTTON_RADIO | PDF_FORM_FIELD_FLAG.BUTTON_NOTOGGLETOOFF;
3309+
}
3310+
this.pdfiumModule.FPDFAnnot_SetFormFieldFlags(formHandle, annotationPtr, finalFlags);
31263311

31273312
// 4. Field name
31283313
if (annotation.field.name) {
@@ -8486,7 +8671,18 @@ export class PdfiumNative implements IPdfiumExecutor {
84868671
this.pdfiumModule.pdfium.UTF16ToString,
84878672
);
84888673

8489-
const base = { flag, name, alternateName, value };
8674+
const fieldObjectId = this.pdfiumModule.EPDFAnnot_GetFormFieldObjectNumber(
8675+
formHandle,
8676+
annotationPtr,
8677+
);
8678+
8679+
const base = {
8680+
flag,
8681+
name,
8682+
alternateName,
8683+
value,
8684+
fieldObjectId: fieldObjectId > 0 ? fieldObjectId : undefined,
8685+
};
84908686

84918687
switch (type) {
84928688
case PDF_FORM_FIELD_TYPE.TEXTFIELD: {

packages/engines/src/lib/webworker/engine.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,57 @@ export class WebWorkerEngine implements PdfEngine {
893893
return task;
894894
}
895895

896+
renameWidgetField(
897+
doc: PdfDocumentObject,
898+
page: PdfPageObject,
899+
annotation: PdfWidgetAnnoObject,
900+
name: string,
901+
) {
902+
this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'renameWidgetField', doc, annotation, name);
903+
const requestId = this.generateRequestId(doc.id);
904+
const task = new WorkerTask<boolean>(this.worker, requestId);
905+
906+
const request: ExecuteRequest = createRequest(requestId, 'renameWidgetField', [
907+
doc,
908+
page,
909+
annotation,
910+
name,
911+
]);
912+
this.proxy(task, request);
913+
914+
return task;
915+
}
916+
917+
shareWidgetField(
918+
doc: PdfDocumentObject,
919+
sourcePage: PdfPageObject,
920+
sourceAnnotation: PdfWidgetAnnoObject,
921+
targetPage: PdfPageObject,
922+
targetAnnotation: PdfWidgetAnnoObject,
923+
) {
924+
this.logger.debug(
925+
LOG_SOURCE,
926+
LOG_CATEGORY,
927+
'shareWidgetField',
928+
doc,
929+
sourceAnnotation,
930+
targetAnnotation,
931+
);
932+
const requestId = this.generateRequestId(doc.id);
933+
const task = new WorkerTask<boolean>(this.worker, requestId);
934+
935+
const request: ExecuteRequest = createRequest(requestId, 'shareWidgetField', [
936+
doc,
937+
sourcePage,
938+
sourceAnnotation,
939+
targetPage,
940+
targetAnnotation,
941+
]);
942+
this.proxy(task, request);
943+
944+
return task;
945+
}
946+
896947
regenerateWidgetAppearances(
897948
doc: PdfDocumentObject,
898949
page: PdfPageObject,

0 commit comments

Comments
 (0)