Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .changeset/weak-pugs-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'@embedpdf/engines': minor
'@embedpdf/models': minor
'@embedpdf/plugin-render': minor
---

Add optional **form widget rendering** to the render pipeline.

### What changed

- **@embedpdf/models**

- `PdfRenderPageOptions` now supports `withForms?: boolean` to request drawing interactive form widgets.

- **@embedpdf/engines**

- `PdfiumEngine.renderPage` and `renderPageRect` honor `withForms`.
When enabled, the engine initializes the page form handle and calls `FPDF_FFLDraw` with the correct device transform.
- New helper `computeFormDrawParams(matrix, rect, pageSize, rotation)` calculates start offsets and sizes for `FPDF_FFLDraw`.

- **@embedpdf/plugin-render**
- New plugin config flags:
- `withForms?: boolean` (default `false`)
- `withAnnotations?: boolean` (default `false`)
- The plugin merges per-call options with plugin defaults so callers can set once at init or override per call.
24 changes: 23 additions & 1 deletion packages/engines/src/lib/pdfium/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
PdfWidgetAnnoOption,
PdfFileAttachmentAnnoObject,
Rect,
Size,
PdfAttachmentObject,
PdfUnsupportedAnnoObject,
PdfTextAnnoObject,
Expand Down Expand Up @@ -114,13 +115,14 @@ import {
PdfAnnotationsProgress,
ConvertToBlobOptions,
buildUserToDeviceMatrix,
Matrix,
PdfMetadataObject,
PdfPrintOptions,
PdfTrappedStatus,
PdfStampFit,
PdfAddAttachmentParams,
} from '@embedpdf/models';
import { isValidCustomKey, readArrayBuffer, readString } from './helper';
import { computeFormDrawParams, isValidCustomKey, readArrayBuffer, readString } from './helper';
import { WrappedPdfiumModule } from '@embedpdf/pdfium';
import { DocumentContext, PageContext, PdfCache } from './cache';
import { ImageDataConverter, LazyImageData } from '../converters/types';
Expand Down Expand Up @@ -6956,6 +6958,8 @@ export class PdfiumEngine<T = Blob> implements PdfEngine<T> {
const bytes = stride * hDev;

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

// ---- 2) allocate a BGRA bitmap in WASM
const heapPtr = this.memoryManager.malloc(bytes);
Expand Down Expand Up @@ -6992,6 +6996,24 @@ export class PdfiumEngine<T = Blob> implements PdfEngine<T> {
clipPtr,
flags,
);

if (formHandle !== undefined) {
const formParams = computeFormDrawParams(M, rect, page.size, rotation);
const { startX, startY, formsWidth, formsHeight, scaleX, scaleY } = formParams;

// Draw form elements using the same effective transform as the page bitmap.
this.pdfiumModule.FPDF_FFLDraw(
formHandle,
bitmapPtr,
pageCtx.pagePtr,
startX,
startY,
formsWidth,
formsHeight,
rotation,
flags,
);
}
} finally {
pageCtx.release();
this.memoryManager.free(mPtr);
Expand Down
63 changes: 63 additions & 0 deletions packages/engines/src/lib/pdfium/helper.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Matrix, Rotation, Rect, Size } from '@embedpdf/models';
import { PdfiumRuntimeMethods, PdfiumModule } from '@embedpdf/pdfium';

/**
Expand Down Expand Up @@ -92,3 +93,65 @@ export function isValidCustomKey(key: string): boolean {
}
return true;
}

interface FormDrawParams {
startX: number;
startY: number;
formsWidth: number;
formsHeight: number;
scaleX: number;
scaleY: number;
}

export function computeFormDrawParams(
matrix: Matrix,
rect: Rect,
pageSize: Size,
rotation: Rotation,
): FormDrawParams {
const rectLeft = rect.origin.x;
const rectBottom = rect.origin.y;
const rectRight = rectLeft + rect.size.width;
const rectTop = rectBottom + rect.size.height;
const pageWidth = pageSize.width;
const pageHeight = pageSize.height;

// Extract the per-axis scale that the render matrix applies.
const scaleX = Math.hypot(matrix.a, matrix.b);
const scaleY = Math.hypot(matrix.c, matrix.d);
const swap = (rotation & 1) === 1;

const formsWidth = swap
? Math.max(1, Math.round(pageHeight * scaleX))
: Math.max(1, Math.round(pageWidth * scaleX));
const formsHeight = swap
? Math.max(1, Math.round(pageWidth * scaleY))
: Math.max(1, Math.round(pageHeight * scaleY));

let startX: number;
let startY: number;
switch (rotation) {
case Rotation.Degree0:
startX = -Math.round(rectLeft * scaleX);
startY = -Math.round(rectBottom * scaleY);
break;
case Rotation.Degree90:
startX = Math.round((rectTop - pageHeight) * scaleX);
startY = -Math.round(rectLeft * scaleY);
break;
case Rotation.Degree180:
startX = Math.round((rectRight - pageWidth) * scaleX);
startY = Math.round((rectTop - pageHeight) * scaleY);
break;
case Rotation.Degree270:
startX = -Math.round(rectBottom * scaleX);
startY = Math.round((rectRight - pageWidth) * scaleY);
break;
default:
startX = -Math.round(rectLeft * scaleX);
startY = -Math.round(rectBottom * scaleY);
break;
}

return { startX, startY, formsWidth, formsHeight, scaleX, scaleY };
}
4 changes: 4 additions & 0 deletions packages/models/src/pdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2512,6 +2512,10 @@ export interface PdfRenderPageOptions extends PdfRenderOptions {
* Whether to render annotations
*/
withAnnotations?: boolean;
/**
* Whether to render interactive form widgets
*/
withForms?: boolean;
}

export interface PdfRenderPageAnnotationOptions extends PdfRenderOptions {
Expand Down
23 changes: 20 additions & 3 deletions packages/plugin-render/src/lib/render-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export class RenderPlugin extends BasePlugin<RenderPluginConfig, RenderCapabilit
static readonly id = 'render' as const;

private readonly refreshPages$ = createEmitter<number[]>();
private withForms = false;
private withAnnotations = false;

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

async initialize(_config: RenderPluginConfig): Promise<void> {}
async initialize(config: RenderPluginConfig): Promise<void> {
this.withForms = config.withForms ?? false;
this.withAnnotations = config.withAnnotations ?? false;
}

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

return this.engine.renderPage(coreState.document, page, options);
const mergedOptions = {
...(options ?? {}),
withForms: options?.withForms ?? this.withForms,
withAnnotations: options?.withAnnotations ?? this.withAnnotations,
};

return this.engine.renderPage(coreState.document, page, mergedOptions);
}

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

return this.engine.renderPageRect(coreState.document, page, rect, options);
const mergedOptions = {
...(options ?? {}),
withForms: options?.withForms ?? this.withForms,
withAnnotations: options?.withAnnotations ?? this.withAnnotations,
};

return this.engine.renderPageRect(coreState.document, page, rect, mergedOptions);
}
}
13 changes: 12 additions & 1 deletion packages/plugin-render/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { BasePluginConfig } from '@embedpdf/core';
import { PdfErrorReason, PdfRenderPageOptions, Rect, Task } from '@embedpdf/models';

export interface RenderPluginConfig extends BasePluginConfig {}
export interface RenderPluginConfig extends BasePluginConfig {
/**
* Initialize and draw form widgets during renders.
* Defaults to `false`.
*/
withForms?: boolean;
/**
* Whether to render annotations
* Defaults to `false`.
*/
withAnnotations?: boolean;
}

export interface RenderPageRectOptions {
pageIndex: number;
Expand Down