@@ -162,7 +162,17 @@ export class AnnotationFlattener {
162162 continue ;
163163 }
164164
165- // Skip non-flattenable types
165+ // Handle Link annotations specially
166+ if ( subtype === "Link" ) {
167+ if ( options . removeLinks ) {
168+ // Remove Link annotations for signing security (they contain "hidden behavior")
169+ if ( annotRef ) {
170+ refsToRemove . add ( `${ annotRef . objectNumber } ${ annotRef . generation } ` ) ;
171+ }
172+ }
173+ }
174+
175+ // Skip other non-flattenable types
166176 if ( NON_FLATTENABLE_TYPES . includes ( subtype ) ) {
167177 continue ;
168178 }
@@ -212,12 +222,17 @@ export class AnnotationFlattener {
212222 continue ;
213223 }
214224
215- // Normalize appearance stream
225+ // Normalize appearance stream (only if needed)
216226 this . normalizeAppearanceStream ( appearance ) ;
217227
218- // Add appearance as XObject
228+ // Add appearance as XObject - reuse existing ref if already registered
219229 const xObjectName = `FlatAnnot${ xObjectIndex ++ } ` ;
220- const appearanceRef = this . registry . register ( appearance ) ;
230+ let appearanceRef = this . registry . getRef ( appearance ) ;
231+
232+ if ( ! appearanceRef ) {
233+ appearanceRef = this . registry . register ( appearance ) ;
234+ }
235+
221236 xObjects . set ( xObjectName , appearanceRef ) ;
222237
223238 // Calculate transformation matrix
@@ -267,7 +282,6 @@ export class AnnotationFlattener {
267282 * Generate appearance for annotation types we support.
268283 */
269284 private generateAppearance ( annotation : PDFAnnotation ) : PdfStream | null {
270- const type = annotation . type ;
271285 const rect = annotation . rect ;
272286
273287 // Use instanceof checks for annotation types instead of a switch on `type`
@@ -569,39 +583,78 @@ export class AnnotationFlattener {
569583
570584 /**
571585 * Remove specific annotations from page.
586+ *
587+ * IMPORTANT: If Annots is an indirect reference, we modify the array in-place
588+ * to preserve the indirection. This is critical for signing: if we convert
589+ * an indirect Annots to a direct array, then later signing will convert it
590+ * back to indirect, modifying the page object and potentially breaking
591+ * signature validation.
572592 */
573593 private removeAnnotations ( page : PdfDict , toRemove : Set < string > ) : void {
574594 if ( toRemove . size === 0 ) {
575595 return ;
576596 }
577597
578- let annots = page . get ( "Annots" ) ;
598+ const annotsEntry = page . get ( "Annots" ) ;
599+
600+ if ( ! annotsEntry ) {
601+ return ;
602+ }
603+
604+ // Track if Annots was indirect - we need to preserve this
605+ const wasIndirect = annotsEntry instanceof PdfRef ;
606+ let annots : PdfArray | undefined ;
579607
580- if ( annots instanceof PdfRef ) {
581- annots = this . registry . resolve ( annots ) ?? undefined ;
608+ if ( annotsEntry instanceof PdfRef ) {
609+ const resolved = this . registry . resolve ( annotsEntry ) ;
610+ annots = resolved instanceof PdfArray ? resolved : undefined ;
611+ } else if ( annotsEntry instanceof PdfArray ) {
612+ annots = annotsEntry ;
582613 }
583614
584- if ( ! ( annots instanceof PdfArray ) ) {
615+ if ( ! annots ) {
585616 return ;
586617 }
587618
588- const remaining : PdfRef [ ] = [ ] ;
619+ // Find indices to remove (in reverse order for safe removal)
620+ const indicesToRemove : number [ ] = [ ] ;
589621
590622 for ( let i = 0 ; i < annots . length ; i ++ ) {
591623 const item = annots . at ( i ) ;
592624
593625 if ( item instanceof PdfRef ) {
594626 const key = `${ item . objectNumber } ${ item . generation } ` ;
595627
596- if ( ! toRemove . has ( key ) ) {
597- remaining . push ( item ) ;
628+ if ( toRemove . has ( key ) ) {
629+ indicesToRemove . push ( i ) ;
598630 }
599631 }
600632 }
601633
602- if ( remaining . length === 0 ) {
603- page . delete ( "Annots" ) ;
604- } else if ( remaining . length < annots . length ) {
634+ if ( indicesToRemove . length === 0 ) {
635+ return ;
636+ }
637+
638+ // If Annots was indirect, modify in-place to preserve indirection
639+ if ( wasIndirect ) {
640+ // Remove in reverse order to maintain valid indices
641+ for ( let i = indicesToRemove . length - 1 ; i >= 0 ; i -- ) {
642+ annots . remove ( indicesToRemove [ i ] ) ;
643+ }
644+ } else {
645+ // Annots was direct - build a new array with remaining items
646+ const remaining : PdfRef [ ] = [ ] ;
647+
648+ for ( let i = 0 ; i < annots . length ; i ++ ) {
649+ if ( ! indicesToRemove . includes ( i ) ) {
650+ const item = annots . at ( i ) ;
651+
652+ if ( item instanceof PdfRef ) {
653+ remaining . push ( item ) ;
654+ }
655+ }
656+ }
657+
605658 page . set ( "Annots" , PdfArray . of ( ...remaining ) ) ;
606659 }
607660 }
0 commit comments