Skip to main content

oxidize_pdf/memory/
memory_mapped.rs

1//! Memory-mapped file support for efficient large PDF handling
2//!
3//! Uses OS-level memory mapping to access PDF files without loading
4//! them entirely into memory.
5
6use crate::error::{PdfError, Result};
7use std::fs::File;
8use std::io::{Read, Seek, SeekFrom};
9use std::ops::Deref;
10use std::path::Path;
11use std::sync::Arc;
12
13/// Platform-specific memory mapping implementation
14#[cfg(unix)]
15mod unix_mmap {
16    use super::*;
17    use std::os::unix::io::AsRawFd;
18    use std::ptr;
19
20    pub struct MmapInner {
21        ptr: *mut u8,
22        len: usize,
23    }
24
25    // SAFETY: MmapInner is used in a read-only context
26    unsafe impl Send for MmapInner {}
27    unsafe impl Sync for MmapInner {}
28
29    impl MmapInner {
30        pub fn new(file: &File, len: usize) -> Result<Self> {
31            if len == 0 {
32                return Err(PdfError::InvalidFormat(
33                    "Cannot mmap empty file".to_string(),
34                ));
35            }
36
37            unsafe {
38                let ptr = libc::mmap(
39                    ptr::null_mut(),
40                    len,
41                    libc::PROT_READ,
42                    libc::MAP_PRIVATE,
43                    file.as_raw_fd(),
44                    0,
45                );
46
47                if ptr == libc::MAP_FAILED {
48                    return Err(PdfError::Io(std::io::Error::last_os_error()));
49                }
50
51                Ok(Self {
52                    ptr: ptr as *mut u8,
53                    len,
54                })
55            }
56        }
57
58        pub fn as_slice(&self) -> &[u8] {
59            unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
60        }
61    }
62
63    impl Drop for MmapInner {
64        fn drop(&mut self) {
65            unsafe {
66                libc::munmap(self.ptr as *mut libc::c_void, self.len);
67            }
68        }
69    }
70}
71
72#[cfg(windows)]
73mod windows_mmap {
74    use super::*;
75    use std::os::windows::io::AsRawHandle;
76    use std::ptr;
77    use winapi::um::handleapi::CloseHandle;
78    use winapi::um::memoryapi::{
79        CreateFileMappingW, MapViewOfFile, UnmapViewOfFile, FILE_MAP_READ,
80    };
81    use winapi::um::winnt::PAGE_READONLY;
82
83    pub struct MmapInner {
84        ptr: *mut u8,
85        len: usize,
86        mapping_handle: *mut winapi::ctypes::c_void,
87    }
88
89    unsafe impl Send for MmapInner {}
90    unsafe impl Sync for MmapInner {}
91
92    impl MmapInner {
93        pub fn new(file: &File, len: usize) -> Result<Self> {
94            if len == 0 {
95                return Err(PdfError::InvalidFormat(
96                    "Cannot mmap empty file".to_string(),
97                ));
98            }
99
100            unsafe {
101                let mapping_handle = CreateFileMappingW(
102                    file.as_raw_handle() as *mut _,
103                    ptr::null_mut(),
104                    PAGE_READONLY,
105                    0,
106                    0,
107                    ptr::null(),
108                );
109
110                if mapping_handle.is_null() {
111                    return Err(PdfError::Io(std::io::Error::last_os_error()));
112                }
113
114                let ptr = MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, len);
115
116                if ptr.is_null() {
117                    CloseHandle(mapping_handle);
118                    return Err(PdfError::Io(std::io::Error::last_os_error()));
119                }
120
121                Ok(Self {
122                    ptr: ptr as *mut u8,
123                    len,
124                    mapping_handle,
125                })
126            }
127        }
128
129        pub fn as_slice(&self) -> &[u8] {
130            unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
131        }
132    }
133
134    impl Drop for MmapInner {
135        fn drop(&mut self) {
136            unsafe {
137                UnmapViewOfFile(self.ptr as *mut _);
138                CloseHandle(self.mapping_handle);
139            }
140        }
141    }
142}
143
144// Fallback implementation for unsupported platforms
145#[cfg(not(any(unix, windows)))]
146mod fallback_mmap {
147    use super::*;
148
149    pub struct MmapInner {
150        data: Vec<u8>,
151    }
152
153    impl MmapInner {
154        pub fn new(file: &File, len: usize) -> Result<Self> {
155            let mut data = vec![0u8; len];
156            let mut file_clone = file.try_clone()?;
157            file_clone.seek(SeekFrom::Start(0))?;
158            file_clone.read_exact(&mut data)?;
159            Ok(Self { data })
160        }
161
162        pub fn as_slice(&self) -> &[u8] {
163            &self.data
164        }
165    }
166}
167
168#[cfg(not(any(unix, windows)))]
169use fallback_mmap::MmapInner;
170#[cfg(unix)]
171use unix_mmap::MmapInner;
172#[cfg(windows)]
173use windows_mmap::MmapInner;
174
175/// Memory-mapped file for efficient access
176pub struct MemoryMappedFile {
177    inner: Arc<MmapInner>,
178}
179
180impl MemoryMappedFile {
181    /// Create a new memory-mapped file
182    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
183        let file = File::open(path)?;
184        let metadata = file.metadata()?;
185        let len = metadata.len() as usize;
186
187        let inner = MmapInner::new(&file, len)?;
188
189        Ok(Self {
190            inner: Arc::new(inner),
191        })
192    }
193
194    /// Get the length of the mapped region
195    pub fn len(&self) -> usize {
196        self.inner.as_slice().len()
197    }
198
199    /// Check if the mapped region is empty
200    pub fn is_empty(&self) -> bool {
201        self.inner.as_slice().is_empty()
202    }
203}
204
205impl Deref for MemoryMappedFile {
206    type Target = [u8];
207
208    fn deref(&self) -> &Self::Target {
209        self.inner.as_slice()
210    }
211}
212
213impl AsRef<[u8]> for MemoryMappedFile {
214    fn as_ref(&self) -> &[u8] {
215        self.inner.as_slice()
216    }
217}
218
219/// A reader that uses memory-mapped files
220pub struct MappedReader {
221    mmap: MemoryMappedFile,
222    position: usize,
223}
224
225impl MappedReader {
226    /// Create a new mapped reader
227    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
228        let mmap = MemoryMappedFile::new(path)?;
229        Ok(Self { mmap, position: 0 })
230    }
231
232    /// Get a slice of the file at the given range
233    pub fn get_slice(&self, start: usize, end: usize) -> Option<&[u8]> {
234        let data = self.mmap.as_ref();
235        if start <= end && end <= data.len() {
236            Some(&data[start..end])
237        } else {
238            None
239        }
240    }
241}
242
243impl Read for MappedReader {
244    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
245        let data = self.mmap.as_ref();
246        let remaining = data.len().saturating_sub(self.position);
247        let to_read = buf.len().min(remaining);
248
249        if to_read > 0 {
250            buf[..to_read].copy_from_slice(&data[self.position..self.position + to_read]);
251            self.position += to_read;
252        }
253
254        Ok(to_read)
255    }
256}
257
258impl Seek for MappedReader {
259    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
260        let new_pos = match pos {
261            SeekFrom::Start(n) => n as i64,
262            SeekFrom::End(n) => self.mmap.len() as i64 + n,
263            SeekFrom::Current(n) => self.position as i64 + n,
264        };
265
266        if new_pos < 0 || new_pos > self.mmap.len() as i64 {
267            return Err(std::io::Error::new(
268                std::io::ErrorKind::InvalidInput,
269                "Seek position out of bounds",
270            ));
271        }
272
273        self.position = new_pos as usize;
274        Ok(self.position as u64)
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281    use std::io::Write;
282    use tempfile::NamedTempFile;
283
284    #[test]
285    fn test_memory_mapped_file() {
286        // Create a temporary file
287        let mut temp_file = NamedTempFile::new().unwrap();
288        let test_data = b"Hello, memory mapped world!";
289        temp_file.write_all(test_data).unwrap();
290        temp_file.flush().unwrap();
291
292        // Memory map it
293        let mmap = MemoryMappedFile::new(temp_file.path()).unwrap();
294
295        assert_eq!(mmap.len(), test_data.len());
296        assert!(!mmap.is_empty());
297        assert_eq!(&mmap[..], test_data);
298    }
299
300    #[test]
301    fn test_mapped_reader_read() {
302        let mut temp_file = NamedTempFile::new().unwrap();
303        let test_data = b"Test data for mapped reader";
304        temp_file.write_all(test_data).unwrap();
305        temp_file.flush().unwrap();
306
307        let mut reader = MappedReader::new(temp_file.path()).unwrap();
308
309        // Read partial data
310        let mut buf = [0u8; 4];
311        assert_eq!(reader.read(&mut buf).unwrap(), 4);
312        assert_eq!(&buf, b"Test");
313
314        // Read more data
315        let mut buf = [0u8; 5];
316        assert_eq!(reader.read(&mut buf).unwrap(), 5);
317        assert_eq!(&buf, b" data");
318    }
319
320    #[test]
321    fn test_mapped_reader_seek() {
322        let mut temp_file = NamedTempFile::new().unwrap();
323        let test_data = b"0123456789";
324        temp_file.write_all(test_data).unwrap();
325        temp_file.flush().unwrap();
326
327        let mut reader = MappedReader::new(temp_file.path()).unwrap();
328
329        // Seek to position 5
330        assert_eq!(reader.seek(SeekFrom::Start(5)).unwrap(), 5);
331        let mut buf = [0u8; 2];
332        let _bytes_read = reader.read(&mut buf).unwrap();
333        assert_eq!(&buf, b"56");
334
335        // Seek relative
336        assert_eq!(reader.seek(SeekFrom::Current(-3)).unwrap(), 4);
337        let _bytes_read = reader.read(&mut buf).unwrap();
338        assert_eq!(&buf, b"45");
339
340        // Seek from end
341        assert_eq!(reader.seek(SeekFrom::End(-2)).unwrap(), 8);
342        let _bytes_read = reader.read(&mut buf).unwrap();
343        assert_eq!(&buf, b"89");
344    }
345
346    #[test]
347    fn test_get_slice() {
348        let mut temp_file = NamedTempFile::new().unwrap();
349        let test_data = b"Hello, World!";
350        temp_file.write_all(test_data).unwrap();
351        temp_file.flush().unwrap();
352
353        let reader = MappedReader::new(temp_file.path()).unwrap();
354
355        assert_eq!(reader.get_slice(0, 5), Some(&b"Hello"[..]));
356        assert_eq!(reader.get_slice(7, 12), Some(&b"World"[..]));
357        assert_eq!(
358            reader.get_slice(0, test_data.len()),
359            Some(test_data.as_ref())
360        );
361
362        // Out of bounds
363        assert_eq!(reader.get_slice(10, 20), None);
364        assert_eq!(reader.get_slice(5, 3), None); // start > end
365    }
366
367    #[test]
368    fn test_memory_mapped_file_empty() {
369        let temp_file = NamedTempFile::new().unwrap();
370        // Don't write anything to create empty file
371
372        let result = MemoryMappedFile::new(temp_file.path());
373
374        // Should fail to map empty file
375        assert!(result.is_err());
376        match result {
377            Err(PdfError::InvalidFormat(msg)) => {
378                assert!(msg.contains("empty file"));
379            }
380            _ => panic!("Expected InvalidFormat error for empty file"),
381        }
382    }
383
384    #[test]
385    fn test_memory_mapped_file_nonexistent() {
386        let nonexistent_path = "/path/that/does/not/exist.pdf";
387        let result = MemoryMappedFile::new(nonexistent_path);
388
389        assert!(result.is_err());
390        match result {
391            Err(PdfError::Io(_)) => {}
392            _ => panic!("Expected IO error for nonexistent file"),
393        }
394    }
395
396    #[test]
397    fn test_memory_mapped_file_large() {
398        let mut temp_file = NamedTempFile::new().unwrap();
399        let large_data = vec![0xAB; 10000];
400        temp_file.write_all(&large_data).unwrap();
401        temp_file.flush().unwrap();
402
403        let mmap = MemoryMappedFile::new(temp_file.path()).unwrap();
404
405        assert_eq!(mmap.len(), 10000);
406        assert!(!mmap.is_empty());
407        assert_eq!(mmap[0], 0xAB);
408        assert_eq!(mmap[9999], 0xAB);
409        assert_eq!(&mmap[..100], &large_data[..100]);
410    }
411
412    #[test]
413    fn test_memory_mapped_file_deref() {
414        let mut temp_file = NamedTempFile::new().unwrap();
415        let test_data = b"Deref test data";
416        temp_file.write_all(test_data).unwrap();
417        temp_file.flush().unwrap();
418
419        let mmap = MemoryMappedFile::new(temp_file.path()).unwrap();
420
421        // Test Deref implementation
422        let slice: &[u8] = &mmap;
423        assert_eq!(slice, test_data);
424
425        // Test indexing (uses Deref)
426        assert_eq!(mmap[0], b'D');
427        assert_eq!(mmap[5], b' ');
428        assert_eq!(mmap[test_data.len() - 1], b'a');
429
430        // Test AsRef implementation
431        let as_ref: &[u8] = mmap.as_ref();
432        assert_eq!(as_ref, test_data);
433    }
434
435    #[test]
436    fn test_memory_mapped_file_binary_data() {
437        let mut temp_file = NamedTempFile::new().unwrap();
438        let binary_data = vec![0x00, 0xFF, 0x7F, 0x80, 0x01, 0xFE];
439        temp_file.write_all(&binary_data).unwrap();
440        temp_file.flush().unwrap();
441
442        let mmap = MemoryMappedFile::new(temp_file.path()).unwrap();
443
444        assert_eq!(mmap.len(), 6);
445        assert_eq!(mmap[0], 0x00);
446        assert_eq!(mmap[1], 0xFF);
447        assert_eq!(mmap[2], 0x7F);
448        assert_eq!(mmap[3], 0x80);
449        assert_eq!(mmap[4], 0x01);
450        assert_eq!(mmap[5], 0xFE);
451    }
452
453    #[test]
454    fn test_memory_mapped_file_single_byte() {
455        let mut temp_file = NamedTempFile::new().unwrap();
456        temp_file.write_all(&[42]).unwrap();
457        temp_file.flush().unwrap();
458
459        let mmap = MemoryMappedFile::new(temp_file.path()).unwrap();
460
461        assert_eq!(mmap.len(), 1);
462        assert!(!mmap.is_empty());
463        assert_eq!(mmap[0], 42);
464    }
465
466    #[test]
467    fn test_mapped_reader_creation() {
468        let mut temp_file = NamedTempFile::new().unwrap();
469        let test_data = b"Reader creation test";
470        temp_file.write_all(test_data).unwrap();
471        temp_file.flush().unwrap();
472
473        let reader = MappedReader::new(temp_file.path()).unwrap();
474
475        assert_eq!(reader.mmap.len(), test_data.len());
476        assert_eq!(reader.position, 0);
477    }
478
479    #[test]
480    fn test_mapped_reader_read_entire_file() {
481        let mut temp_file = NamedTempFile::new().unwrap();
482        let test_data = b"Complete file read test";
483        temp_file.write_all(test_data).unwrap();
484        temp_file.flush().unwrap();
485
486        let mut reader = MappedReader::new(temp_file.path()).unwrap();
487
488        let mut buf = vec![0u8; test_data.len()];
489        let bytes_read = reader.read(&mut buf).unwrap();
490
491        assert_eq!(bytes_read, test_data.len());
492        assert_eq!(&buf, test_data);
493        assert_eq!(reader.position, test_data.len());
494    }
495
496    #[test]
497    fn test_mapped_reader_read_beyond_eof() {
498        let mut temp_file = NamedTempFile::new().unwrap();
499        let test_data = b"Short";
500        temp_file.write_all(test_data).unwrap();
501        temp_file.flush().unwrap();
502
503        let mut reader = MappedReader::new(temp_file.path()).unwrap();
504
505        // Read entire file
506        let mut buf = vec![0u8; test_data.len()];
507        assert_eq!(reader.read(&mut buf).unwrap(), test_data.len());
508
509        // Try to read more - should return 0
510        let mut buf = vec![0u8; 10];
511        assert_eq!(reader.read(&mut buf).unwrap(), 0);
512        assert_eq!(reader.position, test_data.len());
513    }
514
515    #[test]
516    fn test_mapped_reader_partial_reads() {
517        let mut temp_file = NamedTempFile::new().unwrap();
518        let test_data = b"0123456789ABCDEF";
519        temp_file.write_all(test_data).unwrap();
520        temp_file.flush().unwrap();
521
522        let mut reader = MappedReader::new(temp_file.path()).unwrap();
523
524        // Read in chunks
525        let mut buf = [0u8; 4];
526
527        assert_eq!(reader.read(&mut buf).unwrap(), 4);
528        assert_eq!(&buf, b"0123");
529        assert_eq!(reader.position, 4);
530
531        assert_eq!(reader.read(&mut buf).unwrap(), 4);
532        assert_eq!(&buf, b"4567");
533        assert_eq!(reader.position, 8);
534
535        assert_eq!(reader.read(&mut buf).unwrap(), 4);
536        assert_eq!(&buf, b"89AB");
537        assert_eq!(reader.position, 12);
538
539        assert_eq!(reader.read(&mut buf).unwrap(), 4);
540        assert_eq!(&buf, b"CDEF");
541        assert_eq!(reader.position, 16);
542
543        // EOF
544        assert_eq!(reader.read(&mut buf).unwrap(), 0);
545    }
546
547    #[test]
548    fn test_mapped_reader_seek_start() {
549        let mut temp_file = NamedTempFile::new().unwrap();
550        let test_data = b"Seek test data";
551        temp_file.write_all(test_data).unwrap();
552        temp_file.flush().unwrap();
553
554        let mut reader = MappedReader::new(temp_file.path()).unwrap();
555
556        // Seek to different positions from start
557        assert_eq!(reader.seek(SeekFrom::Start(0)).unwrap(), 0);
558        assert_eq!(reader.position, 0);
559
560        assert_eq!(reader.seek(SeekFrom::Start(5)).unwrap(), 5);
561        assert_eq!(reader.position, 5);
562
563        assert_eq!(
564            reader
565                .seek(SeekFrom::Start(test_data.len() as u64))
566                .unwrap(),
567            test_data.len() as u64
568        );
569        assert_eq!(reader.position, test_data.len());
570    }
571
572    #[test]
573    fn test_mapped_reader_seek_current() {
574        let mut temp_file = NamedTempFile::new().unwrap();
575        let test_data = b"Current seek test";
576        temp_file.write_all(test_data).unwrap();
577        temp_file.flush().unwrap();
578
579        let mut reader = MappedReader::new(temp_file.path()).unwrap();
580
581        // Move forward
582        assert_eq!(reader.seek(SeekFrom::Current(5)).unwrap(), 5);
583        assert_eq!(reader.position, 5);
584
585        // Move forward more
586        assert_eq!(reader.seek(SeekFrom::Current(3)).unwrap(), 8);
587        assert_eq!(reader.position, 8);
588
589        // Move backward
590        assert_eq!(reader.seek(SeekFrom::Current(-2)).unwrap(), 6);
591        assert_eq!(reader.position, 6);
592
593        // Move to start
594        assert_eq!(
595            reader
596                .seek(SeekFrom::Current(-(reader.position as i64)))
597                .unwrap(),
598            0
599        );
600        assert_eq!(reader.position, 0);
601    }
602
603    #[test]
604    fn test_mapped_reader_seek_end() {
605        let mut temp_file = NamedTempFile::new().unwrap();
606        let test_data = b"End seek test data";
607        temp_file.write_all(test_data).unwrap();
608        temp_file.flush().unwrap();
609
610        let mut reader = MappedReader::new(temp_file.path()).unwrap();
611
612        // Seek to end
613        assert_eq!(
614            reader.seek(SeekFrom::End(0)).unwrap(),
615            test_data.len() as u64
616        );
617        assert_eq!(reader.position, test_data.len());
618
619        // Seek backward from end
620        assert_eq!(
621            reader.seek(SeekFrom::End(-5)).unwrap(),
622            (test_data.len() - 5) as u64
623        );
624        assert_eq!(reader.position, test_data.len() - 5);
625
626        // Seek to specific position from end
627        assert_eq!(
628            reader
629                .seek(SeekFrom::End(-(test_data.len() as i64)))
630                .unwrap(),
631            0
632        );
633        assert_eq!(reader.position, 0);
634    }
635
636    #[test]
637    fn test_mapped_reader_seek_out_of_bounds() {
638        let mut temp_file = NamedTempFile::new().unwrap();
639        let test_data = b"Bounds test";
640        temp_file.write_all(test_data).unwrap();
641        temp_file.flush().unwrap();
642
643        let mut reader = MappedReader::new(temp_file.path()).unwrap();
644
645        // Negative position
646        let result = reader.seek(SeekFrom::Start(u64::MAX));
647        assert!(result.is_err());
648        assert_eq!(reader.position, 0); // Position should remain unchanged
649
650        // Beyond file end
651        let result = reader.seek(SeekFrom::Start((test_data.len() + 1) as u64));
652        assert!(result.is_err());
653        assert_eq!(reader.position, 0);
654
655        // Negative from current
656        let result = reader.seek(SeekFrom::Current(-1));
657        assert!(result.is_err());
658        assert_eq!(reader.position, 0);
659
660        // Too far from end
661        let result = reader.seek(SeekFrom::End(-((test_data.len() + 1) as i64)));
662        assert!(result.is_err());
663        assert_eq!(reader.position, 0);
664    }
665
666    #[test]
667    fn test_mapped_reader_seek_and_read_combination() {
668        let mut temp_file = NamedTempFile::new().unwrap();
669        let test_data = b"0123456789";
670        temp_file.write_all(test_data).unwrap();
671        temp_file.flush().unwrap();
672
673        let mut reader = MappedReader::new(temp_file.path()).unwrap();
674
675        // Seek and read from middle
676        reader.seek(SeekFrom::Start(3)).unwrap();
677        let mut buf = [0u8; 3];
678        let _bytes_read = reader.read(&mut buf).unwrap();
679        assert_eq!(&buf, b"345");
680
681        // Seek back and read
682        reader.seek(SeekFrom::Start(1)).unwrap();
683        let _bytes_read = reader.read(&mut buf).unwrap();
684        assert_eq!(&buf, b"123");
685
686        // Seek from current position
687        reader.seek(SeekFrom::Current(2)).unwrap(); // Now at position 6
688        let mut buf = [0u8; 2];
689        let _bytes_read = reader.read(&mut buf).unwrap();
690        assert_eq!(&buf, b"67");
691    }
692
693    #[test]
694    fn test_get_slice_edge_cases() {
695        let mut temp_file = NamedTempFile::new().unwrap();
696        let test_data = b"Edge case testing";
697        temp_file.write_all(test_data).unwrap();
698        temp_file.flush().unwrap();
699
700        let reader = MappedReader::new(temp_file.path()).unwrap();
701
702        // Empty slice
703        assert_eq!(reader.get_slice(5, 5), Some(&b""[..]));
704
705        // Single byte
706        assert_eq!(reader.get_slice(0, 1), Some(&b"E"[..]));
707        assert_eq!(
708            reader.get_slice(test_data.len() - 1, test_data.len()),
709            Some(&b"g"[..])
710        );
711
712        // Full file
713        assert_eq!(
714            reader.get_slice(0, test_data.len()),
715            Some(test_data.as_ref())
716        );
717
718        // At boundary
719        assert_eq!(
720            reader.get_slice(test_data.len(), test_data.len()),
721            Some(&b""[..])
722        );
723
724        // Out of bounds scenarios
725        assert_eq!(reader.get_slice(test_data.len(), test_data.len() + 1), None);
726        assert_eq!(
727            reader.get_slice(test_data.len() + 1, test_data.len() + 2),
728            None
729        );
730        assert_eq!(reader.get_slice(10, 5), None); // start > end
731    }
732
733    #[test]
734    fn test_mapped_reader_zero_length_read() {
735        let mut temp_file = NamedTempFile::new().unwrap();
736        let test_data = b"Zero length test";
737        temp_file.write_all(test_data).unwrap();
738        temp_file.flush().unwrap();
739
740        let mut reader = MappedReader::new(temp_file.path()).unwrap();
741
742        // Read zero bytes
743        let mut buf = [];
744        assert_eq!(reader.read(&mut buf).unwrap(), 0);
745        assert_eq!(reader.position, 0); // Position should not change
746    }
747
748    #[test]
749    fn test_mapped_reader_large_buffer_read() {
750        let mut temp_file = NamedTempFile::new().unwrap();
751        let test_data = b"Small data";
752        temp_file.write_all(test_data).unwrap();
753        temp_file.flush().unwrap();
754
755        let mut reader = MappedReader::new(temp_file.path()).unwrap();
756
757        // Try to read more than available
758        let mut buf = vec![0u8; 100];
759        let bytes_read = reader.read(&mut buf).unwrap();
760
761        assert_eq!(bytes_read, test_data.len());
762        assert_eq!(&buf[..bytes_read], test_data);
763        assert_eq!(reader.position, test_data.len());
764    }
765
766    #[test]
767    fn test_memory_mapped_file_utf8_content() {
768        let mut temp_file = NamedTempFile::new().unwrap();
769        let test_data = "Hello, δΈ–η•Œ! πŸ¦€".as_bytes();
770        temp_file.write_all(test_data).unwrap();
771        temp_file.flush().unwrap();
772
773        let mmap = MemoryMappedFile::new(temp_file.path()).unwrap();
774
775        assert_eq!(mmap.len(), test_data.len());
776        assert_eq!(&mmap[..], test_data);
777
778        // Verify UTF-8 content
779        let content = String::from_utf8_lossy(&mmap);
780        assert_eq!(content, "Hello, δΈ–η•Œ! πŸ¦€");
781    }
782
783    #[test]
784    fn test_mapped_reader_error_propagation() {
785        let nonexistent_path = "/definitely/does/not/exist/test.pdf";
786        let result = MappedReader::new(nonexistent_path);
787
788        assert!(result.is_err());
789        match result {
790            Err(PdfError::Io(_)) => {}
791            _ => panic!("Expected IO error"),
792        }
793    }
794
795    #[test]
796    fn test_get_slice_exact_boundaries() {
797        let mut temp_file = NamedTempFile::new().unwrap();
798        let test_data = b"Boundary test";
799        temp_file.write_all(test_data).unwrap();
800        temp_file.flush().unwrap();
801
802        let reader = MappedReader::new(temp_file.path()).unwrap();
803
804        // Test exact boundaries
805        let len = test_data.len();
806
807        // First character
808        assert_eq!(reader.get_slice(0, 1), Some(&b"B"[..]));
809
810        // Last character
811        assert_eq!(reader.get_slice(len - 1, len), Some(&b"t"[..]));
812
813        // Whole string
814        assert_eq!(reader.get_slice(0, len), Some(test_data.as_ref()));
815
816        // Empty at end
817        assert_eq!(reader.get_slice(len, len), Some(&b""[..]));
818
819        // One past end should fail
820        assert_eq!(reader.get_slice(len, len + 1), None);
821        assert_eq!(reader.get_slice(len + 1, len + 1), None);
822    }
823
824    #[test]
825    fn test_memory_mapped_file_clone_and_share() {
826        use std::sync::Arc;
827        use std::thread;
828
829        let mut temp_file = NamedTempFile::new().unwrap();
830        let test_data = b"Shared mmap test data";
831        temp_file.write_all(test_data).unwrap();
832        temp_file.flush().unwrap();
833
834        let mmap = Arc::new(MemoryMappedFile::new(temp_file.path()).unwrap());
835        let mmap_clone = mmap.clone();
836
837        let handle = thread::spawn(move || {
838            assert_eq!(mmap_clone.len(), test_data.len());
839            assert_eq!(&mmap_clone[..5], b"Share");
840        });
841
842        handle.join().unwrap();
843
844        // Original still works
845        assert_eq!(&mmap[..], test_data);
846    }
847
848    #[test]
849    fn test_mapped_reader_position_consistency() {
850        let mut temp_file = NamedTempFile::new().unwrap();
851        let test_data = b"Position consistency test";
852        temp_file.write_all(test_data).unwrap();
853        temp_file.flush().unwrap();
854
855        let mut reader = MappedReader::new(temp_file.path()).unwrap();
856
857        assert_eq!(reader.position, 0);
858
859        // Read some data
860        let mut buf = [0u8; 8];
861        let _bytes_read = reader.read(&mut buf).unwrap();
862        assert_eq!(reader.position, 8);
863
864        // Seek and verify position
865        reader.seek(SeekFrom::Start(5)).unwrap();
866        assert_eq!(reader.position, 5);
867
868        // Read more and verify position updates
869        let _bytes_read = reader.read(&mut buf).unwrap();
870        assert_eq!(reader.position, 13);
871
872        // Seek relative and verify
873        reader.seek(SeekFrom::Current(-3)).unwrap();
874        assert_eq!(reader.position, 10);
875    }
876}