@@ -18,6 +18,9 @@ use weezl::{decode::Decoder as LzwDecoder, BitOrder, LzwError, LzwStatus};
1818
1919/// GIF palettes are RGB
2020pub const PLTE_CHANNELS : usize = 3 ;
21+ /// Headers for supported extensions.
22+ const EXT_NAME_NETSCAPE : & [ u8 ] = b"\x0b NETSCAPE2.0\x03 " ;
23+ const EXT_NAME_XMP : & [ u8 ] = b"\x0b XMP DataXMP" ;
2124
2225/// An error returned in the case of the image not being formatted properly.
2326#[ derive( Debug ) ]
@@ -377,6 +380,8 @@ pub struct StreamingDecoder {
377380 current : Option < Frame < ' static > > ,
378381 /// Needs to emit `HeaderEnd` once
379382 header_end_reached : bool ,
383+ /// XMP metadata bytes.
384+ xmp_metadata : Option < Vec < u8 > > ,
380385}
381386
382387/// One version number of the GIF standard.
@@ -464,6 +469,7 @@ impl StreamingDecoder {
464469 } ,
465470 current : None ,
466471 header_end_reached : false ,
472+ xmp_metadata : None ,
467473 }
468474 }
469475
@@ -530,6 +536,12 @@ impl StreamingDecoder {
530536 self . height
531537 }
532538
539+ /// XMP metadata stored in the image.
540+ #[ must_use]
541+ pub fn xmp_metadata ( & self ) -> Option < & [ u8 ] > {
542+ self . xmp_metadata . as_deref ( )
543+ }
544+
533545 /// The version number of the GIF standard used in this image.
534546 ///
535547 /// We suppose a minimum of `V87a` compatibility. This value will be reported until we have
@@ -754,17 +766,36 @@ impl StreamingDecoder {
754766 }
755767 }
756768 } else {
769+ self . ext . data . push ( b) ;
757770 self . ext . is_block_end = false ;
758771 goto ! ( ExtensionDataBlock ( b as usize ) , emit Decoded :: SubBlockFinished ( self . ext. id) )
759772 }
760773 }
761774 ApplicationExtension => {
762775 debug_assert_eq ! ( 0 , b) ;
776+ // We can consume the extension data here as the next states won't access it anymore.
777+ let mut data = std:: mem:: take ( & mut self . ext . data ) ;
778+
763779 // the parser removes sub-block lenghts, so app name and data are concatenated
764- if self . ext . data . len ( ) >= 15 && & self . ext . data [ 1 ..13 ] == b"NETSCAPE2.0\x01 " {
765- let repeat = & self . ext . data [ 13 ..15 ] ;
766- let repeat = u16:: from ( repeat[ 0 ] ) | u16:: from ( repeat[ 1 ] ) << 8 ;
780+ if let Some ( & [ first, second, ..] ) = data. strip_prefix ( EXT_NAME_NETSCAPE ) {
781+ let repeat = u16:: from ( first) | u16:: from ( second) << 8 ;
767782 goto ! ( BlockEnd , emit Decoded :: Repetitions ( if repeat == 0 { Repeat :: Infinite } else { Repeat :: Finite ( repeat) } ) )
783+ } else if data. starts_with ( EXT_NAME_XMP ) {
784+ data. drain ( ..EXT_NAME_XMP . len ( ) ) ;
785+ // XMP adds a "ramp" of 257 bytes to the end of the metadata to let the "pascal-strings"
786+ // parser converge to the null byte. The ramp looks like "0x01, 0xff, .., 0x01, 0x00".
787+ // For convenience and to allow consumers to not be bothered with this implementation detail,
788+ // we cut the ramp.
789+ const RAMP_SIZE : usize = 257 ;
790+ if data. len ( ) >= RAMP_SIZE
791+ && data. ends_with ( & [ 0x03 , 0x02 , 0x01 , 0x00 ] )
792+ && data[ data. len ( ) - RAMP_SIZE ..] . starts_with ( & [ 0x01 , 0x0ff ] )
793+ {
794+ data. truncate ( data. len ( ) - RAMP_SIZE ) ;
795+ }
796+
797+ self . xmp_metadata = Some ( data) ;
798+ goto ! ( BlockEnd )
768799 } else {
769800 goto ! ( BlockEnd )
770801 }
0 commit comments