use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;
use crate::color::{IccProfile, RenderingIntent, Transform};
use crate::document::PdfDocument;
use crate::object::Object;
pub(crate) struct IccTransformCache {
entries: RefCell<HashMap<(u64, RenderingIntent), Arc<Transform>>>,
#[cfg(feature = "test-support")]
pub(crate) build_count: std::cell::Cell<usize>,
}
impl IccTransformCache {
pub(crate) fn new() -> Self {
Self {
entries: RefCell::new(HashMap::new()),
#[cfg(feature = "test-support")]
build_count: std::cell::Cell::new(0),
}
}
pub(crate) fn get_or_build(
&self,
profile: &Arc<IccProfile>,
intent: RenderingIntent,
) -> Arc<Transform> {
let key = (profile.content_hash(), intent);
if let Some(t) = self.entries.borrow().get(&key).cloned() {
return t;
}
let t = Arc::new(Transform::new_srgb_target(Arc::clone(profile), intent));
self.entries.borrow_mut().insert(key, Arc::clone(&t));
#[cfg(feature = "test-support")]
self.build_count.set(self.build_count.get() + 1);
t
}
pub(crate) fn clear(&self) {
self.entries.borrow_mut().clear();
#[cfg(feature = "test-support")]
self.build_count.set(0);
}
#[cfg(feature = "test-support")]
pub(crate) fn build_count(&self) -> usize {
self.build_count.get()
}
}
impl Default for IccTransformCache {
fn default() -> Self {
Self::new()
}
}
pub(crate) struct ResolutionContext<'a> {
pub(crate) doc: &'a PdfDocument,
pub(crate) color_spaces: &'a HashMap<String, Object>,
pub(crate) output_intent_cmyk: Option<&'a Arc<IccProfile>>,
pub(crate) rendering_intent: RenderingIntent,
pub(crate) default_gray: Option<&'a Object>,
pub(crate) default_rgb: Option<&'a Object>,
pub(crate) default_cmyk: Option<&'a Object>,
pub(crate) icc_transform_cache: Option<&'a IccTransformCache>,
}
impl<'a> ResolutionContext<'a> {
pub(crate) fn new(doc: &'a PdfDocument, color_spaces: &'a HashMap<String, Object>) -> Self {
Self {
doc,
color_spaces,
output_intent_cmyk: None,
rendering_intent: RenderingIntent::default(),
default_gray: None,
default_rgb: None,
default_cmyk: None,
icc_transform_cache: None,
}
}
pub(crate) fn with_icc_transform_cache(mut self, cache: Option<&'a IccTransformCache>) -> Self {
self.icc_transform_cache = cache;
self
}
pub(crate) fn with_output_intent(mut self, profile: Option<&'a Arc<IccProfile>>) -> Self {
self.output_intent_cmyk = profile;
self
}
pub(crate) fn with_rendering_intent(mut self, intent: RenderingIntent) -> Self {
self.rendering_intent = intent;
self
}
pub(crate) fn with_defaults(
mut self,
gray: Option<&'a Object>,
rgb: Option<&'a Object>,
cmyk: Option<&'a Object>,
) -> Self {
self.default_gray = gray;
self.default_rgb = rgb;
self.default_cmyk = cmyk;
self
}
}
#[cfg(test)]
mod tests {
use super::super::test_support::fixture_doc;
use super::*;
#[test]
fn context_carries_empty_color_spaces() {
let doc = fixture_doc();
let color_spaces = HashMap::new();
let ctx = ResolutionContext::new(&doc, &color_spaces);
assert!(ctx.color_spaces.is_empty());
assert!(ctx.output_intent_cmyk.is_none());
assert_eq!(ctx.rendering_intent, RenderingIntent::RelativeColorimetric);
assert!(ctx.default_gray.is_none());
assert!(ctx.default_rgb.is_none());
assert!(ctx.default_cmyk.is_none());
}
#[test]
fn context_borrows_color_space_map() {
let doc = fixture_doc();
let mut color_spaces = HashMap::new();
color_spaces.insert("CS1".to_string(), Object::Name("DeviceCMYK".to_string()));
let ctx = ResolutionContext::new(&doc, &color_spaces);
assert!(ctx.color_spaces.contains_key("CS1"));
let ctx2 = ResolutionContext::new(&doc, &color_spaces);
assert_eq!(ctx2.color_spaces.len(), 1);
}
#[test]
fn context_carries_output_intent_when_set() {
let doc = fixture_doc();
let color_spaces = HashMap::new();
let profile = Arc::new(
IccProfile::parse(super::tests::header_only_cmyk_profile_bytes(), 4)
.expect("header-only stub profile parses"),
);
let ctx = ResolutionContext::new(&doc, &color_spaces).with_output_intent(Some(&profile));
assert!(ctx.output_intent_cmyk.is_some());
}
#[test]
fn with_rendering_intent_overrides_default() {
let doc = fixture_doc();
let color_spaces = HashMap::new();
let ctx = ResolutionContext::new(&doc, &color_spaces)
.with_rendering_intent(RenderingIntent::AbsoluteColorimetric);
assert_eq!(ctx.rendering_intent, RenderingIntent::AbsoluteColorimetric);
}
#[test]
fn with_defaults_attaches_each_override_independently() {
let doc = fixture_doc();
let color_spaces = HashMap::new();
let gray = Object::Name("DeviceGray".to_string());
let cmyk = Object::Name("DeviceCMYK".to_string());
let ctx = ResolutionContext::new(&doc, &color_spaces).with_defaults(
Some(&gray),
None,
Some(&cmyk),
);
assert!(ctx.default_gray.is_some());
assert!(ctx.default_rgb.is_none());
assert!(ctx.default_cmyk.is_some());
}
pub(crate) fn header_only_cmyk_profile_bytes() -> Vec<u8> {
let mut v = vec![0u8; 128];
v[8..12].copy_from_slice(&0x04000000u32.to_be_bytes());
v[12..16].copy_from_slice(b"prtr");
v[16..20].copy_from_slice(b"CMYK");
v[20..24].copy_from_slice(b"Lab ");
v[36..40].copy_from_slice(b"acsp");
v
}
}