55
66use crate :: common:: error:: Result ;
77use crate :: images:: { Blip , BlipStore , BlipStoreEntry } ;
8+ use crate :: ole:: escher:: EscherRecord ;
89use crate :: ole:: ppt:: escher:: { EscherContainer , EscherParser , EscherRecordType } ;
910use std:: borrow:: Cow ;
1011
@@ -208,6 +209,112 @@ impl ImageExtractor {
208209 Ok ( store)
209210 }
210211
212+ /// Extract BLIP from BSE record, handling both embedded and delay-loaded cases.
213+ ///
214+ /// # Arguments
215+ /// * `bse` - Parsed BSE record
216+ /// * `record_data` - Raw BSE record data (without Escher header)
217+ /// * `delay_stream` - Optional data stream for delay-loaded BLIPs
218+ ///
219+ /// # Returns
220+ /// Owned BLIP data
221+ fn blip_from_bse (
222+ bse : & BlipStoreEntry < ' _ > ,
223+ bse_record : & EscherRecord < ' _ > ,
224+ delay_stream : Option < & [ u8 ] > ,
225+ ) -> Result < Blip < ' static > > {
226+ let is_in_delayed_stream = bse. is_delay_loaded ( ) && bse_record. length <= 36 ;
227+ let blip_data = if is_in_delayed_stream {
228+ let stream = delay_stream. ok_or_else ( || {
229+ crate :: common:: error:: Error :: ParseError (
230+ "BSE record is delay-loaded but no data stream was provided" . into ( ) ,
231+ )
232+ } ) ?;
233+ let offset = bse. offset as usize ;
234+ if offset >= stream. len ( ) {
235+ return Err ( crate :: common:: error:: Error :: ParseError (
236+ "BSE delay stream offset is out of bounds" . into ( ) ,
237+ ) ) ;
238+ }
239+ & stream[ offset..]
240+ } else {
241+ if bse_record. data . len ( ) <= 36 {
242+ return Err ( crate :: common:: error:: Error :: ParseError (
243+ "BSE record data is too short for embedded BLIP" . into ( ) ,
244+ ) ) ;
245+ }
246+ & bse_record. data [ 36 ..]
247+ } ;
248+
249+ if blip_data. len ( ) < 8 {
250+ return Err ( crate :: common:: error:: Error :: ParseError (
251+ "Insufficient data for BLIP" . into ( ) ,
252+ ) ) ;
253+ }
254+
255+ Blip :: parse ( blip_data) . map ( |b| b. into_owned ( ) )
256+ }
257+
258+ /// Extract image from a single Escher record
259+ ///
260+ /// This method handles both BSE (0xF007) and BLIP type records directly.
261+ /// For BSE records, it extracts the embedded BLIP data.
262+ /// For BLIP records (0xF01A-0xF029), it parses the image directly.
263+ ///
264+ /// # Arguments
265+ /// * `record` - An Escher record that is either BSE or a BLIP type
266+ ///
267+ /// # Returns
268+ /// ExtractedImage if the record contains valid image data
269+ pub fn extract_from_escher_record (
270+ record : & EscherRecord < ' _ > ,
271+ ) -> Result < ExtractedImage < ' static > > {
272+ Self :: extract_from_escher_record_with_stream ( record, None )
273+ }
274+
275+ /// Extract image from a single Escher record with optional data stream.
276+ ///
277+ /// This method handles both BSE (0xF007) and BLIP type records directly.
278+ /// For BSE records with delay-loaded BLIPs (offset != 0), the delay_stream
279+ /// parameter must be provided to locate the BLIP data.
280+ ///
281+ /// # Arguments
282+ /// * `record` - An Escher record that is either BSE or a BLIP type
283+ /// * `delay_stream` - Optional main stream for delay-loaded BLIPs
284+ ///
285+ /// # Returns
286+ /// ExtractedImage if the record contains valid image data
287+ pub fn extract_from_escher_record_with_stream (
288+ record : & EscherRecord < ' _ > ,
289+ delay_stream : Option < & [ u8 ] > ,
290+ ) -> Result < ExtractedImage < ' static > > {
291+ match record. record_type {
292+ // BLIP type records - parse directly
293+ EscherRecordType :: BlipEmf
294+ | EscherRecordType :: BlipWmf
295+ | EscherRecordType :: BlipPict
296+ | EscherRecordType :: BlipJpeg
297+ | EscherRecordType :: BlipPng
298+ | EscherRecordType :: BlipDib
299+ | EscherRecordType :: BlipTiff => {
300+ let blip = Blip :: try_from_escher_record ( record) ?;
301+ Ok ( ExtractedImage :: new ( blip, None , 0 ) )
302+ } ,
303+
304+ // BSE record - extract embedded or delay-loaded BLIP
305+ EscherRecordType :: BSE => {
306+ let bse = BlipStoreEntry :: parse ( record. data ) ?;
307+ let name = bse. name . as_ref ( ) . map ( |n| n. to_string ( ) ) ;
308+ let blip = Self :: blip_from_bse ( & bse, record, delay_stream) ?;
309+ Ok ( ExtractedImage :: new ( blip, name, 0 ) )
310+ } ,
311+ _ => Err ( crate :: common:: error:: Error :: ParseError ( format ! (
312+ "Record type 0x{:04X} is not a supported image record" ,
313+ record. record_type_raw
314+ ) ) ) ,
315+ }
316+ }
317+
211318 /// Extract all BLIPs from Escher drawing data
212319 ///
213320 /// This extracts actual BLIP records (image data) from the drawing layer.
@@ -361,40 +468,48 @@ impl ImageExtractor {
361468 /// part of a document (e.g., a specific slide in PPT).
362469 pub fn extract_from_container (
363470 container : & EscherContainer ,
471+ ) -> Result < Vec < ExtractedImage < ' static > > > {
472+ Self :: extract_from_container_with_stream ( container, None )
473+ }
474+
475+ /// Extract images from a specific Escher container with optional data stream.
476+ ///
477+ /// # Arguments
478+ /// * `container` - The Escher container to extract from
479+ /// * `delay_stream` - Optional data stream for delay-loaded BLIPs
480+ pub fn extract_from_container_with_stream (
481+ container : & EscherContainer ,
482+ delay_stream : Option < & [ u8 ] > ,
364483 ) -> Result < Vec < ExtractedImage < ' static > > > {
365484 let mut images = Vec :: new ( ) ;
366485 let mut index = 0 ;
367486
368487 // Recursively search for BLIP records
369- Self :: extract_from_container_recursive ( container, & mut images, & mut index) ?;
488+ Self :: extract_from_container_recursive_with_stream (
489+ container,
490+ & mut images,
491+ & mut index,
492+ delay_stream,
493+ ) ?;
370494
371495 Ok ( images)
372496 }
373497
374- /// Recursively extract BLIPs from a container and its children
375- fn extract_from_container_recursive (
498+ /// Recursively extract BLIPs from a container with optional data stream.
499+ fn extract_from_container_recursive_with_stream (
376500 container : & EscherContainer ,
377501 images : & mut Vec < ExtractedImage < ' static > > ,
378502 index : & mut usize ,
503+ delay_stream : Option < & [ u8 ] > ,
379504 ) -> Result < ( ) > {
380505 // Check if this container has BLIP records
381506 for child_result in container. children ( ) {
382507 let child = match child_result {
383508 Ok ( c) => c,
384509 Err ( _) => continue , // Skip invalid records
385510 } ;
386- let is_blip = matches ! (
387- child. record_type,
388- EscherRecordType :: BlipEmf
389- | EscherRecordType :: BlipWmf
390- | EscherRecordType :: BlipPict
391- | EscherRecordType :: BlipJpeg
392- | EscherRecordType :: BlipPng
393- | EscherRecordType :: BlipDib
394- | EscherRecordType :: BlipTiff
395- ) ;
396511
397- if is_blip {
512+ if child . record_type . is_blip ( ) {
398513 // Reconstruct full BLIP record
399514 let mut full_data = Vec :: with_capacity ( 8 + child. data . len ( ) ) ;
400515 let ver_inst = ( child. instance << 4 ) | ( child. version as u16 ) ;
@@ -412,7 +527,31 @@ impl ImageExtractor {
412527 } else if child. is_container ( ) {
413528 // Recurse into child containers
414529 let child_container = EscherContainer :: new ( child) ;
415- Self :: extract_from_container_recursive ( & child_container, images, index) ?;
530+ Self :: extract_from_container_recursive_with_stream (
531+ & child_container,
532+ images,
533+ index,
534+ delay_stream,
535+ ) ?;
536+ } else if child. record_type == EscherRecordType :: BSE {
537+ // BSE records can contain embedded or delay-loaded BLIP data
538+ match BlipStoreEntry :: parse ( child. data ) {
539+ Ok ( bse) => {
540+ let name = bse. name . as_ref ( ) . map ( |n| n. to_string ( ) ) ;
541+ match Self :: blip_from_bse ( & bse, & child, delay_stream) {
542+ Ok ( blip) => {
543+ images. push ( ExtractedImage :: new ( blip, name, * index) ) ;
544+ * index += 1 ;
545+ } ,
546+ Err ( _) => {
547+ continue ;
548+ } ,
549+ }
550+ } ,
551+ Err ( _) => {
552+ continue ;
553+ } ,
554+ }
416555 }
417556 }
418557
0 commit comments