1use crate::error::{PdfError, Result};
7use crate::parser::content::{ContentOperation, ContentParser};
8use crate::parser::PdfObject;
9use std::io::{BufRead, BufReader, Read, Seek, Write};
10
11#[derive(Debug, Clone)]
13pub struct StreamingOptions {
14 pub buffer_size: usize,
16 pub max_stream_size: usize,
18 pub skip_images: bool,
20 pub skip_fonts: bool,
22}
23
24impl Default for StreamingOptions {
25 fn default() -> Self {
26 Self {
27 buffer_size: 64 * 1024, max_stream_size: 10 * 1024 * 1024, skip_images: false,
30 skip_fonts: false,
31 }
32 }
33}
34
35pub struct StreamProcessor<R: Read + Seek> {
37 reader: BufReader<R>,
38 #[allow(dead_code)]
39 options: StreamingOptions,
40}
41
42impl<R: Read + Seek> StreamProcessor<R> {
43 pub fn new(reader: R, options: StreamingOptions) -> Self {
45 let buf_reader = BufReader::with_capacity(options.buffer_size, reader);
46 Self {
47 reader: buf_reader,
48 options,
49 }
50 }
51
52 pub fn process_with<F>(&mut self, mut callback: F) -> Result<()>
54 where
55 F: FnMut(ProcessingEvent) -> Result<ProcessingAction>,
56 {
57 callback(ProcessingEvent::Start)?;
59
60 self.process_header(&mut callback)?;
62
63 self.process_objects(&mut callback)?;
65
66 callback(ProcessingEvent::End)?;
68
69 Ok(())
70 }
71
72 pub fn process_pages<F>(&mut self, mut page_callback: F) -> Result<()>
74 where
75 F: FnMut(u32, PageData) -> Result<ProcessingAction>,
76 {
77 let mut page_index = 0;
78
79 self.process_with(|event| match event {
80 ProcessingEvent::Page(data) => {
81 let action = page_callback(page_index, data)?;
82 page_index += 1;
83 Ok(action)
84 }
85 _ => Ok(ProcessingAction::Continue),
86 })
87 }
88
89 pub fn extract_text_streaming<W: Write>(&mut self, output: &mut W) -> Result<()> {
91 self.process_pages(|_index, page_data| {
92 if let Some(text) = page_data.text {
93 output.write_all(text.as_bytes())?;
94 output.write_all(b"\n")?;
95 }
96 Ok(ProcessingAction::Continue)
97 })
98 }
99
100 fn process_header<F>(&mut self, callback: &mut F) -> Result<()>
101 where
102 F: FnMut(ProcessingEvent) -> Result<ProcessingAction>,
103 {
104 let mut header = String::new();
105 self.reader.read_line(&mut header)?;
106
107 if !header.starts_with("%PDF-") {
108 return Err(PdfError::InvalidHeader);
109 }
110
111 let version = header.trim_start_matches("%PDF-").trim();
112 callback(ProcessingEvent::Header {
113 version: version.to_string(),
114 })?;
115
116 Ok(())
117 }
118
119 fn process_objects<F>(&mut self, callback: &mut F) -> Result<()>
120 where
121 F: FnMut(ProcessingEvent) -> Result<ProcessingAction>,
122 {
123 for i in 0..3 {
128 let page_data = PageData {
129 number: i,
130 width: 595.0,
131 height: 842.0,
132 text: Some(format!("Page {} content", i + 1)),
133 operations: vec![],
134 };
135
136 match callback(ProcessingEvent::Page(page_data))? {
137 ProcessingAction::Continue => {}
138 ProcessingAction::Skip => continue,
139 ProcessingAction::Stop => break,
140 }
141 }
142
143 Ok(())
144 }
145}
146
147#[derive(Debug)]
149pub enum ProcessingEvent {
150 Start,
152 Header { version: String },
154 Object { id: (u32, u16), object: PdfObject },
156 Page(PageData),
158 Resource {
160 name: String,
161 resource_type: ResourceType,
162 },
163 End,
165}
166
167#[derive(Debug)]
169pub struct PageData {
170 pub number: u32,
172 pub width: f32,
174 pub height: f32,
176 pub text: Option<String>,
178 pub operations: Vec<ContentOperation>,
180}
181
182#[derive(Debug, Clone)]
184pub enum ResourceType {
185 Font,
186 Image,
187 ColorSpace,
188 Pattern,
189 XObject,
190}
191
192#[derive(Debug, PartialEq)]
194pub enum ProcessingAction {
195 Continue,
197 Skip,
199 Stop,
201}
202
203pub struct ContentStreamProcessor {
205 buffer: Vec<u8>,
206 options: StreamingOptions,
207}
208
209impl ContentStreamProcessor {
210 pub fn new(options: StreamingOptions) -> Self {
212 Self {
213 buffer: Vec::with_capacity(options.buffer_size),
214 options,
215 }
216 }
217
218 pub fn process_stream<R: Read, F>(&mut self, mut reader: R, mut callback: F) -> Result<()>
220 where
221 F: FnMut(&ContentOperation) -> Result<ProcessingAction>,
222 {
223 self.buffer.clear();
224 reader.read_to_end(&mut self.buffer)?;
225
226 if self.buffer.len() > self.options.max_stream_size {
227 return Err(PdfError::ContentStreamTooLarge(self.buffer.len()));
228 }
229
230 let operations =
231 ContentParser::parse(&self.buffer).map_err(|e| PdfError::ParseError(e.to_string()))?;
232
233 for op in operations {
234 match callback(&op)? {
235 ProcessingAction::Continue => {}
236 ProcessingAction::Skip => continue,
237 ProcessingAction::Stop => break,
238 }
239 }
240
241 Ok(())
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use std::io::Cursor;
249
250 #[test]
251 fn test_streaming_options_default() {
252 let options = StreamingOptions::default();
253 assert_eq!(options.buffer_size, 64 * 1024);
254 assert_eq!(options.max_stream_size, 10 * 1024 * 1024);
255 assert!(!options.skip_images);
256 assert!(!options.skip_fonts);
257 }
258
259 #[test]
260 fn test_stream_processor_creation() {
261 let data = b"%PDF-1.7\n";
262 let cursor = Cursor::new(data);
263 let options = StreamingOptions::default();
264 let _processor = StreamProcessor::new(cursor, options);
265 }
266
267 #[test]
268 fn test_processing_events() {
269 let data = b"%PDF-1.7\n";
270 let cursor = Cursor::new(data);
271 let options = StreamingOptions::default();
272 let mut processor = StreamProcessor::new(cursor, options);
273
274 let mut events = Vec::new();
275
276 processor
277 .process_with(|event| {
278 match &event {
279 ProcessingEvent::Start => events.push("start"),
280 ProcessingEvent::Header { version } => {
281 assert_eq!(version, "1.7");
282 events.push("header");
283 }
284 ProcessingEvent::Page(_) => events.push("page"),
285 ProcessingEvent::End => events.push("end"),
286 _ => {}
287 }
288 Ok(ProcessingAction::Continue)
289 })
290 .unwrap();
291
292 assert!(events.contains(&"start"));
293 assert!(events.contains(&"header"));
294 assert!(events.contains(&"end"));
295 }
296
297 #[test]
298 fn test_process_pages() {
299 let data = b"%PDF-1.7\n";
300 let cursor = Cursor::new(data);
301 let options = StreamingOptions::default();
302 let mut processor = StreamProcessor::new(cursor, options);
303
304 let mut page_count = 0;
305
306 processor
307 .process_pages(|index, page| {
308 assert_eq!(index, page_count);
309 assert_eq!(page.width, 595.0);
310 assert_eq!(page.height, 842.0);
311 page_count += 1;
312 Ok(ProcessingAction::Continue)
313 })
314 .unwrap();
315
316 assert!(page_count > 0);
317 }
318
319 #[test]
320 fn test_extract_text_streaming() {
321 let data = b"%PDF-1.7\n";
322 let cursor = Cursor::new(data);
323 let options = StreamingOptions::default();
324 let mut processor = StreamProcessor::new(cursor, options);
325
326 let mut output = Vec::new();
327 processor.extract_text_streaming(&mut output).unwrap();
328
329 let text = String::from_utf8(output).unwrap();
330 assert!(text.contains("Page"));
331 }
332
333 #[test]
334 fn test_processing_action() {
335 assert_eq!(ProcessingAction::Continue, ProcessingAction::Continue);
336 assert_eq!(ProcessingAction::Skip, ProcessingAction::Skip);
337 assert_eq!(ProcessingAction::Stop, ProcessingAction::Stop);
338 assert_ne!(ProcessingAction::Continue, ProcessingAction::Stop);
339 }
340
341 #[test]
342 fn test_content_stream_processor() {
343 let options = StreamingOptions::default();
344 let mut processor = ContentStreamProcessor::new(options);
345
346 let content = b"BT /F1 12 Tf 100 700 Td (Hello) Tj ET";
348 let cursor = Cursor::new(content);
349
350 let mut op_count = 0;
351 processor
352 .process_stream(cursor, |op| {
353 op_count += 1;
354 match op {
355 ContentOperation::BeginText => assert_eq!(op_count, 1),
356 ContentOperation::EndText => assert_eq!(op_count, 5),
357 _ => {}
358 }
359 Ok(ProcessingAction::Continue)
360 })
361 .unwrap();
362
363 assert!(op_count > 0);
364 }
365
366 #[test]
367 fn test_stop_processing() {
368 let data = b"%PDF-1.7\n";
369 let cursor = Cursor::new(data);
370 let options = StreamingOptions::default();
371 let mut processor = StreamProcessor::new(cursor, options);
372
373 let mut page_count = 0;
374
375 processor
376 .process_pages(|_index, _page| {
377 page_count += 1;
378 if page_count >= 2 {
379 Ok(ProcessingAction::Stop)
380 } else {
381 Ok(ProcessingAction::Continue)
382 }
383 })
384 .unwrap();
385
386 assert_eq!(page_count, 2);
387 }
388
389 #[test]
390 fn test_streaming_options_custom() {
391 let options = StreamingOptions {
392 buffer_size: 1024,
393 max_stream_size: 2048,
394 skip_images: true,
395 skip_fonts: true,
396 };
397
398 assert_eq!(options.buffer_size, 1024);
399 assert_eq!(options.max_stream_size, 2048);
400 assert!(options.skip_images);
401 assert!(options.skip_fonts);
402 }
403
404 #[test]
405 fn test_streaming_options_debug_clone() {
406 let options = StreamingOptions {
407 buffer_size: 512,
408 max_stream_size: 1024,
409 skip_images: false,
410 skip_fonts: true,
411 };
412
413 let debug_str = format!("{options:?}");
414 assert!(debug_str.contains("StreamingOptions"));
415 assert!(debug_str.contains("512"));
416 assert!(debug_str.contains("1024"));
417
418 let cloned = options.clone();
419 assert_eq!(cloned.buffer_size, 512);
420 assert_eq!(cloned.max_stream_size, 1024);
421 assert!(!cloned.skip_images);
422 assert!(cloned.skip_fonts);
423 }
424
425 #[test]
426 fn test_processing_event_debug() {
427 let events = vec![
428 ProcessingEvent::Start,
429 ProcessingEvent::Header {
430 version: "1.7".to_string(),
431 },
432 ProcessingEvent::Object {
433 id: (1, 0),
434 object: PdfObject::Null,
435 },
436 ProcessingEvent::Page(PageData {
437 number: 0,
438 width: 595.0,
439 height: 842.0,
440 text: Some("test".to_string()),
441 operations: vec![],
442 }),
443 ProcessingEvent::Resource {
444 name: "Font1".to_string(),
445 resource_type: ResourceType::Font,
446 },
447 ProcessingEvent::End,
448 ];
449
450 for event in events {
451 let debug_str = format!("{event:?}");
452 assert!(!debug_str.is_empty());
453 }
454 }
455
456 #[test]
457 fn test_page_data_debug() {
458 let page_data = PageData {
459 number: 5,
460 width: 612.0,
461 height: 792.0,
462 text: Some("Page content".to_string()),
463 operations: vec![ContentOperation::BeginText],
464 };
465
466 let debug_str = format!("{page_data:?}");
467 assert!(debug_str.contains("PageData"));
468 assert!(debug_str.contains("5"));
469 assert!(debug_str.contains("612.0"));
470 assert!(debug_str.contains("Page content"));
471 }
472
473 #[test]
474 fn test_resource_type_debug_clone() {
475 let resource_types = vec![
476 ResourceType::Font,
477 ResourceType::Image,
478 ResourceType::ColorSpace,
479 ResourceType::Pattern,
480 ResourceType::XObject,
481 ];
482
483 for resource_type in resource_types {
484 let debug_str = format!("{resource_type:?}");
485 assert!(!debug_str.is_empty());
486
487 let cloned = resource_type.clone();
488 let cloned_debug = format!("{cloned:?}");
489 assert_eq!(debug_str, cloned_debug);
490 }
491 }
492
493 #[test]
494 fn test_processing_action_debug_partial_eq() {
495 let action = ProcessingAction::Continue;
496
497 let debug_str = format!("{action:?}");
498 assert!(debug_str.contains("Continue"));
499
500 assert_eq!(ProcessingAction::Continue, ProcessingAction::Continue);
501 assert_eq!(ProcessingAction::Skip, ProcessingAction::Skip);
502 assert_eq!(ProcessingAction::Stop, ProcessingAction::Stop);
503
504 assert_ne!(ProcessingAction::Continue, ProcessingAction::Skip);
505 assert_ne!(ProcessingAction::Skip, ProcessingAction::Stop);
506 assert_ne!(ProcessingAction::Stop, ProcessingAction::Continue);
507 }
508
509 #[test]
510 fn test_stream_processor_invalid_header() {
511 let data = b"Not a PDF\n";
512 let cursor = Cursor::new(data);
513 let options = StreamingOptions::default();
514 let mut processor = StreamProcessor::new(cursor, options);
515
516 let result = processor.process_with(|_event| Ok(ProcessingAction::Continue));
517
518 assert!(result.is_err());
519 match result {
520 Err(PdfError::InvalidHeader) => {}
521 _ => panic!("Expected InvalidHeader error"),
522 }
523 }
524
525 #[test]
526 fn test_stream_processor_header_parsing() {
527 let data = b"%PDF-2.0\n";
528 let cursor = Cursor::new(data);
529 let options = StreamingOptions::default();
530 let mut processor = StreamProcessor::new(cursor, options);
531
532 let mut header_version = String::new();
533
534 processor
535 .process_with(|event| {
536 if let ProcessingEvent::Header { version } = event {
537 header_version = version;
538 }
539 Ok(ProcessingAction::Continue)
540 })
541 .unwrap();
542
543 assert_eq!(header_version, "2.0");
544 }
545
546 #[test]
547 fn test_skip_processing_action() {
548 let data = b"%PDF-1.7\n";
549 let cursor = Cursor::new(data);
550 let options = StreamingOptions::default();
551 let mut processor = StreamProcessor::new(cursor, options);
552
553 let mut page_count = 0;
554 let mut skipped_count = 0;
555
556 processor
557 .process_pages(|index, _page| {
558 if index % 2 == 0 {
559 page_count += 1;
560 Ok(ProcessingAction::Continue)
561 } else {
562 skipped_count += 1;
563 Ok(ProcessingAction::Skip)
564 }
565 })
566 .unwrap();
567
568 assert!(page_count > 0);
569 assert!(skipped_count > 0);
570 }
571
572 #[test]
573 fn test_extract_text_streaming_with_output() {
574 let data = b"%PDF-1.7\n";
575 let cursor = Cursor::new(data);
576 let options = StreamingOptions::default();
577 let mut processor = StreamProcessor::new(cursor, options);
578
579 let mut output = Vec::new();
580 processor.extract_text_streaming(&mut output).unwrap();
581
582 let text = String::from_utf8(output).unwrap();
583
584 assert!(text.contains("Page 1 content"));
586 assert!(text.contains("Page 2 content"));
587 assert!(text.contains("Page 3 content"));
588
589 assert!(text.contains('\n'));
591 }
592
593 #[test]
594 fn test_content_stream_processor_creation() {
595 let options = StreamingOptions {
596 buffer_size: 2048,
597 max_stream_size: 4096,
598 skip_images: true,
599 skip_fonts: false,
600 };
601
602 let processor = ContentStreamProcessor::new(options.clone());
603
604 assert_eq!(processor.buffer.capacity(), options.buffer_size);
605 assert_eq!(processor.options.buffer_size, 2048);
606 assert_eq!(processor.options.max_stream_size, 4096);
607 assert!(processor.options.skip_images);
608 assert!(!processor.options.skip_fonts);
609 }
610
611 #[test]
612 fn test_content_stream_processor_empty_stream() {
613 let options = StreamingOptions::default();
614 let mut processor = ContentStreamProcessor::new(options);
615
616 let content = b"";
617 let cursor = Cursor::new(content);
618
619 let mut op_count = 0;
620 processor
621 .process_stream(cursor, |_op| {
622 op_count += 1;
623 Ok(ProcessingAction::Continue)
624 })
625 .unwrap();
626
627 assert_eq!(op_count, 0);
628 }
629
630 #[test]
631 fn test_content_stream_processor_large_stream_error() {
632 let options = StreamingOptions {
633 buffer_size: 1024,
634 max_stream_size: 10, skip_images: false,
636 skip_fonts: false,
637 };
638
639 let mut processor = ContentStreamProcessor::new(options);
640
641 let content = b"BT /F1 12 Tf 100 700 Td (This is a long content stream) Tj ET";
643 let cursor = Cursor::new(content);
644
645 let result = processor.process_stream(cursor, |_op| Ok(ProcessingAction::Continue));
646
647 assert!(result.is_err());
648 match result {
649 Err(PdfError::ContentStreamTooLarge(size)) => {
650 assert_eq!(size, content.len());
651 }
652 _ => panic!("Expected ContentStreamTooLarge error"),
653 }
654 }
655
656 #[test]
657 fn test_content_stream_processor_skip_action() {
658 let options = StreamingOptions::default();
659 let mut processor = ContentStreamProcessor::new(options);
660
661 let content = b"BT /F1 12 Tf 100 700 Td (Hello) Tj 50 0 Td (World) Tj ET";
662 let cursor = Cursor::new(content);
663
664 let mut processed_count = 0;
665 let mut skipped_count = 0;
666
667 processor
668 .process_stream(cursor, |op| match op {
669 ContentOperation::ShowText(_) => {
670 skipped_count += 1;
671 Ok(ProcessingAction::Skip)
672 }
673 _ => {
674 processed_count += 1;
675 Ok(ProcessingAction::Continue)
676 }
677 })
678 .unwrap();
679
680 assert!(processed_count > 0);
681 assert!(skipped_count > 0);
682 }
683
684 #[test]
685 fn test_content_stream_processor_stop_action() {
686 let options = StreamingOptions::default();
687 let mut processor = ContentStreamProcessor::new(options);
688
689 let content = b"BT /F1 12 Tf 100 700 Td (Hello) Tj 50 0 Td (World) Tj ET";
690 let cursor = Cursor::new(content);
691
692 let mut op_count = 0;
693
694 processor
695 .process_stream(cursor, |_op| {
696 op_count += 1;
697 if op_count >= 3 {
698 Ok(ProcessingAction::Stop)
699 } else {
700 Ok(ProcessingAction::Continue)
701 }
702 })
703 .unwrap();
704
705 assert_eq!(op_count, 3);
706 }
707
708 #[test]
709 fn test_content_stream_processor_invalid_content() {
710 let options = StreamingOptions::default();
711 let mut processor = ContentStreamProcessor::new(options);
712
713 let content = b"Invalid PDF content that cannot be parsed";
714 let cursor = Cursor::new(content);
715
716 let result = processor.process_stream(cursor, |_op| Ok(ProcessingAction::Continue));
717
718 match result {
720 Ok(_) => {} Err(PdfError::ParseError(_)) => {} _ => panic!("Unexpected error type"),
723 }
724 }
725
726 #[test]
727 fn test_content_stream_processor_callback_error() {
728 let options = StreamingOptions::default();
729 let mut processor = ContentStreamProcessor::new(options);
730
731 let content = b"BT /F1 12 Tf ET";
732 let cursor = Cursor::new(content);
733
734 let result = processor.process_stream(cursor, |_op| {
735 Err(PdfError::ParseError("Test error".to_string()))
736 });
737
738 assert!(result.is_err());
739 match result {
740 Err(PdfError::ParseError(msg)) => {
741 assert_eq!(msg, "Test error");
742 }
743 _ => panic!("Expected ParseError"),
744 }
745 }
746
747 #[test]
748 fn test_stream_processor_with_custom_buffer_size() {
749 let options = StreamingOptions {
750 buffer_size: 128,
751 max_stream_size: 1024,
752 skip_images: false,
753 skip_fonts: false,
754 };
755
756 let data = b"%PDF-1.4\n";
757 let cursor = Cursor::new(data);
758 let mut processor = StreamProcessor::new(cursor, options);
759
760 let mut header_found = false;
761
762 processor
763 .process_with(|event| {
764 if let ProcessingEvent::Header { version } = event {
765 assert_eq!(version, "1.4");
766 header_found = true;
767 }
768 Ok(ProcessingAction::Continue)
769 })
770 .unwrap();
771
772 assert!(header_found);
773 }
774
775 #[test]
776 fn test_processing_with_all_event_types() {
777 let data = b"%PDF-1.7\n";
778 let cursor = Cursor::new(data);
779 let options = StreamingOptions::default();
780 let mut processor = StreamProcessor::new(cursor, options);
781
782 let mut event_types = Vec::new();
783
784 processor
785 .process_with(|event| {
786 match event {
787 ProcessingEvent::Start => event_types.push("start"),
788 ProcessingEvent::Header { .. } => event_types.push("header"),
789 ProcessingEvent::Object { .. } => event_types.push("object"),
790 ProcessingEvent::Page(_) => event_types.push("page"),
791 ProcessingEvent::Resource { .. } => event_types.push("resource"),
792 ProcessingEvent::End => event_types.push("end"),
793 }
794 Ok(ProcessingAction::Continue)
795 })
796 .unwrap();
797
798 assert!(event_types.contains(&"start"));
799 assert!(event_types.contains(&"header"));
800 assert!(event_types.contains(&"page"));
801 assert!(event_types.contains(&"end"));
802 }
803
804 #[test]
805 fn test_page_data_with_operations() {
806 let page_data = PageData {
807 number: 0,
808 width: 595.0,
809 height: 842.0,
810 text: Some("Test page".to_string()),
811 operations: vec![ContentOperation::BeginText, ContentOperation::EndText],
812 };
813
814 assert_eq!(page_data.number, 0);
815 assert_eq!(page_data.width, 595.0);
816 assert_eq!(page_data.height, 842.0);
817 assert_eq!(page_data.text, Some("Test page".to_string()));
818 assert_eq!(page_data.operations.len(), 2);
819 }
820
821 #[test]
822 fn test_page_data_without_text() {
823 let page_data = PageData {
824 number: 1,
825 width: 612.0,
826 height: 792.0,
827 text: None,
828 operations: vec![],
829 };
830
831 assert_eq!(page_data.number, 1);
832 assert_eq!(page_data.text, None);
833 assert!(page_data.operations.is_empty());
834 }
835
836 #[test]
837 fn test_extract_text_streaming_no_text() {
838 let data = b"%PDF-1.7\n";
840 let cursor = Cursor::new(data);
841 let options = StreamingOptions::default();
842 let mut processor = StreamProcessor::new(cursor, options);
843
844 let mut pages_processed = 0;
846
847 processor
848 .process_pages(|_index, page| {
849 pages_processed += 1;
850 assert!(page.text.is_some()); Ok(ProcessingAction::Continue)
852 })
853 .unwrap();
854
855 assert!(pages_processed > 0);
856 }
857
858 #[test]
859 fn test_stream_processor_io_error() {
860 use std::io::Error;
861
862 struct ErrorReader;
863 impl Read for ErrorReader {
864 fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
865 Err(Error::other("IO Error"))
866 }
867 }
868 impl Seek for ErrorReader {
869 fn seek(&mut self, _pos: std::io::SeekFrom) -> std::io::Result<u64> {
870 Ok(0)
871 }
872 }
873
874 let options = StreamingOptions::default();
875 let mut processor = StreamProcessor::new(ErrorReader, options);
876
877 let result = processor.process_with(|_event| Ok(ProcessingAction::Continue));
878 assert!(result.is_err());
879 }
880
881 #[test]
882 fn test_content_stream_processor_buffer_reuse() {
883 let options = StreamingOptions::default();
884 let mut processor = ContentStreamProcessor::new(options);
885
886 let content1 = b"BT (First) Tj ET";
888 let cursor1 = Cursor::new(content1);
889
890 let mut first_ops = Vec::new();
891 processor
892 .process_stream(cursor1, |op| {
893 first_ops.push(format!("{op:?}"));
894 Ok(ProcessingAction::Continue)
895 })
896 .unwrap();
897
898 let content2 = b"BT (Second) Tj ET";
900 let cursor2 = Cursor::new(content2);
901
902 let mut second_ops = Vec::new();
903 processor
904 .process_stream(cursor2, |op| {
905 second_ops.push(format!("{op:?}"));
906 Ok(ProcessingAction::Continue)
907 })
908 .unwrap();
909
910 assert!(!first_ops.is_empty());
911 assert!(!second_ops.is_empty());
912 assert_ne!(first_ops, second_ops);
914 }
915}