@@ -113,6 +113,12 @@ import {
113113 PdfTrappedStatus ,
114114 PdfStampFit ,
115115 PdfAddAttachmentParams ,
116+ AnnotationAppearanceMap ,
117+ AnnotationAppearances ,
118+ AnnotationAppearanceImage ,
119+ AP_MODE_NORMAL ,
120+ AP_MODE_ROLLOVER ,
121+ AP_MODE_DOWN ,
116122} from '@embedpdf/models' ;
117123import { computeFormDrawParams , isValidCustomKey , readArrayBuffer , readString } from './helper' ;
118124import { WrappedPdfiumModule } from '@embedpdf/pdfium' ;
@@ -4147,6 +4153,12 @@ export class PdfiumNative implements IPdfiumExecutor {
41474153 // Post-process: reverse-rotate vertices for vertex types that have rotation metadata
41484154 if ( annotation ) {
41494155 annotation = this . reverseRotateAnnotationOnLoad ( annotation ) ;
4156+
4157+ // Populate available appearance stream modes bitmask
4158+ const apModes = this . pdfiumModule . EPDFAnnot_GetAvailableAppearanceModes ( annotationPtr ) ;
4159+ if ( apModes ) {
4160+ annotation . appearanceModes = apModes ;
4161+ }
41504162 }
41514163
41524164 return annotation ;
@@ -7491,6 +7503,194 @@ export class PdfiumNative implements IPdfiumExecutor {
74917503 return task ;
74927504 }
74937505
7506+ /**
7507+ * Batch-render all annotation appearance streams for a page in one call.
7508+ * Returns a map of annotation ID -> rendered appearances (Normal/Rollover/Down).
7509+ * Skips annotations that have rotation + unrotatedRect (EmbedPDF-rotated)
7510+ * and annotations without any appearance stream.
7511+ *
7512+ * @public
7513+ */
7514+ renderPageAnnotationsRaw (
7515+ doc : PdfDocumentObject ,
7516+ page : PdfPageObject ,
7517+ options ?: PdfRenderPageAnnotationOptions ,
7518+ ) : PdfTask < AnnotationAppearanceMap > {
7519+ const { scaleFactor = 1 , rotation = Rotation . Degree0 , dpr = 1 } = options ?? { } ;
7520+
7521+ this . logger . debug ( LOG_SOURCE , LOG_CATEGORY , 'renderPageAnnotationsRaw' , doc , page , options ) ;
7522+ this . logger . perf (
7523+ LOG_SOURCE ,
7524+ LOG_CATEGORY ,
7525+ 'RenderPageAnnotationsRaw' ,
7526+ 'Begin' ,
7527+ `${ doc . id } -${ page . index } ` ,
7528+ ) ;
7529+
7530+ const ctx = this . cache . getContext ( doc . id ) ;
7531+ if ( ! ctx ) {
7532+ this . logger . perf (
7533+ LOG_SOURCE ,
7534+ LOG_CATEGORY ,
7535+ 'RenderPageAnnotationsRaw' ,
7536+ 'End' ,
7537+ `${ doc . id } -${ page . index } ` ,
7538+ ) ;
7539+ return PdfTaskHelper . reject ( {
7540+ code : PdfErrorCode . DocNotOpen ,
7541+ message : 'document does not open' ,
7542+ } ) ;
7543+ }
7544+
7545+ const pageCtx = ctx . acquirePage ( page . index ) ;
7546+ const result : AnnotationAppearanceMap = { } ;
7547+ const finalScale = Math . max ( 0.01 , scaleFactor * dpr ) ;
7548+ const annotCount = this . pdfiumModule . FPDFPage_GetAnnotCount ( pageCtx . pagePtr ) ;
7549+
7550+ for ( let i = 0 ; i < annotCount ; i ++ ) {
7551+ const annotPtr = this . pdfiumModule . FPDFPage_GetAnnot ( pageCtx . pagePtr , i ) ;
7552+ if ( ! annotPtr ) continue ;
7553+
7554+ try {
7555+ // Read annotation NM (id)
7556+ const nm = this . getAnnotString ( annotPtr , 'NM' ) ;
7557+ if ( ! nm ) continue ;
7558+
7559+ // Skip EmbedPDF-rotated annotations (have rotation + unrotatedRect)
7560+ const extRotation = this . getAnnotExtendedRotation ( annotPtr ) ;
7561+ if ( extRotation !== 0 ) {
7562+ const unrotatedRaw = this . readAnnotUnrotatedRect ( annotPtr ) ;
7563+ if ( unrotatedRaw ) continue ;
7564+ }
7565+
7566+ // Detect available AP modes
7567+ const apModes = this . pdfiumModule . EPDFAnnot_GetAvailableAppearanceModes ( annotPtr ) ;
7568+ if ( ! apModes ) continue ;
7569+
7570+ const appearances : AnnotationAppearances = { } ;
7571+
7572+ // Render each available mode
7573+ const modesToRender : Array < {
7574+ bit : number ;
7575+ mode : AppearanceMode ;
7576+ key : keyof AnnotationAppearances ;
7577+ } > = [
7578+ { bit : AP_MODE_NORMAL , mode : AppearanceMode . Normal , key : 'normal' } ,
7579+ { bit : AP_MODE_ROLLOVER , mode : AppearanceMode . Rollover , key : 'rollover' } ,
7580+ { bit : AP_MODE_DOWN , mode : AppearanceMode . Down , key : 'down' } ,
7581+ ] ;
7582+
7583+ for ( const { bit, mode, key } of modesToRender ) {
7584+ if ( ! ( apModes & bit ) ) continue ;
7585+
7586+ const rendered = this . renderSingleAnnotAppearance (
7587+ doc ,
7588+ page ,
7589+ pageCtx ,
7590+ annotPtr ,
7591+ mode ,
7592+ rotation ,
7593+ finalScale ,
7594+ ) ;
7595+ if ( rendered ) {
7596+ appearances [ key ] = rendered ;
7597+ }
7598+ }
7599+
7600+ if ( appearances . normal || appearances . rollover || appearances . down ) {
7601+ result [ nm ] = appearances ;
7602+ }
7603+ } finally {
7604+ this . pdfiumModule . FPDFPage_CloseAnnot ( annotPtr ) ;
7605+ }
7606+ }
7607+
7608+ pageCtx . release ( ) ;
7609+ this . logger . perf (
7610+ LOG_SOURCE ,
7611+ LOG_CATEGORY ,
7612+ 'RenderPageAnnotationsRaw' ,
7613+ 'End' ,
7614+ `${ doc . id } -${ page . index } ` ,
7615+ ) ;
7616+
7617+ const task = new Task < AnnotationAppearanceMap , PdfErrorReason > ( ) ;
7618+ task . resolve ( result ) ;
7619+ return task ;
7620+ }
7621+
7622+ /**
7623+ * Render a single annotation's appearance for a given mode.
7624+ * Returns the image data and rect, or null on failure.
7625+ * @private
7626+ */
7627+ private renderSingleAnnotAppearance (
7628+ doc : PdfDocumentObject ,
7629+ page : PdfPageObject ,
7630+ pageCtx : PageContext ,
7631+ annotPtr : number ,
7632+ mode : AppearanceMode ,
7633+ rotation : Rotation ,
7634+ finalScale : number ,
7635+ ) : AnnotationAppearanceImage | null {
7636+ // Read rect using EPDFAnnot_GetRect (normalized) and convert to device coords
7637+ const pageRect = this . readPageAnnoRect ( annotPtr ) ;
7638+ const annotRect = this . convertPageRectToDeviceRect ( doc , page , pageRect ) ;
7639+
7640+ const rect = toIntRect ( annotRect ) ;
7641+ const devRect = toIntRect ( transformRect ( page . size , rect , rotation , finalScale ) ) ;
7642+ const wDev = Math . max ( 1 , devRect . size . width ) ;
7643+ const hDev = Math . max ( 1 , devRect . size . height ) ;
7644+ const stride = wDev * 4 ;
7645+ const bytes = stride * hDev ;
7646+
7647+ const heapPtr = this . memoryManager . malloc ( bytes ) ;
7648+ const bitmapPtr = this . pdfiumModule . FPDFBitmap_CreateEx (
7649+ wDev ,
7650+ hDev ,
7651+ BitmapFormat . Bitmap_BGRA ,
7652+ heapPtr ,
7653+ stride ,
7654+ ) ;
7655+ this . pdfiumModule . FPDFBitmap_FillRect ( bitmapPtr , 0 , 0 , wDev , hDev , 0x00000000 ) ;
7656+
7657+ const M = buildUserToDeviceMatrix ( rect , rotation , wDev , hDev ) ;
7658+ const mPtr = this . memoryManager . malloc ( 6 * 4 ) ;
7659+ const mView = new Float32Array ( this . pdfiumModule . pdfium . HEAPF32 . buffer , mPtr , 6 ) ;
7660+ mView . set ( [ M . a , M . b , M . c , M . d , M . e , M . f ] ) ;
7661+
7662+ const FLAGS = RenderFlag . REVERSE_BYTE_ORDER ;
7663+ let ok = false ;
7664+ try {
7665+ ok = ! ! this . pdfiumModule . EPDF_RenderAnnotBitmap (
7666+ bitmapPtr ,
7667+ pageCtx . pagePtr ,
7668+ annotPtr ,
7669+ mode ,
7670+ mPtr ,
7671+ FLAGS ,
7672+ ) ;
7673+ } finally {
7674+ this . memoryManager . free ( mPtr ) ;
7675+ this . pdfiumModule . FPDFBitmap_Destroy ( bitmapPtr ) ;
7676+ }
7677+
7678+ if ( ! ok ) {
7679+ this . memoryManager . free ( heapPtr ) ;
7680+ return null ;
7681+ }
7682+
7683+ const data = this . pdfiumModule . pdfium . HEAPU8 . subarray ( heapPtr , heapPtr + bytes ) ;
7684+ const imageData : ImageDataLike = {
7685+ data : new Uint8ClampedArray ( data ) ,
7686+ width : wDev ,
7687+ height : hDev ,
7688+ } ;
7689+ this . memoryManager . free ( heapPtr ) ;
7690+
7691+ return { data : imageData , rect : annotRect } ;
7692+ }
7693+
74947694 private renderRectEncoded (
74957695 doc : PdfDocumentObject ,
74967696 page : PdfPageObject ,
0 commit comments