use crate::error::Result;
use crate::graphics::Color;
use crate::text::Font;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ResourceKey {
resource_type: ResourceType,
hash: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum ResourceType {
Font,
Image,
Pattern,
#[allow(dead_code)]
ColorSpace,
}
#[derive(Debug, Clone)]
pub struct FontResource {
pub font: Font,
pub size: f64,
pub color: Color,
pub encoding: String,
pub embedded_data: Option<Arc<Vec<u8>>>,
pub character_set: Option<Vec<char>>, }
impl FontResource {
pub fn new(font: Font, size: f64) -> Self {
Self {
font,
size,
color: Color::black(),
encoding: "WinAnsiEncoding".to_string(),
embedded_data: None,
character_set: None,
}
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn with_encoding(mut self, encoding: String) -> Self {
self.encoding = encoding;
self
}
pub fn with_embedded_data(mut self, data: Vec<u8>) -> Self {
self.embedded_data = Some(Arc::new(data));
self
}
pub fn with_character_set(mut self, chars: Vec<char>) -> Self {
self.character_set = Some(chars);
self
}
fn calculate_hash(&self) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut hasher = DefaultHasher::new();
self.font.hash(&mut hasher);
self.size.to_bits().hash(&mut hasher);
self.color.r().to_bits().hash(&mut hasher);
self.color.g().to_bits().hash(&mut hasher);
self.color.b().to_bits().hash(&mut hasher);
self.encoding.hash(&mut hasher);
if let Some(data) = &self.embedded_data {
data.len().hash(&mut hasher);
if data.len() > 128 {
data[..64].hash(&mut hasher);
data[data.len() - 64..].hash(&mut hasher);
} else {
data.as_slice().hash(&mut hasher);
}
}
hasher.finish()
}
}
#[derive(Debug, Clone)]
pub struct ImageResource {
pub data: Arc<Vec<u8>>,
pub width: u32,
pub height: u32,
pub format: ImageFormat,
pub color_space: String,
pub compression: CompressionType,
pub dpi: Option<(u32, u32)>,
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum ImageFormat {
Jpeg,
Png,
Tiff,
Bmp,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CompressionType {
None,
Flate,
DCT,
CCITT,
}
impl ImageResource {
pub fn new(data: Vec<u8>, width: u32, height: u32, format: ImageFormat) -> Self {
let compression = match format {
ImageFormat::Jpeg => CompressionType::DCT,
ImageFormat::Png => CompressionType::Flate,
_ => CompressionType::None,
};
Self {
data: Arc::new(data),
width,
height,
format,
color_space: "DeviceRGB".to_string(),
compression,
dpi: None,
}
}
pub fn with_dpi(mut self, x_dpi: u32, y_dpi: u32) -> Self {
self.dpi = Some((x_dpi, y_dpi));
self
}
pub fn with_color_space(mut self, color_space: String) -> Self {
self.color_space = color_space;
self
}
fn calculate_hash(&self) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut hasher = DefaultHasher::new();
self.width.hash(&mut hasher);
self.height.hash(&mut hasher);
self.format.hash(&mut hasher);
self.color_space.hash(&mut hasher);
if self.data.len() > 1024 {
self.data[..512].hash(&mut hasher);
self.data[self.data.len() - 512..].hash(&mut hasher);
self.data.len().hash(&mut hasher);
} else {
self.data.as_slice().hash(&mut hasher);
}
hasher.finish()
}
}
#[derive(Debug, Clone)]
pub struct PatternResource {
pub pattern_type: PatternType,
pub colors: Vec<Color>,
pub coordinates: Vec<f64>,
pub matrix: [f64; 6],
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub enum PatternType {
LinearGradient,
RadialGradient,
Mesh,
Tiling,
}
impl PatternResource {
pub fn linear_gradient(start: (f64, f64), end: (f64, f64), colors: Vec<Color>) -> Self {
Self {
pattern_type: PatternType::LinearGradient,
colors,
coordinates: vec![start.0, start.1, end.0, end.1],
matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], }
}
pub fn radial_gradient(center: (f64, f64), radius: f64, colors: Vec<Color>) -> Self {
Self {
pattern_type: PatternType::RadialGradient,
colors,
coordinates: vec![center.0, center.1, radius],
matrix: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
}
}
fn calculate_hash(&self) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut hasher = DefaultHasher::new();
self.pattern_type.hash(&mut hasher);
for color in &self.colors {
color.r().to_bits().hash(&mut hasher);
color.g().to_bits().hash(&mut hasher);
color.b().to_bits().hash(&mut hasher);
}
for coord in &self.coordinates {
coord.to_bits().hash(&mut hasher);
}
for val in &self.matrix {
val.to_bits().hash(&mut hasher);
}
hasher.finish()
}
}
pub struct ResourcePool {
fonts: RwLock<HashMap<ResourceKey, Arc<FontResource>>>,
images: RwLock<HashMap<ResourceKey, Arc<ImageResource>>>,
patterns: RwLock<HashMap<ResourceKey, Arc<PatternResource>>>,
stats: RwLock<ResourcePoolStats>,
}
impl ResourcePool {
pub fn new() -> Self {
Self {
fonts: RwLock::new(HashMap::new()),
images: RwLock::new(HashMap::new()),
patterns: RwLock::new(HashMap::new()),
stats: RwLock::new(ResourcePoolStats::default()),
}
}
pub fn add_font_resource(&self, resource: FontResource) -> Result<ResourceKey> {
let hash = resource.calculate_hash();
let key = ResourceKey {
resource_type: ResourceType::Font,
hash,
};
let mut fonts = self.fonts.write().map_err(|_| {
crate::error::PdfError::InvalidStructure(
"Resource pool lock poisoned (fonts)".to_string(),
)
})?;
let mut stats = self.stats.write().map_err(|_| {
crate::error::PdfError::InvalidStructure(
"Resource pool lock poisoned (stats)".to_string(),
)
})?;
if fonts.contains_key(&key) {
stats.font_duplicates_avoided += 1;
stats.total_duplicates_avoided += 1;
} else {
fonts.insert(key.clone(), Arc::new(resource));
stats.unique_fonts += 1;
stats.total_unique_resources += 1;
}
stats.total_font_requests += 1;
stats.total_requests += 1;
Ok(key)
}
pub fn add_image_resource(&self, resource: ImageResource) -> Result<ResourceKey> {
let hash = resource.calculate_hash();
let key = ResourceKey {
resource_type: ResourceType::Image,
hash,
};
let mut images = self.images.write().map_err(|_| {
crate::error::PdfError::InvalidStructure(
"Resource pool lock poisoned (images)".to_string(),
)
})?;
let mut stats = self.stats.write().map_err(|_| {
crate::error::PdfError::InvalidStructure(
"Resource pool lock poisoned (stats)".to_string(),
)
})?;
if images.contains_key(&key) {
stats.image_duplicates_avoided += 1;
stats.total_duplicates_avoided += 1;
} else {
images.insert(key.clone(), Arc::new(resource));
stats.unique_images += 1;
stats.total_unique_resources += 1;
}
stats.total_image_requests += 1;
stats.total_requests += 1;
Ok(key)
}
pub fn add_pattern_resource(&self, resource: PatternResource) -> Result<ResourceKey> {
let hash = resource.calculate_hash();
let key = ResourceKey {
resource_type: ResourceType::Pattern,
hash,
};
let mut patterns = self.patterns.write().map_err(|_| {
crate::error::PdfError::InvalidStructure(
"Resource pool lock poisoned (patterns)".to_string(),
)
})?;
let mut stats = self.stats.write().map_err(|_| {
crate::error::PdfError::InvalidStructure(
"Resource pool lock poisoned (stats)".to_string(),
)
})?;
if patterns.contains_key(&key) {
stats.pattern_duplicates_avoided += 1;
stats.total_duplicates_avoided += 1;
} else {
patterns.insert(key.clone(), Arc::new(resource));
stats.unique_patterns += 1;
stats.total_unique_resources += 1;
}
stats.total_pattern_requests += 1;
stats.total_requests += 1;
Ok(key)
}
pub fn get_font(&self, key: &ResourceKey) -> Option<Arc<FontResource>> {
self.fonts.read().ok()?.get(key).cloned()
}
pub fn get_image(&self, key: &ResourceKey) -> Option<Arc<ImageResource>> {
self.images.read().ok()?.get(key).cloned()
}
pub fn get_pattern(&self, key: &ResourceKey) -> Option<Arc<PatternResource>> {
self.patterns.read().ok()?.get(key).cloned()
}
pub fn stats(&self) -> ResourcePoolStats {
self.stats
.read()
.map(|guard| guard.clone())
.unwrap_or_default()
}
pub fn clear(&self) {
if let Ok(mut guard) = self.fonts.write() {
guard.clear();
}
if let Ok(mut guard) = self.images.write() {
guard.clear();
}
if let Ok(mut guard) = self.patterns.write() {
guard.clear();
}
if let Ok(mut guard) = self.stats.write() {
*guard = ResourcePoolStats::default();
}
}
pub fn memory_usage(&self) -> usize {
let fonts = match self.fonts.read() {
Ok(guard) => guard,
Err(_) => return 0, };
let images = match self.images.read() {
Ok(guard) => guard,
Err(_) => return 0,
};
let patterns = match self.patterns.read() {
Ok(guard) => guard,
Err(_) => return 0,
};
let font_memory: usize = fonts
.values()
.map(|f| f.embedded_data.as_ref().map_or(1024, |d| d.len()))
.sum();
let image_memory: usize = images.values().map(|i| i.data.len()).sum();
let pattern_memory: usize = patterns.len() * 512;
font_memory + image_memory + pattern_memory
}
}
impl Default for ResourcePool {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct ResourcePoolStats {
pub total_requests: u64,
pub total_unique_resources: u64,
pub total_duplicates_avoided: u64,
pub total_font_requests: u64,
pub unique_fonts: u64,
pub font_duplicates_avoided: u64,
pub total_image_requests: u64,
pub unique_images: u64,
pub image_duplicates_avoided: u64,
pub total_pattern_requests: u64,
pub unique_patterns: u64,
pub pattern_duplicates_avoided: u64,
}
impl ResourcePoolStats {
pub fn deduplication_ratio(&self) -> f64 {
if self.total_requests == 0 {
return 0.0;
}
self.total_duplicates_avoided as f64 / self.total_requests as f64
}
pub fn font_deduplication_ratio(&self) -> f64 {
if self.total_font_requests == 0 {
return 0.0;
}
self.font_duplicates_avoided as f64 / self.total_font_requests as f64
}
pub fn image_deduplication_ratio(&self) -> f64 {
if self.total_image_requests == 0 {
return 0.0;
}
self.image_duplicates_avoided as f64 / self.total_image_requests as f64
}
pub fn summary(&self) -> String {
format!(
"Resource Pool Stats:\n\
- Total Requests: {} (Unique: {}, Duplicates Avoided: {})\n\
- Fonts: {} requests, {} unique, {:.1}% deduplicated\n\
- Images: {} requests, {} unique, {:.1}% deduplicated\n\
- Patterns: {} requests, {} unique, {:.1}% deduplicated\n\
- Overall Deduplication: {:.1}%",
self.total_requests,
self.total_unique_resources,
self.total_duplicates_avoided,
self.total_font_requests,
self.unique_fonts,
self.font_deduplication_ratio() * 100.0,
self.total_image_requests,
self.unique_images,
self.image_deduplication_ratio() * 100.0,
self.total_pattern_requests,
self.unique_patterns,
self.pattern_duplicates_avoided as f64 / self.total_pattern_requests.max(1) as f64
* 100.0,
self.deduplication_ratio() * 100.0
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_pool_creation() {
let pool = ResourcePool::new();
let stats = pool.stats();
assert_eq!(stats.total_requests, 0);
assert_eq!(stats.total_unique_resources, 0);
}
#[test]
fn test_font_resource_deduplication() {
let pool = ResourcePool::new();
let font1 = FontResource::new(Font::Helvetica, 12.0);
let font2 = FontResource::new(Font::Helvetica, 12.0); let font3 = FontResource::new(Font::Helvetica, 14.0);
let key1 = pool.add_font_resource(font1).unwrap();
let key2 = pool.add_font_resource(font2).unwrap();
let key3 = pool.add_font_resource(font3).unwrap();
assert_eq!(key1, key2);
assert_ne!(key1, key3);
let stats = pool.stats();
assert_eq!(stats.total_font_requests, 3);
assert_eq!(stats.unique_fonts, 2);
assert_eq!(stats.font_duplicates_avoided, 1);
}
#[test]
fn test_image_resource_deduplication() {
let pool = ResourcePool::new();
let data = vec![1, 2, 3, 4];
let image1 = ImageResource::new(data.clone(), 100, 100, ImageFormat::Jpeg);
let image2 = ImageResource::new(data.clone(), 100, 100, ImageFormat::Jpeg);
let image3 = ImageResource::new(data, 200, 200, ImageFormat::Jpeg);
let key1 = pool.add_image_resource(image1).unwrap();
let key2 = pool.add_image_resource(image2).unwrap();
let key3 = pool.add_image_resource(image3).unwrap();
assert_eq!(key1, key2);
assert_ne!(key1, key3);
let stats = pool.stats();
assert_eq!(stats.total_image_requests, 3);
assert_eq!(stats.unique_images, 2);
assert_eq!(stats.image_duplicates_avoided, 1);
}
#[test]
fn test_pattern_resource_deduplication() {
let pool = ResourcePool::new();
let colors = vec![Color::red(), Color::blue()];
let pattern1 = PatternResource::linear_gradient((0.0, 0.0), (100.0, 0.0), colors.clone());
let pattern2 = PatternResource::linear_gradient((0.0, 0.0), (100.0, 0.0), colors.clone());
let pattern3 = PatternResource::radial_gradient((50.0, 50.0), 25.0, colors);
let key1 = pool.add_pattern_resource(pattern1).unwrap();
let key2 = pool.add_pattern_resource(pattern2).unwrap();
let key3 = pool.add_pattern_resource(pattern3).unwrap();
assert_eq!(key1, key2);
assert_ne!(key1, key3);
let stats = pool.stats();
assert_eq!(stats.total_pattern_requests, 3);
assert_eq!(stats.unique_patterns, 2);
assert_eq!(stats.pattern_duplicates_avoided, 1);
}
#[test]
fn test_resource_retrieval() {
let pool = ResourcePool::new();
let font = FontResource::new(Font::Helvetica, 12.0);
let key = pool.add_font_resource(font).unwrap();
let retrieved = pool.get_font(&key);
assert!(retrieved.is_some());
let retrieved_font = retrieved.unwrap();
assert_eq!(retrieved_font.font, Font::Helvetica);
assert_eq!(retrieved_font.size, 12.0);
}
#[test]
fn test_deduplication_ratio() {
let pool = ResourcePool::new();
let font = FontResource::new(Font::Helvetica, 12.0);
for _ in 0..5 {
pool.add_font_resource(font.clone()).unwrap();
}
let stats = pool.stats();
assert_eq!(stats.total_font_requests, 5);
assert_eq!(stats.unique_fonts, 1);
assert_eq!(stats.font_duplicates_avoided, 4);
assert_eq!(stats.font_deduplication_ratio(), 0.8); }
#[test]
fn test_memory_usage_estimation() {
let pool = ResourcePool::new();
let data = vec![0u8; 1024]; let image = ImageResource::new(data, 100, 100, ImageFormat::Png);
pool.add_image_resource(image).unwrap();
let memory = pool.memory_usage();
assert!(memory >= 1024); }
#[test]
fn test_pool_clear() {
let pool = ResourcePool::new();
let font = FontResource::new(Font::Helvetica, 12.0);
pool.add_font_resource(font).unwrap();
assert_eq!(pool.stats().unique_fonts, 1);
pool.clear();
assert_eq!(pool.stats().unique_fonts, 0);
}
}