Skip to content

Commit 9e21875

Browse files
authored
Merge pull request #238 from 0xbe7a/init-forms-options
add option for opt-in form rendering
2 parents 66f2e7c + e452cc6 commit 9e21875

File tree

6 files changed

+147
-5
lines changed

6 files changed

+147
-5
lines changed

.changeset/weak-pugs-hear.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@embedpdf/engines': minor
3+
'@embedpdf/models': minor
4+
'@embedpdf/plugin-render': minor
5+
---
6+
7+
Add optional **form widget rendering** to the render pipeline.
8+
9+
### What changed
10+
11+
- **@embedpdf/models**
12+
13+
- `PdfRenderPageOptions` now supports `withForms?: boolean` to request drawing interactive form widgets.
14+
15+
- **@embedpdf/engines**
16+
17+
- `PdfiumEngine.renderPage` and `renderPageRect` honor `withForms`.
18+
When enabled, the engine initializes the page form handle and calls `FPDF_FFLDraw` with the correct device transform.
19+
- New helper `computeFormDrawParams(matrix, rect, pageSize, rotation)` calculates start offsets and sizes for `FPDF_FFLDraw`.
20+
21+
- **@embedpdf/plugin-render**
22+
- New plugin config flags:
23+
- `withForms?: boolean` (default `false`)
24+
- `withAnnotations?: boolean` (default `false`)
25+
- The plugin merges per-call options with plugin defaults so callers can set once at init or override per call.

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
PdfWidgetAnnoOption,
2525
PdfFileAttachmentAnnoObject,
2626
Rect,
27+
Size,
2728
PdfAttachmentObject,
2829
PdfUnsupportedAnnoObject,
2930
PdfTextAnnoObject,
@@ -114,13 +115,14 @@ import {
114115
PdfAnnotationsProgress,
115116
ConvertToBlobOptions,
116117
buildUserToDeviceMatrix,
118+
Matrix,
117119
PdfMetadataObject,
118120
PdfPrintOptions,
119121
PdfTrappedStatus,
120122
PdfStampFit,
121123
PdfAddAttachmentParams,
122124
} from '@embedpdf/models';
123-
import { isValidCustomKey, readArrayBuffer, readString } from './helper';
125+
import { computeFormDrawParams, isValidCustomKey, readArrayBuffer, readString } from './helper';
124126
import { WrappedPdfiumModule } from '@embedpdf/pdfium';
125127
import { DocumentContext, PageContext, PdfCache } from './cache';
126128
import { ImageDataConverter, LazyImageData } from '../converters/types';
@@ -6956,6 +6958,8 @@ export class PdfiumEngine<T = Blob> implements PdfEngine<T> {
69566958
const bytes = stride * hDev;
69576959

69586960
const pageCtx = ctx.acquirePage(page.index);
6961+
const shouldRenderForms = options?.withForms ?? false;
6962+
const formHandle = shouldRenderForms ? pageCtx.getFormHandle() : undefined;
69596963

69606964
// ---- 2) allocate a BGRA bitmap in WASM
69616965
const heapPtr = this.memoryManager.malloc(bytes);
@@ -6992,6 +6996,24 @@ export class PdfiumEngine<T = Blob> implements PdfEngine<T> {
69926996
clipPtr,
69936997
flags,
69946998
);
6999+
7000+
if (formHandle !== undefined) {
7001+
const formParams = computeFormDrawParams(M, rect, page.size, rotation);
7002+
const { startX, startY, formsWidth, formsHeight, scaleX, scaleY } = formParams;
7003+
7004+
// Draw form elements using the same effective transform as the page bitmap.
7005+
this.pdfiumModule.FPDF_FFLDraw(
7006+
formHandle,
7007+
bitmapPtr,
7008+
pageCtx.pagePtr,
7009+
startX,
7010+
startY,
7011+
formsWidth,
7012+
formsHeight,
7013+
rotation,
7014+
flags,
7015+
);
7016+
}
69957017
} finally {
69967018
pageCtx.release();
69977019
this.memoryManager.free(mPtr);

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Matrix, Rotation, Rect, Size } from '@embedpdf/models';
12
import { PdfiumRuntimeMethods, PdfiumModule } from '@embedpdf/pdfium';
23

34
/**
@@ -92,3 +93,65 @@ export function isValidCustomKey(key: string): boolean {
9293
}
9394
return true;
9495
}
96+
97+
interface FormDrawParams {
98+
startX: number;
99+
startY: number;
100+
formsWidth: number;
101+
formsHeight: number;
102+
scaleX: number;
103+
scaleY: number;
104+
}
105+
106+
export function computeFormDrawParams(
107+
matrix: Matrix,
108+
rect: Rect,
109+
pageSize: Size,
110+
rotation: Rotation,
111+
): FormDrawParams {
112+
const rectLeft = rect.origin.x;
113+
const rectBottom = rect.origin.y;
114+
const rectRight = rectLeft + rect.size.width;
115+
const rectTop = rectBottom + rect.size.height;
116+
const pageWidth = pageSize.width;
117+
const pageHeight = pageSize.height;
118+
119+
// Extract the per-axis scale that the render matrix applies.
120+
const scaleX = Math.hypot(matrix.a, matrix.b);
121+
const scaleY = Math.hypot(matrix.c, matrix.d);
122+
const swap = (rotation & 1) === 1;
123+
124+
const formsWidth = swap
125+
? Math.max(1, Math.round(pageHeight * scaleX))
126+
: Math.max(1, Math.round(pageWidth * scaleX));
127+
const formsHeight = swap
128+
? Math.max(1, Math.round(pageWidth * scaleY))
129+
: Math.max(1, Math.round(pageHeight * scaleY));
130+
131+
let startX: number;
132+
let startY: number;
133+
switch (rotation) {
134+
case Rotation.Degree0:
135+
startX = -Math.round(rectLeft * scaleX);
136+
startY = -Math.round(rectBottom * scaleY);
137+
break;
138+
case Rotation.Degree90:
139+
startX = Math.round((rectTop - pageHeight) * scaleX);
140+
startY = -Math.round(rectLeft * scaleY);
141+
break;
142+
case Rotation.Degree180:
143+
startX = Math.round((rectRight - pageWidth) * scaleX);
144+
startY = Math.round((rectTop - pageHeight) * scaleY);
145+
break;
146+
case Rotation.Degree270:
147+
startX = -Math.round(rectBottom * scaleX);
148+
startY = Math.round((rectRight - pageWidth) * scaleY);
149+
break;
150+
default:
151+
startX = -Math.round(rectLeft * scaleX);
152+
startY = -Math.round(rectBottom * scaleY);
153+
break;
154+
}
155+
156+
return { startX, startY, formsWidth, formsHeight, scaleX, scaleY };
157+
}

packages/models/src/pdf.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,6 +2512,10 @@ export interface PdfRenderPageOptions extends PdfRenderOptions {
25122512
* Whether to render annotations
25132513
*/
25142514
withAnnotations?: boolean;
2515+
/**
2516+
* Whether to render interactive form widgets
2517+
*/
2518+
withForms?: boolean;
25152519
}
25162520

25172521
export interface PdfRenderPageAnnotationOptions extends PdfRenderOptions {

packages/plugin-render/src/lib/render-plugin.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export class RenderPlugin extends BasePlugin<RenderPluginConfig, RenderCapabilit
1616
static readonly id = 'render' as const;
1717

1818
private readonly refreshPages$ = createEmitter<number[]>();
19+
private withForms = false;
20+
private withAnnotations = false;
1921

2022
constructor(id: string, registry: PluginRegistry) {
2123
super(id, registry);
@@ -25,7 +27,10 @@ export class RenderPlugin extends BasePlugin<RenderPluginConfig, RenderCapabilit
2527
});
2628
}
2729

28-
async initialize(_config: RenderPluginConfig): Promise<void> {}
30+
async initialize(config: RenderPluginConfig): Promise<void> {
31+
this.withForms = config.withForms ?? false;
32+
this.withAnnotations = config.withAnnotations ?? false;
33+
}
2934

3035
protected buildCapability(): RenderCapability {
3136
return {
@@ -50,7 +55,13 @@ export class RenderPlugin extends BasePlugin<RenderPluginConfig, RenderCapabilit
5055
throw new Error('page does not exist');
5156
}
5257

53-
return this.engine.renderPage(coreState.document, page, options);
58+
const mergedOptions = {
59+
...(options ?? {}),
60+
withForms: options?.withForms ?? this.withForms,
61+
withAnnotations: options?.withAnnotations ?? this.withAnnotations,
62+
};
63+
64+
return this.engine.renderPage(coreState.document, page, mergedOptions);
5465
}
5566

5667
private renderPageRect({ pageIndex, rect, options }: RenderPageRectOptions) {
@@ -65,6 +76,12 @@ export class RenderPlugin extends BasePlugin<RenderPluginConfig, RenderCapabilit
6576
throw new Error('page does not exist');
6677
}
6778

68-
return this.engine.renderPageRect(coreState.document, page, rect, options);
79+
const mergedOptions = {
80+
...(options ?? {}),
81+
withForms: options?.withForms ?? this.withForms,
82+
withAnnotations: options?.withAnnotations ?? this.withAnnotations,
83+
};
84+
85+
return this.engine.renderPageRect(coreState.document, page, rect, mergedOptions);
6986
}
7087
}

packages/plugin-render/src/lib/types.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
import { BasePluginConfig } from '@embedpdf/core';
22
import { PdfErrorReason, PdfRenderPageOptions, Rect, Task } from '@embedpdf/models';
33

4-
export interface RenderPluginConfig extends BasePluginConfig {}
4+
export interface RenderPluginConfig extends BasePluginConfig {
5+
/**
6+
* Initialize and draw form widgets during renders.
7+
* Defaults to `false`.
8+
*/
9+
withForms?: boolean;
10+
/**
11+
* Whether to render annotations
12+
* Defaults to `false`.
13+
*/
14+
withAnnotations?: boolean;
15+
}
516

617
export interface RenderPageRectOptions {
718
pageIndex: number;

0 commit comments

Comments
 (0)