use crate::error::Result;
use std::collections::VecDeque;
use std::io::{BufReader, Read, Seek};
pub mod chunk_processor;
pub mod incremental_parser;
pub mod page_streamer;
pub mod text_streamer;
pub use chunk_processor::{
process_in_chunks, ChunkOptions, ChunkProcessor, ChunkType, ContentChunk,
};
pub use incremental_parser::{process_incrementally, IncrementalParser, ParseEvent};
pub use page_streamer::{PageStreamer, StreamingPage};
pub use text_streamer::{stream_text, TextChunk, TextStreamOptions, TextStreamer};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamingOptions {
pub buffer_size: usize,
pub page_cache_size: usize,
pub max_content_stream_size: usize,
pub progressive_hints: bool,
pub memory_limit: usize,
}
impl Default for StreamingOptions {
fn default() -> Self {
Self {
buffer_size: 256 * 1024, page_cache_size: 3, max_content_stream_size: 10 * 1024 * 1024, progressive_hints: true,
memory_limit: 100 * 1024 * 1024, }
}
}
impl StreamingOptions {
pub fn minimal_memory() -> Self {
Self {
buffer_size: 64 * 1024,
page_cache_size: 1,
max_content_stream_size: 1024 * 1024,
progressive_hints: false,
memory_limit: 10 * 1024 * 1024,
}
}
pub fn fast_processing() -> Self {
Self {
buffer_size: 1024 * 1024,
page_cache_size: 10,
max_content_stream_size: 50 * 1024 * 1024,
progressive_hints: true,
memory_limit: 500 * 1024 * 1024,
}
}
pub fn with_buffer_size(mut self, size: usize) -> Self {
self.buffer_size = size;
self
}
pub fn with_page_cache_size(mut self, size: usize) -> Self {
self.page_cache_size = size;
self
}
pub fn with_memory_limit(mut self, limit: usize) -> Self {
self.memory_limit = limit;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamingConfiguration {
pub requested: StreamingOptions,
pub effective: StreamingOptions,
pub progressive_hints_unsupported: bool,
}
pub struct StreamingDocument<R: Read + Seek> {
streamer: PageStreamer<BufReader<R>>,
options: StreamingOptions,
configuration: StreamingConfiguration,
page_cache: VecDeque<StreamingPage>,
memory_used: usize,
}
impl<R: Read + Seek> StreamingDocument<R> {
pub fn new(reader: R, options: StreamingOptions) -> Result<Self> {
if options.buffer_size > isize::MAX as usize {
return Err(crate::PdfError::InvalidOperation(
"StreamingOptions buffer_size exceeds the addressable buffer size".into(),
));
}
let mut effective = options.clone();
effective.buffer_size = options.buffer_size.max(1);
effective.progressive_hints = false;
let configuration = StreamingConfiguration {
progressive_hints_unsupported: options.progressive_hints,
requested: options,
effective: effective.clone(),
};
let options = effective;
let mut streamer = PageStreamer::with_content_limit(
BufReader::with_capacity(options.buffer_size.max(1), reader),
options.max_content_stream_size,
);
streamer.initialize()?;
Ok(Self {
streamer,
options,
configuration,
page_cache: VecDeque::new(),
memory_used: 0,
})
}
pub fn configuration(&self) -> &StreamingConfiguration {
&self.configuration
}
pub fn cached_pages(&self) -> usize {
self.page_cache.len()
}
pub fn next_page(&mut self) -> Result<Option<StreamingPage>> {
let Some(page) = self.streamer.next()? else {
return Ok(None);
};
let size = page.retained_bytes();
while !self.page_cache.is_empty()
&& (self.page_cache.len() >= self.options.page_cache_size
|| size > self.options.memory_limit.saturating_sub(self.memory_used))
{
if let Some(old) = self.page_cache.pop_front() {
self.memory_used -= old.retained_bytes();
}
}
if self.options.page_cache_size > 0
&& size <= self.options.memory_limit.saturating_sub(self.memory_used)
{
self.memory_used += size;
self.page_cache.push_back(page.clone());
}
Ok(Some(page))
}
pub fn process_pages<F>(&mut self, mut callback: F) -> Result<()>
where
F: FnMut(&StreamingPage) -> Result<()>,
{
while let Some(page) = self.next_page()? {
callback(&page)?;
}
Ok(())
}
pub fn memory_usage(&self) -> usize {
self.memory_used
}
pub fn clear_cache(&mut self) {
self.page_cache.clear();
self.memory_used = 0;
}
}
#[derive(Debug, Clone, Default)]
pub struct StreamingStats {
pub bytes_processed: usize,
pub pages_processed: u32,
pub objects_parsed: u32,
pub memory_used: usize,
pub peak_memory: usize,
pub cache_evictions: u32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_streaming_options_default() {
let options = StreamingOptions::default();
assert_eq!(options.buffer_size, 256 * 1024);
assert_eq!(options.page_cache_size, 3);
assert!(options.progressive_hints);
}
#[test]
fn test_streaming_options_minimal() {
let options = StreamingOptions::minimal_memory();
assert_eq!(options.buffer_size, 64 * 1024);
assert_eq!(options.page_cache_size, 1);
assert!(!options.progressive_hints);
assert_eq!(options.memory_limit, 10 * 1024 * 1024);
}
#[test]
fn test_streaming_options_fast() {
let options = StreamingOptions::fast_processing();
assert_eq!(options.buffer_size, 1024 * 1024);
assert_eq!(options.page_cache_size, 10);
assert!(options.progressive_hints);
}
#[test]
fn test_streaming_options_builder() {
let options = StreamingOptions::default()
.with_buffer_size(512 * 1024)
.with_page_cache_size(5)
.with_memory_limit(50 * 1024 * 1024);
assert_eq!(options.buffer_size, 512 * 1024);
assert_eq!(options.page_cache_size, 5);
assert_eq!(options.memory_limit, 50 * 1024 * 1024);
}
#[test]
fn test_streaming_stats() {
let stats = StreamingStats::default();
assert_eq!(stats.bytes_processed, 0);
assert_eq!(stats.pages_processed, 0);
assert_eq!(stats.objects_parsed, 0);
assert_eq!(stats.memory_used, 0);
assert_eq!(stats.peak_memory, 0);
assert_eq!(stats.cache_evictions, 0);
}
}