feat(annotation): add moveAnnotation API for efficient repositioning of annotations, and bump embed to 2.7.0 (#5809)

This commit is contained in:
Balázs Szücs
2026-03-01 16:45:44 +01:00
committed by GitHub
parent 13d7ee7496
commit c244edf8b7
5 changed files with 222 additions and 195 deletions

View File

@@ -7,6 +7,7 @@ import type {
AnnotationAPI,
AnnotationEvent,
AnnotationPatch,
AnnotationRect,
} from '@app/components/viewer/viewerTypes';
import { useDocumentReady } from '@app/components/viewer/hooks/useDocumentReady';
@@ -90,6 +91,8 @@ type AnnotationApiSurface = {
updateAnnotation?: (pageIndex: number, annotationId: string, patch: AnnotationPatch) => void;
onAnnotationEvent?: (listener: (event: AnnotationEvent) => void) => void | (() => void);
purgeAnnotation?: (pageIndex: number, annotationId: string) => void;
/** v2.7.0: move annotation without regenerating its appearance stream */
moveAnnotation?: (pageIndex: number, annotationId: string, newRect: AnnotationRect) => void;
};
type ToolDefaultsBuilder = (options?: AnnotationToolOptions) => AnnotationDefaults;
@@ -301,7 +304,7 @@ export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(function Annotation
const configureAnnotationTool = useCallback(
(toolId: AnnotationToolId, options?: AnnotationToolOptions) => {
const api = annotationApi as AnnotationApiSurface | undefined;
const api = annotationApi as unknown as AnnotationApiSurface | undefined;
if (!api?.setActiveTool) return;
const defaults = buildAnnotationDefaults(toolId, options);
@@ -328,13 +331,13 @@ export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(function Annotation
isReady: () => !!annotationApi && documentReady,
setAnnotationStyle: (toolId: AnnotationToolId, options?: AnnotationToolOptions) => {
const defaults = buildAnnotationDefaults(toolId, options);
const api = annotationApi as AnnotationApiSurface | undefined;
const api = annotationApi as unknown as AnnotationApiSurface | undefined;
if (defaults && api?.setToolDefaults) {
api.setToolDefaults(toolId, defaults);
}
},
getSelectedAnnotation: () => {
const api = annotationApi as AnnotationApiSurface | undefined;
const api = annotationApi as unknown as AnnotationApiSurface | undefined;
if (!api?.getSelectedAnnotation) {
return null;
}
@@ -354,33 +357,38 @@ export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(function Annotation
}
},
deselectAnnotation: () => {
const api = annotationApi as AnnotationApiSurface | undefined;
const api = annotationApi as unknown as AnnotationApiSurface | undefined;
api?.deselectAnnotation?.();
},
updateAnnotation: (pageIndex: number, annotationId: string, patch: AnnotationPatch) => {
const api = annotationApi as AnnotationApiSurface | undefined;
const api = annotationApi as unknown as AnnotationApiSurface | undefined;
api?.updateAnnotation?.(pageIndex, annotationId, patch);
},
deactivateTools: () => {
const api = annotationApi as AnnotationApiSurface | undefined;
const api = annotationApi as unknown as AnnotationApiSurface | undefined;
api?.setActiveTool?.(null);
},
onAnnotationEvent: (listener: (event: AnnotationEvent) => void) => {
const api = annotationApi as AnnotationApiSurface | undefined;
const api = annotationApi as unknown as AnnotationApiSurface | undefined;
if (api?.onAnnotationEvent) {
return api.onAnnotationEvent(listener);
}
return undefined;
},
getActiveTool: () => {
const api = annotationApi as AnnotationApiSurface | undefined;
const api = annotationApi as unknown as AnnotationApiSurface | undefined;
return api?.getActiveTool?.() ?? null;
},
purgeAnnotation: (pageIndex: number, annotationId: string) => {
const api = annotationApi as AnnotationApiSurface | undefined;
const api = annotationApi as unknown as AnnotationApiSurface | undefined;
api?.purgeAnnotation?.(pageIndex, annotationId);
},
moveAnnotation: (pageIndex: number, annotationId: string, newRect: AnnotationRect) => {
const api = annotationApi as unknown as AnnotationApiSurface | undefined;
api?.moveAnnotation?.(pageIndex, annotationId, newRect);
},
}),
[annotationApi, configureAnnotationTool, buildAnnotationDefaults]
);

View File

@@ -2,7 +2,7 @@ import { useImperativeHandle, forwardRef, useEffect, useCallback, useRef, useSta
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
import { PdfAnnotationSubtype, uuidV4 } from '@embedpdf/models';
import { useSignature } from '@app/contexts/SignatureContext';
import type { SignatureAPI } from '@app/components/viewer/viewerTypes';
import type { SignatureAPI, AnnotationRect } from '@app/components/viewer/viewerTypes';
import type { SignParameters } from '@app/hooks/tools/sign/useSignParameters';
import { useViewer } from '@app/contexts/ViewerContext';
import { useDocumentReady } from '@app/components/viewer/hooks/useDocumentReady';
@@ -394,6 +394,13 @@ export const SignatureAPIBridge = forwardRef<SignatureAPI>(function SignatureAPI
return [];
}
},
moveAnnotation: (pageIndex: number, annotationId: string, newRect: AnnotationRect) => {
if (!annotationApi) return;
// v2.7.0: move signature stamp to newRect without regenerating the AP stream,
// preserving the original appearance (image data stays intact).
(annotationApi as any).moveAnnotation?.(pageIndex, annotationId, newRect);
},
}), [annotationApi, signatureConfig, placementPreviewSize, applyStampDefaults]);
useEffect(() => {

View File

@@ -1,3 +1,8 @@
export interface AnnotationRect {
origin: { x: number; y: number };
size: { width: number; height: number };
}
export interface SignatureAPI {
addImageSignature: (
signatureData: string,
@@ -14,6 +19,7 @@ export interface SignatureAPI {
updateDrawSettings: (color: string, size: number) => void;
deactivateTools: () => void;
getPageAnnotations: (pageIndex: number) => Promise<any[]>;
moveAnnotation?: (pageIndex: number, annotationId: string, newRect: AnnotationRect) => void;
}
export interface AnnotationAPI {
@@ -26,6 +32,12 @@ export interface AnnotationAPI {
onAnnotationEvent?: (listener: (event: AnnotationEvent) => void) => void | (() => void);
getActiveTool?: () => { id: AnnotationToolId } | null;
purgeAnnotation?: (pageIndex: number, annotationId: string) => void;
/**
* Move an annotation to a new position without regenerating its appearance stream.
* Uses the embedPDF v2.7.0 moveAnnotation API for efficient repositioning of annotations
* that have existing AP streams (e.g. stamps, signatures).
*/
moveAnnotation?: (pageIndex: number, annotationId: string, newRect: AnnotationRect) => void;
}
export interface HistoryAPI {