Skip to main content

oxidize_pdf/graphics/
color_profiles.rs

1//! ICC Color Profile support for PDF graphics according to ISO 32000-1 Section 8.6.5.5
2//!
3//! This module provides basic support for ICC-based color spaces including:
4//! - ICC profile embedding
5//! - Basic ICC color space definitions
6//! - RGB, CMYK, and Lab ICC profiles
7//! - Color space dictionaries
8
9use crate::error::{PdfError, Result};
10use crate::objects::{Dictionary, Object};
11use std::collections::HashMap;
12
13/// ICC color profile data
14#[derive(Debug, Clone)]
15pub struct IccProfile {
16    /// Profile name for referencing
17    pub name: String,
18    /// Raw ICC profile data
19    pub data: Vec<u8>,
20    /// Number of color components
21    pub components: u8,
22    /// Color space type (RGB, CMYK, Lab, etc.)
23    pub color_space: IccColorSpace,
24    /// Range array for color components
25    pub range: Option<Vec<f64>>,
26    /// Additional metadata
27    pub metadata: HashMap<String, String>,
28}
29
30/// ICC color space types
31#[derive(Debug, Clone, Copy, PartialEq)]
32pub enum IccColorSpace {
33    /// RGB color space (3 components)
34    Rgb,
35    /// CMYK color space (4 components)
36    Cmyk,
37    /// Lab color space (3 components)
38    Lab,
39    /// Gray color space (1 component)
40    Gray,
41    /// Generic multi-component color space
42    Generic(u8),
43}
44
45impl IccColorSpace {
46    /// Get the number of components for this color space
47    pub fn component_count(&self) -> u8 {
48        match self {
49            IccColorSpace::Gray => 1,
50            IccColorSpace::Rgb | IccColorSpace::Lab => 3,
51            IccColorSpace::Cmyk => 4,
52            IccColorSpace::Generic(n) => *n,
53        }
54    }
55
56    /// Get the PDF name for this color space type
57    pub fn pdf_name(&self) -> &'static str {
58        match self {
59            IccColorSpace::Gray => "DeviceGray",
60            IccColorSpace::Rgb => "DeviceRGB",
61            IccColorSpace::Cmyk => "DeviceCMYK",
62            IccColorSpace::Lab => "Lab",
63            IccColorSpace::Generic(_) => "ICCBased",
64        }
65    }
66
67    /// Get default range for color components
68    pub fn default_range(&self) -> Vec<f64> {
69        match self {
70            IccColorSpace::Gray => vec![0.0, 1.0],
71            IccColorSpace::Rgb => vec![0.0, 1.0, 0.0, 1.0, 0.0, 1.0],
72            IccColorSpace::Cmyk => vec![0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0],
73            IccColorSpace::Lab => vec![0.0, 100.0, -128.0, 127.0, -128.0, 127.0],
74            IccColorSpace::Generic(n) => {
75                let mut range = Vec::new();
76                for _ in 0..*n {
77                    range.extend_from_slice(&[0.0, 1.0]);
78                }
79                range
80            }
81        }
82    }
83}
84
85/// Standard ICC profiles commonly used in PDF
86#[derive(Debug, Clone, Copy, PartialEq)]
87pub enum StandardIccProfile {
88    /// sRGB color space
89    SRgb,
90    /// Adobe RGB (1998)
91    AdobeRgb,
92    /// ProPhoto RGB
93    ProPhotoRgb,
94    /// U.S. Web Coated (SWOP) v2
95    UswcSwopV2,
96    /// Coated FOGRA39 (ISO 12647-2:2004)
97    CoatedFogra39,
98    /// Uncoated FOGRA29 (ISO 12647-2:2004)  
99    UncoatedFogra29,
100    /// Generic Gray Gamma 2.2
101    GrayGamma22,
102}
103
104impl StandardIccProfile {
105    /// Get the color space for this standard profile
106    pub fn color_space(&self) -> IccColorSpace {
107        match self {
108            StandardIccProfile::SRgb
109            | StandardIccProfile::AdobeRgb
110            | StandardIccProfile::ProPhotoRgb => IccColorSpace::Rgb,
111            StandardIccProfile::UswcSwopV2
112            | StandardIccProfile::CoatedFogra39
113            | StandardIccProfile::UncoatedFogra29 => IccColorSpace::Cmyk,
114            StandardIccProfile::GrayGamma22 => IccColorSpace::Gray,
115        }
116    }
117
118    /// Get the standard name for this profile
119    pub fn profile_name(&self) -> &'static str {
120        match self {
121            StandardIccProfile::SRgb => "sRGB IEC61966-2.1",
122            StandardIccProfile::AdobeRgb => "Adobe RGB (1998)",
123            StandardIccProfile::ProPhotoRgb => "ProPhoto RGB",
124            StandardIccProfile::UswcSwopV2 => "U.S. Web Coated (SWOP) v2",
125            StandardIccProfile::CoatedFogra39 => "Coated FOGRA39 (ISO 12647-2:2004)",
126            StandardIccProfile::UncoatedFogra29 => "Uncoated FOGRA29 (ISO 12647-2:2004)",
127            StandardIccProfile::GrayGamma22 => "Generic Gray Gamma 2.2",
128        }
129    }
130
131    /// Get a minimal ICC profile data for this standard profile
132    /// Note: In a real implementation, these would be actual ICC profile files
133    pub fn minimal_profile_data(&self) -> Vec<u8> {
134        // This is a placeholder - real ICC profiles are binary data
135        // In production, you would embed actual ICC profile files
136        let profile_name = self.profile_name();
137        let mut data = Vec::new();
138
139        // ICC profile header (simplified)
140        data.extend_from_slice(b"ADSP"); // Profile CMM type
141        data.extend_from_slice(&[0; 124]); // Placeholder header
142
143        // Add profile description
144        data.extend_from_slice(profile_name.as_bytes());
145
146        // Pad to minimum size
147        while data.len() < 128 {
148            data.push(0);
149        }
150
151        data
152    }
153}
154
155impl IccProfile {
156    /// Create a new ICC profile
157    pub fn new(name: String, data: Vec<u8>, color_space: IccColorSpace) -> Self {
158        let components = color_space.component_count();
159
160        Self {
161            name,
162            data,
163            components,
164            color_space,
165            range: Some(color_space.default_range()),
166            metadata: HashMap::new(),
167        }
168    }
169
170    /// Create ICC profile from standard profile
171    pub fn from_standard(profile: StandardIccProfile) -> Self {
172        let color_space = profile.color_space();
173        let data = profile.minimal_profile_data();
174
175        Self::new(profile.profile_name().to_string(), data, color_space)
176    }
177
178    /// Set custom range for color components
179    pub fn with_range(mut self, range: Vec<f64>) -> Self {
180        // Validate range array length
181        let expected_len = (self.components as usize) * 2;
182        if range.len() == expected_len {
183            self.range = Some(range);
184        }
185        self
186    }
187
188    /// Add metadata to the profile
189    pub fn with_metadata(mut self, key: String, value: String) -> Self {
190        self.metadata.insert(key, value);
191        self
192    }
193
194    /// Generate ICC-based color space array for PDF
195    pub fn to_pdf_color_space_array(&self) -> Result<Vec<Object>> {
196        let mut array = Vec::new();
197
198        // Color space name
199        array.push(Object::Name("ICCBased".to_string()));
200
201        // ICC stream dictionary (simplified - would reference actual stream)
202        let mut icc_dict = Dictionary::new();
203        icc_dict.set("N", Object::Integer(self.components as i64));
204
205        // Alternate color space
206        icc_dict.set(
207            "Alternate",
208            Object::Name(self.color_space.pdf_name().to_string()),
209        );
210
211        // Range array
212        if let Some(ref range) = self.range {
213            let range_objects: Vec<Object> = range.iter().map(|&x| Object::Real(x)).collect();
214            icc_dict.set("Range", Object::Array(range_objects));
215        }
216
217        // Add metadata if present
218        if !self.metadata.is_empty() {
219            // In a real implementation, this would create a metadata dictionary
220            // For now, we'll add a simple description
221            if let Some(desc) = self.metadata.get("Description") {
222                icc_dict.set("Description", Object::String(desc.clone()));
223            }
224        }
225
226        array.push(Object::Dictionary(icc_dict));
227
228        Ok(array)
229    }
230
231    /// Validate ICC profile data
232    pub fn validate(&self) -> Result<()> {
233        // Basic validation
234        if self.data.is_empty() {
235            return Err(PdfError::InvalidStructure(
236                "ICC profile data cannot be empty".to_string(),
237            ));
238        }
239
240        if self.data.len() < 128 {
241            return Err(PdfError::InvalidStructure(
242                "ICC profile data too small (minimum 128 bytes)".to_string(),
243            ));
244        }
245
246        if self.components == 0 || self.components > 15 {
247            return Err(PdfError::InvalidStructure(
248                "Invalid number of color components".to_string(),
249            ));
250        }
251
252        // Validate range array if present
253        if let Some(ref range) = self.range {
254            let expected_len = (self.components as usize) * 2;
255            if range.len() != expected_len {
256                return Err(PdfError::InvalidStructure(format!(
257                    "Range array length {range_len} does not match expected {expected_len} for {components} components",
258                    range_len = range.len(),
259                    components = self.components
260                )));
261            }
262
263            // Check that min <= max for each component
264            for i in 0..self.components as usize {
265                let min = range[i * 2];
266                let max = range[i * 2 + 1];
267                if min > max {
268                    return Err(PdfError::InvalidStructure(format!(
269                        "Invalid range for component {i}: min {min} > max {max}"
270                    )));
271                }
272            }
273        }
274
275        Ok(())
276    }
277
278    /// Get profile size in bytes
279    pub fn size(&self) -> usize {
280        self.data.len()
281    }
282
283    /// Check if profile is for RGB color space
284    pub fn is_rgb(&self) -> bool {
285        matches!(self.color_space, IccColorSpace::Rgb)
286    }
287
288    /// Check if profile is for CMYK color space
289    pub fn is_cmyk(&self) -> bool {
290        matches!(self.color_space, IccColorSpace::Cmyk)
291    }
292
293    /// Check if profile is for grayscale
294    pub fn is_gray(&self) -> bool {
295        matches!(self.color_space, IccColorSpace::Gray)
296    }
297}
298
299/// ICC profile manager for handling multiple color profiles
300#[derive(Debug, Clone)]
301pub struct IccProfileManager {
302    /// Stored profiles
303    profiles: HashMap<String, IccProfile>,
304    /// Next profile ID
305    next_id: usize,
306}
307
308impl Default for IccProfileManager {
309    fn default() -> Self {
310        Self::new()
311    }
312}
313
314impl IccProfileManager {
315    /// Create a new ICC profile manager
316    pub fn new() -> Self {
317        Self {
318            profiles: HashMap::new(),
319            next_id: 1,
320        }
321    }
322
323    /// Add an ICC profile
324    pub fn add_profile(&mut self, mut profile: IccProfile) -> Result<String> {
325        // Validate profile before adding
326        profile.validate()?;
327
328        // Generate unique name if not provided or already exists
329        if profile.name.is_empty() || self.profiles.contains_key(&profile.name) {
330            profile.name = format!("ICC{}", self.next_id);
331            self.next_id += 1;
332        }
333
334        let name = profile.name.clone();
335        self.profiles.insert(name.clone(), profile);
336        Ok(name)
337    }
338
339    /// Add a standard ICC profile
340    pub fn add_standard_profile(&mut self, standard_profile: StandardIccProfile) -> Result<String> {
341        let profile = IccProfile::from_standard(standard_profile);
342        self.add_profile(profile)
343    }
344
345    /// Get an ICC profile by name
346    pub fn get_profile(&self, name: &str) -> Option<&IccProfile> {
347        self.profiles.get(name)
348    }
349
350    /// Get all profiles
351    pub fn profiles(&self) -> &HashMap<String, IccProfile> {
352        &self.profiles
353    }
354
355    /// Remove a profile
356    pub fn remove_profile(&mut self, name: &str) -> Option<IccProfile> {
357        self.profiles.remove(name)
358    }
359
360    /// Clear all profiles
361    pub fn clear(&mut self) {
362        self.profiles.clear();
363        self.next_id = 1;
364    }
365
366    /// Count of registered profiles
367    pub fn count(&self) -> usize {
368        self.profiles.len()
369    }
370
371    /// Generate ICC profile resource dictionary for PDF
372    pub fn to_resource_dictionary(&self) -> Result<String> {
373        if self.profiles.is_empty() {
374            return Ok(String::new());
375        }
376
377        let mut dict = String::from("/ColorSpace <<");
378
379        for name in self.profiles.keys() {
380            // In a real implementation, this would reference the color space object
381            dict.push_str(&format!(" /{} {} 0 R", name, self.next_id));
382        }
383
384        dict.push_str(" >>");
385        Ok(dict)
386    }
387
388    /// Get profiles by color space type
389    pub fn get_profiles_by_type(&self, color_space: IccColorSpace) -> Vec<&IccProfile> {
390        self.profiles
391            .values()
392            .filter(|profile| profile.color_space == color_space)
393            .collect()
394    }
395
396    /// Get RGB profiles
397    pub fn get_rgb_profiles(&self) -> Vec<&IccProfile> {
398        self.get_profiles_by_type(IccColorSpace::Rgb)
399    }
400
401    /// Get CMYK profiles
402    pub fn get_cmyk_profiles(&self) -> Vec<&IccProfile> {
403        self.get_profiles_by_type(IccColorSpace::Cmyk)
404    }
405
406    /// Get grayscale profiles
407    pub fn get_gray_profiles(&self) -> Vec<&IccProfile> {
408        self.get_profiles_by_type(IccColorSpace::Gray)
409    }
410
411    /// Create a default sRGB profile
412    pub fn create_default_srgb(&mut self) -> Result<String> {
413        self.add_standard_profile(StandardIccProfile::SRgb)
414    }
415
416    /// Create a default CMYK profile
417    pub fn create_default_cmyk(&mut self) -> Result<String> {
418        self.add_standard_profile(StandardIccProfile::CoatedFogra39)
419    }
420
421    /// Create a default grayscale profile
422    pub fn create_default_gray(&mut self) -> Result<String> {
423        self.add_standard_profile(StandardIccProfile::GrayGamma22)
424    }
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430
431    #[test]
432    fn test_icc_color_space_component_count() {
433        assert_eq!(IccColorSpace::Gray.component_count(), 1);
434        assert_eq!(IccColorSpace::Rgb.component_count(), 3);
435        assert_eq!(IccColorSpace::Cmyk.component_count(), 4);
436        assert_eq!(IccColorSpace::Lab.component_count(), 3);
437        assert_eq!(IccColorSpace::Generic(5).component_count(), 5);
438    }
439
440    #[test]
441    fn test_icc_color_space_pdf_names() {
442        assert_eq!(IccColorSpace::Gray.pdf_name(), "DeviceGray");
443        assert_eq!(IccColorSpace::Rgb.pdf_name(), "DeviceRGB");
444        assert_eq!(IccColorSpace::Cmyk.pdf_name(), "DeviceCMYK");
445        assert_eq!(IccColorSpace::Lab.pdf_name(), "Lab");
446        assert_eq!(IccColorSpace::Generic(3).pdf_name(), "ICCBased");
447    }
448
449    #[test]
450    fn test_icc_color_space_default_range() {
451        let gray_range = IccColorSpace::Gray.default_range();
452        assert_eq!(gray_range, vec![0.0, 1.0]);
453
454        let rgb_range = IccColorSpace::Rgb.default_range();
455        assert_eq!(rgb_range, vec![0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
456
457        let cmyk_range = IccColorSpace::Cmyk.default_range();
458        assert_eq!(cmyk_range, vec![0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0]);
459    }
460
461    #[test]
462    fn test_standard_icc_profile_properties() {
463        assert_eq!(StandardIccProfile::SRgb.color_space(), IccColorSpace::Rgb);
464        assert_eq!(
465            StandardIccProfile::UswcSwopV2.color_space(),
466            IccColorSpace::Cmyk
467        );
468        assert_eq!(
469            StandardIccProfile::GrayGamma22.color_space(),
470            IccColorSpace::Gray
471        );
472
473        assert_eq!(StandardIccProfile::SRgb.profile_name(), "sRGB IEC61966-2.1");
474        assert_eq!(
475            StandardIccProfile::AdobeRgb.profile_name(),
476            "Adobe RGB (1998)"
477        );
478    }
479
480    #[test]
481    fn test_standard_icc_profile_minimal_data() {
482        let profile_data = StandardIccProfile::SRgb.minimal_profile_data();
483        assert!(!profile_data.is_empty());
484        assert!(profile_data.len() >= 128);
485    }
486
487    #[test]
488    fn test_icc_profile_creation() {
489        let data = vec![0u8; 200];
490        let profile = IccProfile::new("TestProfile".to_string(), data.clone(), IccColorSpace::Rgb);
491
492        assert_eq!(profile.name, "TestProfile");
493        assert_eq!(profile.data, data);
494        assert_eq!(profile.components, 3);
495        assert_eq!(profile.color_space, IccColorSpace::Rgb);
496        assert!(profile.range.is_some());
497    }
498
499    #[test]
500    fn test_icc_profile_from_standard() {
501        let profile = IccProfile::from_standard(StandardIccProfile::SRgb);
502        assert_eq!(profile.name, "sRGB IEC61966-2.1");
503        assert_eq!(profile.color_space, IccColorSpace::Rgb);
504        assert_eq!(profile.components, 3);
505        assert!(!profile.data.is_empty());
506    }
507
508    #[test]
509    fn test_icc_profile_with_range() {
510        let data = vec![0u8; 200];
511        let custom_range = vec![0.0, 255.0, 0.0, 255.0, 0.0, 255.0];
512        let profile = IccProfile::new("TestProfile".to_string(), data, IccColorSpace::Rgb)
513            .with_range(custom_range.clone());
514
515        assert_eq!(profile.range, Some(custom_range));
516    }
517
518    #[test]
519    fn test_icc_profile_with_metadata() {
520        let data = vec![0u8; 200];
521        let profile = IccProfile::new("TestProfile".to_string(), data, IccColorSpace::Rgb)
522            .with_metadata("Description".to_string(), "Test RGB Profile".to_string());
523
524        assert_eq!(
525            profile.metadata.get("Description"),
526            Some(&"Test RGB Profile".to_string())
527        );
528    }
529
530    #[test]
531    fn test_icc_profile_validation_valid() {
532        let data = vec![0u8; 200];
533        let profile = IccProfile::new("TestProfile".to_string(), data, IccColorSpace::Rgb);
534
535        assert!(profile.validate().is_ok());
536    }
537
538    #[test]
539    fn test_icc_profile_validation_empty_data() {
540        let profile = IccProfile::new("TestProfile".to_string(), Vec::new(), IccColorSpace::Rgb);
541
542        assert!(profile.validate().is_err());
543    }
544
545    #[test]
546    fn test_icc_profile_validation_too_small() {
547        let data = vec![0u8; 50]; // Too small
548        let profile = IccProfile::new("TestProfile".to_string(), data, IccColorSpace::Rgb);
549
550        assert!(profile.validate().is_err());
551    }
552
553    #[test]
554    fn test_icc_profile_validation_invalid_range() {
555        let data = vec![0u8; 200];
556        let invalid_range = vec![1.0, 0.0]; // min > max
557        let profile = IccProfile::new("TestProfile".to_string(), data, IccColorSpace::Gray)
558            .with_range(invalid_range);
559
560        assert!(profile.validate().is_err());
561    }
562
563    #[test]
564    fn test_icc_profile_color_space_checks() {
565        let rgb_profile = IccProfile::from_standard(StandardIccProfile::SRgb);
566        assert!(rgb_profile.is_rgb());
567        assert!(!rgb_profile.is_cmyk());
568        assert!(!rgb_profile.is_gray());
569
570        let cmyk_profile = IccProfile::from_standard(StandardIccProfile::CoatedFogra39);
571        assert!(!cmyk_profile.is_rgb());
572        assert!(cmyk_profile.is_cmyk());
573        assert!(!cmyk_profile.is_gray());
574
575        let gray_profile = IccProfile::from_standard(StandardIccProfile::GrayGamma22);
576        assert!(!gray_profile.is_rgb());
577        assert!(!gray_profile.is_cmyk());
578        assert!(gray_profile.is_gray());
579    }
580
581    #[test]
582    fn test_icc_profile_to_pdf_color_space_array() {
583        let profile = IccProfile::from_standard(StandardIccProfile::SRgb);
584        let array = profile.to_pdf_color_space_array().unwrap();
585
586        assert_eq!(array.len(), 2);
587
588        if let Object::Name(name) = &array[0] {
589            assert_eq!(name, "ICCBased");
590        } else {
591            panic!("First element should be ICCBased name");
592        }
593
594        if let Object::Dictionary(dict) = &array[1] {
595            assert!(dict.contains_key("N"));
596            assert!(dict.contains_key("Alternate"));
597        } else {
598            panic!("Second element should be dictionary");
599        }
600    }
601
602    #[test]
603    fn test_icc_profile_manager_creation() {
604        let manager = IccProfileManager::new();
605        assert_eq!(manager.count(), 0);
606        assert!(manager.profiles().is_empty());
607    }
608
609    #[test]
610    fn test_icc_profile_manager_add_profile() {
611        let mut manager = IccProfileManager::new();
612        let profile = IccProfile::from_standard(StandardIccProfile::SRgb);
613
614        let name = manager.add_profile(profile).unwrap();
615        assert_eq!(name, "sRGB IEC61966-2.1");
616        assert_eq!(manager.count(), 1);
617
618        let retrieved = manager.get_profile(&name).unwrap();
619        assert_eq!(retrieved.name, "sRGB IEC61966-2.1");
620    }
621
622    #[test]
623    fn test_icc_profile_manager_add_standard() {
624        let mut manager = IccProfileManager::new();
625
626        let name = manager
627            .add_standard_profile(StandardIccProfile::SRgb)
628            .unwrap();
629        assert_eq!(name, "sRGB IEC61966-2.1");
630        assert_eq!(manager.count(), 1);
631    }
632
633    #[test]
634    fn test_icc_profile_manager_auto_naming() {
635        let mut manager = IccProfileManager::new();
636
637        let data = vec![0u8; 200];
638        let profile = IccProfile::new(
639            String::new(), // Empty name
640            data,
641            IccColorSpace::Rgb,
642        );
643
644        let name = manager.add_profile(profile).unwrap();
645        assert_eq!(name, "ICC1");
646
647        let data2 = vec![0u8; 200];
648        let profile2 = IccProfile::new(String::new(), data2, IccColorSpace::Cmyk);
649
650        let name2 = manager.add_profile(profile2).unwrap();
651        assert_eq!(name2, "ICC2");
652    }
653
654    #[test]
655    fn test_icc_profile_manager_get_by_type() {
656        let mut manager = IccProfileManager::new();
657
658        manager
659            .add_standard_profile(StandardIccProfile::SRgb)
660            .unwrap();
661        manager
662            .add_standard_profile(StandardIccProfile::AdobeRgb)
663            .unwrap();
664        manager
665            .add_standard_profile(StandardIccProfile::CoatedFogra39)
666            .unwrap();
667        manager
668            .add_standard_profile(StandardIccProfile::GrayGamma22)
669            .unwrap();
670
671        let rgb_profiles = manager.get_rgb_profiles();
672        assert_eq!(rgb_profiles.len(), 2);
673
674        let cmyk_profiles = manager.get_cmyk_profiles();
675        assert_eq!(cmyk_profiles.len(), 1);
676
677        let gray_profiles = manager.get_gray_profiles();
678        assert_eq!(gray_profiles.len(), 1);
679    }
680
681    #[test]
682    fn test_icc_profile_manager_defaults() {
683        let mut manager = IccProfileManager::new();
684
685        let srgb_name = manager.create_default_srgb().unwrap();
686        let cmyk_name = manager.create_default_cmyk().unwrap();
687        let gray_name = manager.create_default_gray().unwrap();
688
689        assert_eq!(manager.count(), 3);
690        assert!(manager.get_profile(&srgb_name).unwrap().is_rgb());
691        assert!(manager.get_profile(&cmyk_name).unwrap().is_cmyk());
692        assert!(manager.get_profile(&gray_name).unwrap().is_gray());
693    }
694
695    #[test]
696    fn test_icc_profile_manager_clear() {
697        let mut manager = IccProfileManager::new();
698
699        manager
700            .add_standard_profile(StandardIccProfile::SRgb)
701            .unwrap();
702        manager
703            .add_standard_profile(StandardIccProfile::CoatedFogra39)
704            .unwrap();
705        assert_eq!(manager.count(), 2);
706
707        manager.clear();
708        assert_eq!(manager.count(), 0);
709        assert!(manager.profiles().is_empty());
710    }
711
712    #[test]
713    fn test_icc_profile_manager_remove() {
714        let mut manager = IccProfileManager::new();
715
716        let name = manager
717            .add_standard_profile(StandardIccProfile::SRgb)
718            .unwrap();
719        assert_eq!(manager.count(), 1);
720
721        let removed = manager.remove_profile(&name);
722        assert!(removed.is_some());
723        assert_eq!(manager.count(), 0);
724
725        // Try to remove again
726        let not_found = manager.remove_profile(&name);
727        assert!(not_found.is_none());
728    }
729}