@@ -40,6 +40,7 @@ interface ObfButton {
4040 border_color ?: string ;
4141 semantic_id ?: string ; // Optional semantic identifier for motor planning
4242 hidden ?: boolean ; // OBF uses boolean hidden field
43+ image_id ?: string ; // Reference to image in the images array
4344}
4445
4546/**
@@ -74,9 +75,84 @@ interface ObfBoard {
7475}
7576
7677class ObfProcessor extends BaseProcessor {
78+ private zipFile ?: AdmZip ;
79+ private imageCache : Map < string , string > = new Map ( ) ; // Cache for data URLs
80+
7781 constructor ( options ?: ProcessorOptions ) {
7882 super ( options ) ;
7983 }
84+
85+ /**
86+ * Extract an image from the ZIP file and convert to data URL
87+ */
88+ private extractImageAsDataUrl ( imageId : string , images : any [ ] ) : string | null {
89+ // Check cache first
90+ if ( this . imageCache . has ( imageId ) ) {
91+ return this . imageCache . get ( imageId ) ! ;
92+ }
93+
94+ if ( ! this . zipFile || ! images ) {
95+ return null ;
96+ }
97+
98+ // Find the image metadata
99+ const imageData = images . find ( ( img : any ) => img . id === imageId ) ;
100+ if ( ! imageData ) {
101+ return null ;
102+ }
103+
104+ // Try to get the image file from the ZIP
105+ // Images are typically stored in an 'images' folder or root
106+ const possiblePaths = [
107+ imageData . path , // Explicit path if provided
108+ `images/${ imageData . filename || imageId } ` , // Standard images folder
109+ imageData . id , // Just the ID
110+ ] . filter ( Boolean ) ;
111+
112+ for ( const imagePath of possiblePaths ) {
113+ try {
114+ const entry = this . zipFile . getEntry ( imagePath ) ;
115+ if ( entry ) {
116+ const buffer = entry . getData ( ) ;
117+ const contentType = imageData . content_type || this . getMimeTypeFromFilename ( imagePath ) ;
118+ const dataUrl = `data:${ contentType } ;base64,${ buffer . toString ( 'base64' ) } ` ;
119+ this . imageCache . set ( imageId , dataUrl ) ;
120+ return dataUrl ;
121+ }
122+ } catch ( err ) {
123+ // Continue to next path
124+ continue ;
125+ }
126+ }
127+
128+ // If image has a URL, use that as fallback
129+ if ( imageData . url ) {
130+ this . imageCache . set ( imageId , imageData . url ) ;
131+ return imageData . url ;
132+ }
133+
134+ return null ;
135+ }
136+
137+ private getMimeTypeFromFilename ( filename : string ) : string {
138+ const ext = filename . toLowerCase ( ) . split ( '.' ) . pop ( ) ;
139+ switch ( ext ) {
140+ case 'png' :
141+ return 'image/png' ;
142+ case 'jpg' :
143+ case 'jpeg' :
144+ return 'image/jpeg' ;
145+ case 'gif' :
146+ return 'image/gif' ;
147+ case 'svg' :
148+ return 'image/svg+xml' ;
149+ case 'webp' :
150+ return 'image/webp' ;
151+ default :
152+ return 'image/png' ;
153+ }
154+ }
155+
80156 private processBoard ( boardData : ObfBoard , _boardPath : string ) : AACPage {
81157 const sourceButtons = boardData . buttons || [ ] ;
82158
@@ -109,6 +185,12 @@ class ObfProcessor extends BaseProcessor {
109185 } ,
110186 } ;
111187
188+ // Resolve image if image_id is present
189+ let resolvedImage : string | undefined ;
190+ if ( btn . image_id && boardData . images ) {
191+ resolvedImage = this . extractImageAsDataUrl ( btn . image_id , boardData . images ) || undefined ;
192+ }
193+
112194 return new AACButton ( {
113195 // Make button ID unique by combining page ID and button ID
114196 id : `${ pageId } ::${ btn ?. id || '' } ` ,
@@ -119,6 +201,8 @@ class ObfProcessor extends BaseProcessor {
119201 backgroundColor : btn . background_color ,
120202 borderColor : btn . border_color ,
121203 } ,
204+ image : resolvedImage , // Set the resolved image data URL
205+ resolvedImageEntry : resolvedImage ,
122206 semanticAction,
123207 targetPageId : btn . load_board ?. path ,
124208 semantic_id : btn . semantic_id , // Extract semantic_id if present
@@ -345,6 +429,11 @@ class ObfProcessor extends BaseProcessor {
345429 console . error ( '[OBF] Error instantiating AdmZip with input:' , err ) ;
346430 throw err ;
347431 }
432+
433+ // Store the ZIP file reference for image extraction
434+ this . zipFile = zip ;
435+ this . imageCache . clear ( ) ; // Clear cache for new file
436+
348437 console . log ( '[OBF] Detected zip archive, extracting .obf files' ) ;
349438 zip . getEntries ( ) . forEach ( ( entry ) => {
350439 if ( entry . entryName . endsWith ( '.obf' ) ) {
0 commit comments