1use crate::tiff_types::ByteOrder;
14use crate::types::{BinaryDataFormat, BinaryDataTable, BinaryDataTag, ExifError, Result, TagValue};
15use std::collections::HashMap;
16use tracing::debug;
17
18#[derive(Debug, Clone)]
21pub struct CanonCameraSettingsTag {
22 pub index: u32,
24 pub name: String,
26 pub print_conv: Option<HashMap<i16, String>>,
28}
29
30pub fn create_camera_settings_table() -> HashMap<u32, CanonCameraSettingsTag> {
33 let mut table = HashMap::new();
34
35 table.insert(
37 1,
38 CanonCameraSettingsTag {
39 index: 1,
40 name: "MacroMode".to_string(),
41 print_conv: {
42 let mut conv = HashMap::new();
43 conv.insert(1i16, "Macro".to_string());
44 conv.insert(2i16, "Normal".to_string());
45 Some(conv)
46 },
47 },
48 );
49
50 table.insert(
52 2,
53 CanonCameraSettingsTag {
54 index: 2,
55 name: "SelfTimer".to_string(),
56 print_conv: {
57 let mut conv = HashMap::new();
61 conv.insert(0i16, "Off".to_string());
62 Some(conv)
63 },
64 },
65 );
66
67 table.insert(
69 3,
70 CanonCameraSettingsTag {
71 index: 3,
72 name: "Quality".to_string(),
73 print_conv: {
74 None },
78 },
79 );
80
81 table.insert(
83 4,
84 CanonCameraSettingsTag {
85 index: 4,
86 name: "CanonFlashMode".to_string(),
87 print_conv: {
88 let mut conv = HashMap::new();
89 conv.insert(-1i16, "n/a".to_string()); conv.insert(0i16, "Off".to_string());
91 conv.insert(1i16, "Auto".to_string());
92 conv.insert(2i16, "On".to_string());
93 conv.insert(3i16, "Red-eye reduction".to_string());
94 conv.insert(4i16, "Slow-sync".to_string());
95 conv.insert(5i16, "Red-eye reduction (Auto)".to_string());
96 conv.insert(6i16, "Red-eye reduction (On)".to_string());
97 conv.insert(16i16, "External flash".to_string()); Some(conv)
99 },
100 },
101 );
102
103 table.insert(
105 5,
106 CanonCameraSettingsTag {
107 index: 5,
108 name: "ContinuousDrive".to_string(),
109 print_conv: {
110 let mut conv = HashMap::new();
111 conv.insert(0i16, "Single".to_string());
112 conv.insert(1i16, "Continuous".to_string());
113 conv.insert(2i16, "Movie".to_string()); conv.insert(3i16, "Continuous, Speed Priority".to_string()); Some(conv)
116 },
117 },
118 );
119
120 table.insert(
122 7,
123 CanonCameraSettingsTag {
124 index: 7,
125 name: "FocusMode".to_string(),
126 print_conv: {
127 let mut conv = HashMap::new();
128 conv.insert(0i16, "One-shot AF".to_string());
129 conv.insert(1i16, "AI Servo AF".to_string());
130 conv.insert(2i16, "AI Focus AF".to_string());
131 conv.insert(3i16, "Manual Focus (3)".to_string());
132 conv.insert(4i16, "Single".to_string());
133 conv.insert(5i16, "Continuous".to_string());
134 conv.insert(6i16, "Manual Focus (6)".to_string());
135 conv.insert(16i16, "Pan Focus".to_string()); Some(conv)
137 },
138 },
139 );
140
141 table
142}
143
144pub fn extract_camera_settings(
152 data: &[u8],
153 offset: usize,
154 size: usize,
155 byte_order: ByteOrder,
156) -> Result<HashMap<String, TagValue>> {
157 let table = create_camera_settings_table();
158 let mut results = HashMap::new();
159
160 let format_size = 2; debug!(
164 "Extracting Canon CameraSettings: offset={:#x}, size={}, format=int16s",
165 offset, size
166 );
167
168 for (&index, tag_def) in &table {
170 let entry_offset = (index - 1) as usize * format_size;
172
173 if entry_offset + format_size > size {
174 debug!("Tag {} at index {} beyond data bounds", tag_def.name, index);
175 continue;
176 }
177
178 let data_offset = offset + entry_offset;
179
180 if data_offset + format_size > data.len() {
181 debug!(
182 "Tag {} data offset {:#x} beyond buffer bounds",
183 tag_def.name, data_offset
184 );
185 continue;
186 }
187
188 let raw_value = byte_order.read_u16(data, data_offset)? as i16;
190
191 let final_value = if let Some(print_conv) = &tag_def.print_conv {
193 if let Some(converted) = print_conv.get(&raw_value) {
194 TagValue::String(converted.clone())
195 } else {
196 TagValue::I16(raw_value)
197 }
198 } else {
199 TagValue::I16(raw_value)
200 };
201
202 debug!(
203 "Extracted Canon {} = {:?} (raw: {}) at index {}",
204 tag_def.name, final_value, raw_value, index
205 );
206
207 let tag_name = format!("MakerNotes:{}", tag_def.name);
210 results.insert(tag_name, final_value);
211 }
212
213 Ok(results)
214}
215
216pub fn create_canon_af_info_table() -> BinaryDataTable {
221 use crate::types::{BinaryDataFormat, FormatSpec};
222
223 let mut table = BinaryDataTable {
224 default_format: BinaryDataFormat::Int16u,
225 first_entry: Some(0),
226 groups: HashMap::new(),
227 tags: HashMap::new(),
228 data_member_tags: Vec::new(),
229 dependency_order: Vec::new(),
230 };
231
232 table.groups.insert(0, "MakerNotes".to_string());
234 table.groups.insert(2, "Camera".to_string());
235
236 table.tags.insert(
239 0,
240 BinaryDataTag {
241 name: "NumAFPoints".to_string(),
242 format_spec: Some(FormatSpec::Fixed(BinaryDataFormat::Int16u)),
243 format: Some(BinaryDataFormat::Int16u),
244 mask: None,
245 print_conv: None,
246 data_member: Some("NumAFPoints".to_string()), },
248 );
249 table.data_member_tags.push(0);
250
251 table.tags.insert(
254 1,
255 BinaryDataTag {
256 name: "ValidAFPoints".to_string(),
257 format_spec: Some(FormatSpec::Fixed(BinaryDataFormat::Int16u)),
258 format: Some(BinaryDataFormat::Int16u),
259 mask: None,
260 print_conv: None,
261 data_member: None,
262 },
263 );
264
265 table.tags.insert(
267 2,
268 BinaryDataTag {
269 name: "CanonImageWidth".to_string(),
270 format_spec: Some(FormatSpec::Fixed(BinaryDataFormat::Int16u)),
271 format: Some(BinaryDataFormat::Int16u),
272 mask: None,
273 print_conv: None,
274 data_member: None,
275 },
276 );
277
278 table.tags.insert(
280 3,
281 BinaryDataTag {
282 name: "CanonImageHeight".to_string(),
283 format_spec: Some(FormatSpec::Fixed(BinaryDataFormat::Int16u)),
284 format: Some(BinaryDataFormat::Int16u),
285 mask: None,
286 print_conv: None,
287 data_member: None,
288 },
289 );
290
291 table.tags.insert(
293 4,
294 BinaryDataTag {
295 name: "AFImageWidth".to_string(),
296 format_spec: Some(FormatSpec::Fixed(BinaryDataFormat::Int16u)),
297 format: Some(BinaryDataFormat::Int16u),
298 mask: None,
299 print_conv: None,
300 data_member: None,
301 },
302 );
303
304 table.tags.insert(
306 5,
307 BinaryDataTag {
308 name: "AFImageHeight".to_string(),
309 format_spec: Some(FormatSpec::Fixed(BinaryDataFormat::Int16u)),
310 format: Some(BinaryDataFormat::Int16u),
311 mask: None,
312 print_conv: None,
313 data_member: None,
314 },
315 );
316
317 table.tags.insert(
319 6,
320 BinaryDataTag {
321 name: "AFAreaWidth".to_string(),
322 format_spec: Some(FormatSpec::Fixed(BinaryDataFormat::Int16u)),
323 format: Some(BinaryDataFormat::Int16u),
324 mask: None,
325 print_conv: None,
326 data_member: None,
327 },
328 );
329
330 table.tags.insert(
332 7,
333 BinaryDataTag {
334 name: "AFAreaHeight".to_string(),
335 format_spec: Some(FormatSpec::Fixed(BinaryDataFormat::Int16u)),
336 format: Some(BinaryDataFormat::Int16u),
337 mask: None,
338 print_conv: None,
339 data_member: None,
340 },
341 );
342
343 table.tags.insert(
346 8,
347 BinaryDataTag {
348 name: "AFAreaXPositions".to_string(),
349 format_spec: Some(FormatSpec::Array {
350 base_format: BinaryDataFormat::Int16s,
351 count_expr: "$val{0}".to_string(), }),
353 format: None, mask: None,
355 print_conv: None,
356 data_member: None,
357 },
358 );
359
360 table.tags.insert(
363 9,
364 BinaryDataTag {
365 name: "AFAreaYPositions".to_string(),
366 format_spec: Some(FormatSpec::Array {
367 base_format: BinaryDataFormat::Int16s,
368 count_expr: "$val{0}".to_string(), }),
370 format: None, mask: None,
372 print_conv: None,
373 data_member: None,
374 },
375 );
376
377 table.tags.insert(
380 10,
381 BinaryDataTag {
382 name: "AFPointsInFocus".to_string(),
383 format_spec: Some(FormatSpec::Array {
384 base_format: BinaryDataFormat::Int16s,
385 count_expr: "int(($val{0}+15)/16)".to_string(), }),
387 format: None, mask: None,
389 print_conv: None,
390 data_member: None,
391 },
392 );
393
394 table.analyze_dependencies();
396
397 table
398}
399
400pub fn create_canon_camera_settings_table() -> BinaryDataTable {
404 let mut table = BinaryDataTable {
405 default_format: BinaryDataFormat::Int16s,
406 first_entry: Some(1),
407 groups: HashMap::new(),
408 tags: HashMap::new(),
409 data_member_tags: Vec::new(),
410 dependency_order: Vec::new(),
411 };
412
413 table.groups.insert(0, "MakerNotes".to_string());
415 table.groups.insert(2, "Camera".to_string());
416
417 table.tags.insert(
419 1,
420 BinaryDataTag {
421 name: "MacroMode".to_string(),
422 format_spec: None, format: None, mask: None,
425 print_conv: {
426 let mut conv = HashMap::new();
427 conv.insert(1u32, "Macro".to_string());
428 conv.insert(2u32, "Normal".to_string());
429 Some(conv)
430 },
431 data_member: None,
432 },
433 );
434
435 table.tags.insert(
437 2,
438 BinaryDataTag {
439 name: "SelfTimer".to_string(),
440 format_spec: None,
441 format: None,
442 mask: None,
443 print_conv: {
444 let mut conv = HashMap::new();
445 conv.insert(0u32, "Off".to_string());
446 Some(conv)
447 },
448 data_member: None,
449 },
450 );
451
452 table.tags.insert(
454 4,
455 BinaryDataTag {
456 name: "CanonFlashMode".to_string(),
457 format_spec: None,
458 format: None,
459 mask: None,
460 print_conv: {
461 let mut conv = HashMap::new();
462 conv.insert(0u32, "Off".to_string());
463 conv.insert(1u32, "Auto".to_string());
464 conv.insert(2u32, "On".to_string());
465 Some(conv)
466 },
467 data_member: None,
468 },
469 );
470
471 table.tags.insert(
473 7,
474 BinaryDataTag {
475 name: "FocusMode".to_string(),
476 format_spec: None,
477 format: None,
478 mask: None,
479 print_conv: {
480 let mut conv = HashMap::new();
481 conv.insert(0u32, "One-shot AF".to_string());
482 conv.insert(1u32, "AI Servo AF".to_string());
483 conv.insert(2u32, "AI Focus AF".to_string());
484 conv.insert(3u32, "Manual Focus (3)".to_string());
485 Some(conv)
486 },
487 data_member: None,
488 },
489 );
490
491 table
492}
493
494pub fn extract_binary_value(
497 reader: &crate::exif::ExifReader,
498 offset: usize,
499 format: BinaryDataFormat,
500 _count: usize,
501) -> Result<TagValue> {
502 let data = reader.get_data();
503 let byte_order = if let Some(header) = reader.get_header() {
504 header.byte_order
505 } else {
506 ByteOrder::LittleEndian
508 };
509
510 match format {
511 BinaryDataFormat::Int8u => {
512 if offset >= data.len() {
513 return Err(ExifError::ParseError(
514 "Offset beyond data bounds".to_string(),
515 ));
516 }
517 Ok(TagValue::U8(data[offset]))
518 }
519 BinaryDataFormat::Int8s => {
520 if offset >= data.len() {
521 return Err(ExifError::ParseError(
522 "Offset beyond data bounds".to_string(),
523 ));
524 }
525 Ok(TagValue::I16(data[offset] as i8 as i16))
527 }
528 BinaryDataFormat::Int16u => {
529 if offset + 2 > data.len() {
530 return Err(ExifError::ParseError(
531 "Offset beyond data bounds for int16u".to_string(),
532 ));
533 }
534 let value = byte_order.read_u16(data, offset)?;
535 Ok(TagValue::U16(value))
536 }
537 BinaryDataFormat::Int16s => {
538 if offset + 2 > data.len() {
539 return Err(ExifError::ParseError(
540 "Offset beyond data bounds for int16s".to_string(),
541 ));
542 }
543 let value = byte_order.read_u16(data, offset)? as i16;
544 Ok(TagValue::I16(value))
545 }
546 BinaryDataFormat::Int32u => {
547 if offset + 4 > data.len() {
548 return Err(ExifError::ParseError(
549 "Offset beyond data bounds for int32u".to_string(),
550 ));
551 }
552 let value = byte_order.read_u32(data, offset)?;
553 Ok(TagValue::U32(value))
554 }
555 BinaryDataFormat::Int32s => {
556 if offset + 4 > data.len() {
557 return Err(ExifError::ParseError(
558 "Offset beyond data bounds for int32s".to_string(),
559 ));
560 }
561 let value = byte_order.read_u32(data, offset)? as i32;
562 Ok(TagValue::I32(value))
563 }
564 BinaryDataFormat::String => {
565 if offset >= data.len() {
567 return Err(ExifError::ParseError(
568 "Offset beyond data bounds for string".to_string(),
569 ));
570 }
571
572 let mut end = offset;
573 while end < data.len() && data[end] != 0 {
574 end += 1;
575 }
576
577 let string_bytes = &data[offset..end];
578 let string_value = String::from_utf8_lossy(string_bytes).to_string();
579 Ok(TagValue::String(string_value))
580 }
581 BinaryDataFormat::PString => {
582 if offset >= data.len() {
584 return Err(ExifError::ParseError(
585 "Offset beyond data bounds for pstring".to_string(),
586 ));
587 }
588
589 let length = data[offset] as usize;
590 if offset + 1 + length > data.len() {
591 return Err(ExifError::ParseError(
592 "Pascal string length exceeds data bounds".to_string(),
593 ));
594 }
595
596 let string_bytes = &data[offset + 1..offset + 1 + length];
597 let string_value = String::from_utf8_lossy(string_bytes).to_string();
598 Ok(TagValue::String(string_value))
599 }
600 _ => Err(ExifError::ParseError(format!(
601 "Binary format {format:?} not yet implemented"
602 ))),
603 }
604}
605
606pub fn extract_binary_data_tags(
609 reader: &mut crate::exif::ExifReader,
610 offset: usize,
611 size: usize,
612 table: &BinaryDataTable,
613) -> Result<()> {
614 use crate::types::TagSourceInfo;
615
616 debug!(
617 "Extracting binary data tags: offset={:#x}, size={}, format={:?}",
618 offset, size, table.default_format
619 );
620
621 for (&index, tag_def) in &table.tags {
623 let first_entry = table.first_entry.unwrap_or(0);
625 if index < first_entry {
626 continue;
627 }
628
629 let entry_offset = (index - first_entry) as usize * table.default_format.byte_size();
630 if entry_offset + table.default_format.byte_size() > size {
631 debug!("Tag {} at index {} beyond data bounds", tag_def.name, index);
632 continue;
633 }
634
635 let data_offset = offset + entry_offset;
636
637 let format = tag_def.format.unwrap_or(table.default_format);
639 let raw_value = extract_binary_value(reader, data_offset, format, 1)?;
640
641 let final_value = if let Some(print_conv) = &tag_def.print_conv {
643 match &raw_value {
644 TagValue::I16(val) => {
645 if let Some(converted) = print_conv.get(&(*val as u32)) {
646 TagValue::String(converted.clone())
647 } else {
648 raw_value
649 }
650 }
651 TagValue::U16(val) => {
652 if let Some(converted) = print_conv.get(&(*val as u32)) {
653 TagValue::String(converted.clone())
654 } else {
655 raw_value
656 }
657 }
658 _ => raw_value,
659 }
660 } else {
661 raw_value
662 };
663
664 let group_0 = table
666 .groups
667 .get(&0)
668 .cloned()
669 .unwrap_or_else(|| "Unknown".to_string());
670 let source_info = TagSourceInfo::new(
671 group_0,
672 "Canon".to_string(),
673 "Canon::BinaryData".to_string(),
674 );
675
676 reader.extracted_tags.insert(index as u16, final_value);
677 reader.tag_sources.insert(index as u16, source_info);
678
679 debug!(
680 "Extracted Canon binary tag {} (index {}) = {:?}",
681 tag_def.name,
682 index,
683 reader.extracted_tags.get(&(index as u16))
684 );
685 }
686
687 Ok(())
688}
689
690pub fn find_canon_camera_settings_tag(
693 reader: &crate::exif::ExifReader,
694 ifd_offset: usize,
695 _size: usize,
696) -> Result<usize> {
697 let data = reader.get_data();
698 let byte_order = if let Some(header) = reader.get_header() {
699 header.byte_order
700 } else {
701 ByteOrder::LittleEndian
703 };
704
705 if ifd_offset + 2 > data.len() {
707 return Err(ExifError::ParseError(
708 "IFD offset beyond data bounds".to_string(),
709 ));
710 }
711
712 let entry_count = byte_order.read_u16(data, ifd_offset)? as usize;
714 let entries_start = ifd_offset + 2;
715 let entries_size = entry_count * 12; if entries_start + entries_size > data.len() {
718 return Err(ExifError::ParseError(
719 "IFD entries beyond data bounds".to_string(),
720 ));
721 }
722
723 for i in 0..entry_count {
725 let entry_offset = entries_start + (i * 12);
726 let tag_id = byte_order.read_u16(data, entry_offset)?;
727
728 if tag_id == 0x0001 {
729 let format = byte_order.read_u16(data, entry_offset + 2)?;
731 let count = byte_order.read_u32(data, entry_offset + 4)?;
732 let value_offset = byte_order.read_u32(data, entry_offset + 8)? as usize;
733
734 debug!(
735 "Found Canon CameraSettings tag: format={}, count={}, offset={:#x}",
736 format, count, value_offset
737 );
738
739 return Ok(value_offset);
740 }
741 }
742
743 Err(ExifError::ParseError(
744 "Canon CameraSettings tag not found in IFD".to_string(),
745 ))
746}
747
748#[cfg(test)]
749mod tests {
750 use super::*;
751 use crate::exif::ExifReader;
752 use crate::tiff_types::{ByteOrder, TiffHeader};
753
754 #[test]
755 fn test_canon_af_info_variable_arrays() {
756 let table = create_canon_af_info_table();
761
762 assert_eq!(table.default_format, BinaryDataFormat::Int16u);
764 assert_eq!(table.first_entry, Some(0));
765 assert_eq!(table.groups.get(&0), Some(&"MakerNotes".to_string()));
766 assert_eq!(table.groups.get(&2), Some(&"Camera".to_string()));
767
768 assert!(table.data_member_tags.contains(&0)); let num_af_points = table.tags.get(&0).unwrap();
773 assert_eq!(num_af_points.name, "NumAFPoints");
774 assert_eq!(num_af_points.data_member, Some("NumAFPoints".to_string()));
775
776 let af_x_positions = table.tags.get(&8).unwrap();
778 assert_eq!(af_x_positions.name, "AFAreaXPositions");
779 if let Some(format_spec) = &af_x_positions.format_spec {
780 match format_spec {
781 crate::types::FormatSpec::Array {
782 base_format,
783 count_expr,
784 } => {
785 assert_eq!(*base_format, BinaryDataFormat::Int16s);
786 assert_eq!(count_expr, "$val{0}"); }
788 _ => panic!("Expected Array format spec for AFAreaXPositions"),
789 }
790 } else {
791 panic!("AFAreaXPositions should have format_spec");
792 }
793
794 let af_y_positions = table.tags.get(&9).unwrap();
795 assert_eq!(af_y_positions.name, "AFAreaYPositions");
796
797 let af_points_in_focus = table.tags.get(&10).unwrap();
798 assert_eq!(af_points_in_focus.name, "AFPointsInFocus");
799 if let Some(format_spec) = &af_points_in_focus.format_spec {
800 match format_spec {
801 crate::types::FormatSpec::Array {
802 base_format,
803 count_expr,
804 } => {
805 assert_eq!(*base_format, BinaryDataFormat::Int16s);
806 assert_eq!(count_expr, "int(($val{0}+15)/16)"); }
808 _ => panic!("Expected Array format spec for AFPointsInFocus"),
809 }
810 }
811 }
812
813 #[test]
814 fn test_canon_af_info_processing() {
815 let mut reader = ExifReader::new();
822 reader.set_test_header(TiffHeader {
823 byte_order: ByteOrder::LittleEndian,
824 magic: 42,
825 ifd0_offset: 8,
826 });
827
828 let test_data = vec![
842 0x09, 0x00, 0x07, 0x00, 0x40, 0x06, 0xB0, 0x04, 0x00, 0x04, 0x00, 0x03, 0x40, 0x00, 0x30, 0x00, 0x38, 0xFF, 0x9C, 0xFF, 0x00, 0x00, 0x64, 0x00, 0xC8, 0x00, 0x6A, 0xFF, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x9C, 0xFF, 0xCE, 0xFF, 0x00, 0x00, 0x32, 0x00, 0x64, 0x00, 0x4B, 0x00, 0x00, 0x00, 0xB5, 0xFF, 0x96, 0x00, 0xFF, 0x01, ];
874
875 reader.set_test_data(test_data.clone());
876
877 let table = create_canon_af_info_table();
878
879 let result =
881 reader.process_binary_data_with_dependencies(&test_data, 0, test_data.len(), &table);
882 assert!(
883 result.is_ok(),
884 "Failed to process Canon AF Info data: {result:?}"
885 );
886
887 let extracted_tags = reader.get_extracted_tags();
889
890 assert_eq!(
892 extracted_tags.get(&0),
893 Some(&crate::types::TagValue::U16(9))
894 );
895
896 assert_eq!(
898 extracted_tags.get(&1),
899 Some(&crate::types::TagValue::U16(7))
900 );
901
902 assert_eq!(
904 extracted_tags.get(&2),
905 Some(&crate::types::TagValue::U16(1600))
906 );
907
908 assert_eq!(
910 extracted_tags.get(&3),
911 Some(&crate::types::TagValue::U16(1200))
912 );
913
914 if let Some(crate::types::TagValue::U16Array(x_positions)) = extracted_tags.get(&8) {
916 assert_eq!(
917 x_positions.len(),
918 9,
919 "AFAreaXPositions should have 9 elements based on NumAFPoints"
920 );
921 } else {
923 panic!("AFAreaXPositions should be U16Array");
924 }
925
926 if let Some(crate::types::TagValue::U16Array(y_positions)) = extracted_tags.get(&9) {
928 assert_eq!(
929 y_positions.len(),
930 9,
931 "AFAreaYPositions should have 9 elements based on NumAFPoints"
932 );
933 } else {
934 panic!("AFAreaYPositions should be U16Array");
935 }
936
937 if let Some(crate::types::TagValue::U16Array(points_in_focus)) = extracted_tags.get(&10) {
940 assert_eq!(
941 points_in_focus.len(),
942 1,
943 "AFPointsInFocus should have 1 element based on ceiling division"
944 );
945 } else {
946 panic!("AFPointsInFocus should be U16Array");
947 }
948 }
949
950 #[test]
951 fn test_expression_evaluator() {
952 use crate::types::{DataMemberValue, ExpressionEvaluator};
955 use std::collections::HashMap;
956
957 let data_members = HashMap::new();
958 let mut val_hash = HashMap::new();
959 val_hash.insert(0, DataMemberValue::U16(9)); let evaluator = ExpressionEvaluator::new(val_hash, &data_members);
962
963 let simple_result = evaluator.evaluate_count_expression("$val{0}");
965 assert_eq!(simple_result.unwrap(), 9);
966
967 let complex_result = evaluator.evaluate_count_expression("int(($val{0}+15)/16)");
969 println!("Complex expression result: {complex_result:?}");
970 match complex_result {
971 Ok(val) => assert_eq!(val, 1),
972 Err(e) => panic!("Complex expression failed: {e}"),
973 }
974 }
975
976 #[test]
977 fn test_variable_string_formats() {
978 use crate::exif::ExifReader;
981 use crate::tiff_types::{ByteOrder, TiffHeader};
982 use crate::types::{BinaryDataFormat, BinaryDataTable, BinaryDataTag, FormatSpec};
983 use std::collections::HashMap;
984
985 let mut reader = ExifReader::new();
986 reader.set_test_header(TiffHeader {
987 byte_order: ByteOrder::LittleEndian,
988 magic: 42,
989 ifd0_offset: 8,
990 });
991
992 let mut table = BinaryDataTable {
994 default_format: BinaryDataFormat::Int16u,
995 first_entry: Some(0),
996 groups: HashMap::new(),
997 tags: HashMap::new(),
998 data_member_tags: Vec::new(),
999 dependency_order: Vec::new(),
1000 };
1001
1002 table.tags.insert(
1004 0,
1005 BinaryDataTag {
1006 name: "StringLength".to_string(),
1007 format_spec: Some(FormatSpec::Fixed(BinaryDataFormat::Int16u)),
1008 format: Some(BinaryDataFormat::Int16u),
1009 mask: None,
1010 print_conv: None,
1011 data_member: Some("StringLength".to_string()),
1012 },
1013 );
1014 table.data_member_tags.push(0);
1015
1016 table.tags.insert(
1018 1,
1019 BinaryDataTag {
1020 name: "VariableString".to_string(),
1021 format_spec: Some(FormatSpec::StringWithLength {
1022 length_expr: "$val{0}".to_string(),
1023 }),
1024 format: None,
1025 mask: None,
1026 print_conv: None,
1027 data_member: None,
1028 },
1029 );
1030
1031 table.analyze_dependencies();
1032
1033 let test_data = vec![
1035 0x05, 0x00, b'H', b'e', b'l', b'l', b'o', ];
1038
1039 reader.set_test_data(test_data.clone());
1040
1041 let result =
1042 reader.process_binary_data_with_dependencies(&test_data, 0, test_data.len(), &table);
1043 assert!(result.is_ok());
1044
1045 let extracted_tags = reader.get_extracted_tags();
1046
1047 assert_eq!(
1049 extracted_tags.get(&0),
1050 Some(&crate::types::TagValue::U16(5))
1051 );
1052
1053 assert_eq!(
1055 extracted_tags.get(&1),
1056 Some(&crate::types::TagValue::String("Hello".to_string()))
1057 );
1058 }
1059
1060 #[test]
1061 fn test_edge_cases_zero_count() {
1062 use crate::exif::ExifReader;
1064 use crate::tiff_types::{ByteOrder, TiffHeader};
1065 use crate::types::{BinaryDataFormat, BinaryDataTable, BinaryDataTag, FormatSpec};
1066 use std::collections::HashMap;
1067
1068 let mut reader = ExifReader::new();
1069 reader.set_test_header(TiffHeader {
1070 byte_order: ByteOrder::LittleEndian,
1071 magic: 42,
1072 ifd0_offset: 8,
1073 });
1074
1075 let mut table = BinaryDataTable {
1076 default_format: BinaryDataFormat::Int16u,
1077 first_entry: Some(0),
1078 groups: HashMap::new(),
1079 tags: HashMap::new(),
1080 data_member_tags: Vec::new(),
1081 dependency_order: Vec::new(),
1082 };
1083
1084 table.tags.insert(
1086 0,
1087 BinaryDataTag {
1088 name: "Count".to_string(),
1089 format_spec: Some(FormatSpec::Fixed(BinaryDataFormat::Int16u)),
1090 format: Some(BinaryDataFormat::Int16u),
1091 mask: None,
1092 print_conv: None,
1093 data_member: Some("Count".to_string()),
1094 },
1095 );
1096 table.data_member_tags.push(0);
1097
1098 table.tags.insert(
1100 1,
1101 BinaryDataTag {
1102 name: "EmptyArray".to_string(),
1103 format_spec: Some(FormatSpec::Array {
1104 base_format: BinaryDataFormat::Int16s,
1105 count_expr: "$val{0}".to_string(),
1106 }),
1107 format: None,
1108 mask: None,
1109 print_conv: None,
1110 data_member: None,
1111 },
1112 );
1113
1114 table.analyze_dependencies();
1115
1116 let test_data = vec![0x00, 0x00]; reader.set_test_data(test_data.clone());
1118
1119 let result =
1120 reader.process_binary_data_with_dependencies(&test_data, 0, test_data.len(), &table);
1121 assert!(result.is_ok());
1122
1123 let extracted_tags = reader.get_extracted_tags();
1124
1125 assert_eq!(
1127 extracted_tags.get(&0),
1128 Some(&crate::types::TagValue::U16(0))
1129 );
1130
1131 assert_eq!(
1133 extracted_tags.get(&1),
1134 Some(&crate::types::TagValue::U8Array(vec![]))
1135 );
1136 }
1137
1138 #[test]
1139 fn test_create_camera_settings_table() {
1140 let table = create_camera_settings_table();
1141
1142 assert!(table.contains_key(&1)); assert!(table.contains_key(&2)); assert!(table.contains_key(&3)); assert!(table.contains_key(&4)); assert!(table.contains_key(&5)); assert!(table.contains_key(&7)); let macro_mode = table.get(&1).unwrap();
1152 assert_eq!(macro_mode.name, "MacroMode");
1153 assert!(macro_mode.print_conv.is_some());
1154
1155 let print_conv = macro_mode.print_conv.as_ref().unwrap();
1156 assert_eq!(print_conv.get(&1), Some(&"Macro".to_string()));
1157 assert_eq!(print_conv.get(&2), Some(&"Normal".to_string()));
1158 }
1159
1160 #[test]
1161 fn test_extract_camera_settings_basic() {
1162 let test_data = vec![0x00, 0x01, 0x00, 0x02]; let result = extract_camera_settings(&test_data, 0, 4, ByteOrder::BigEndian);
1166 assert!(result.is_ok());
1167
1168 let tags = result.unwrap();
1169
1170 assert!(tags.contains_key("MakerNotes:MacroMode"));
1172 assert!(tags.contains_key("MakerNotes:SelfTimer"));
1173
1174 if let Some(TagValue::String(value)) = tags.get("MakerNotes:MacroMode") {
1176 assert_eq!(value, "Macro");
1177 } else {
1178 panic!("MacroMode should be converted to string");
1179 }
1180
1181 if let Some(TagValue::I16(value)) = tags.get("MakerNotes:SelfTimer") {
1183 assert_eq!(*value, 2);
1184 } else {
1185 panic!("SelfTimer should be I16 for unconverted value");
1186 }
1187 }
1188}