appthere_color/policy.rs
1//! Document-level color management policy.
2//!
3//! Provides [`ColorPolicy`] and [`OutputIntent`] which represent format-agnostic
4//! document-level color management configuration. This crate provides the data
5//! model only — format-specific serialization belongs in the consuming crate.
6
7extern crate alloc;
8use alloc::collections::BTreeMap;
9use alloc::string::String;
10
11use crate::profile::IccProfile;
12use crate::rendering_intent::RenderingIntent;
13
14/// Describes the intended output condition for a document.
15///
16/// Maps conceptually to PDF/X `/OutputIntents`, ODF `draw:color-profile`, or
17/// embedded ICC profiles in EPUB image resources.
18///
19/// # Examples
20///
21/// ```
22/// use appthere_color::{OutputIntent, IccProfile};
23///
24/// let intent = OutputIntent::new(
25/// "FOGRA39".to_string(),
26/// IccProfile::new_srgb(),
27/// );
28/// assert_eq!(intent.name(), "FOGRA39");
29/// ```
30#[derive(Debug, Clone)]
31pub struct OutputIntent {
32 name: String,
33 profile: IccProfile,
34 condition: Option<String>,
35}
36
37impl OutputIntent {
38 /// Creates a new output intent with the given name and profile.
39 ///
40 /// # Examples
41 ///
42 /// ```
43 /// use appthere_color::{OutputIntent, IccProfile};
44 ///
45 /// let oi = OutputIntent::new("ISOcoated_v2".to_string(), IccProfile::new_srgb());
46 /// ```
47 pub fn new(name: String, profile: IccProfile) -> Self {
48 Self {
49 name,
50 profile,
51 condition: None,
52 }
53 }
54
55 /// Sets the output condition identifier (e.g. a registered condition name).
56 ///
57 /// # Examples
58 ///
59 /// ```
60 /// use appthere_color::{OutputIntent, IccProfile};
61 ///
62 /// let oi = OutputIntent::new("FOGRA39".to_string(), IccProfile::new_srgb())
63 /// .with_condition("FOGRA39L".to_string());
64 /// assert_eq!(oi.condition(), Some("FOGRA39L"));
65 /// ```
66 pub fn with_condition(mut self, condition: String) -> Self {
67 self.condition = Some(condition);
68 self
69 }
70
71 /// Returns the output intent name.
72 ///
73 /// # Examples
74 ///
75 /// ```
76 /// use appthere_color::{OutputIntent, IccProfile};
77 ///
78 /// let oi = OutputIntent::new("test".to_string(), IccProfile::new_srgb());
79 /// assert_eq!(oi.name(), "test");
80 /// ```
81 pub fn name(&self) -> &str {
82 &self.name
83 }
84
85 /// Returns a reference to the output intent's ICC profile.
86 ///
87 /// # Examples
88 ///
89 /// ```
90 /// use appthere_color::{OutputIntent, IccProfile};
91 ///
92 /// let oi = OutputIntent::new("sRGB".to_string(), IccProfile::new_srgb());
93 /// let _profile = oi.profile();
94 /// ```
95 pub fn profile(&self) -> &IccProfile {
96 &self.profile
97 }
98
99 /// Returns the output condition string, if set.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use appthere_color::{OutputIntent, IccProfile};
105 ///
106 /// let oi = OutputIntent::new("test".to_string(), IccProfile::new_srgb());
107 /// assert_eq!(oi.condition(), None);
108 /// ```
109 pub fn condition(&self) -> Option<&str> {
110 self.condition.as_deref()
111 }
112}
113
114/// Document-level color management policy.
115///
116/// Captures the output intent, fallback profile, default rendering intent,
117/// and a registry of named embedded profiles. This is a format-agnostic
118/// representation — serialization to PDF/X, ODF, or EPUB is handled by the
119/// consuming crate.
120///
121/// # Examples
122///
123/// ```
124/// use appthere_color::{ColorPolicy, OutputIntent, IccProfile, RenderingIntent};
125///
126/// let policy = ColorPolicy::builder()
127/// .output_intent(OutputIntent::new(
128/// "sRGB".to_string(),
129/// IccProfile::new_srgb(),
130/// ))
131/// .default_intent(RenderingIntent::RelativeColorimetric)
132/// .build();
133/// ```
134#[derive(Debug, Clone)]
135pub struct ColorPolicy {
136 output_intent: Option<OutputIntent>,
137 fallback_profile: Option<IccProfile>,
138 default_intent: RenderingIntent,
139 embedded_profiles: BTreeMap<String, IccProfile>,
140}
141
142impl ColorPolicy {
143 /// Creates a new [`ColorPolicyBuilder`].
144 ///
145 /// # Examples
146 ///
147 /// ```
148 /// use appthere_color::ColorPolicy;
149 ///
150 /// let builder = ColorPolicy::builder();
151 /// ```
152 pub fn builder() -> ColorPolicyBuilder {
153 ColorPolicyBuilder {
154 output_intent: None,
155 fallback_profile: None,
156 default_intent: RenderingIntent::Perceptual,
157 embedded_profiles: BTreeMap::new(),
158 }
159 }
160
161 /// Returns the output intent, if set.
162 ///
163 /// # Examples
164 ///
165 /// ```
166 /// use appthere_color::ColorPolicy;
167 ///
168 /// let policy = ColorPolicy::builder().build();
169 /// assert!(policy.output_intent().is_none());
170 /// ```
171 pub fn output_intent(&self) -> Option<&OutputIntent> {
172 self.output_intent.as_ref()
173 }
174
175 /// Returns the fallback profile, if set.
176 ///
177 /// # Examples
178 ///
179 /// ```
180 /// use appthere_color::ColorPolicy;
181 ///
182 /// let policy = ColorPolicy::builder().build();
183 /// assert!(policy.fallback_profile().is_none());
184 /// ```
185 pub fn fallback_profile(&self) -> Option<&IccProfile> {
186 self.fallback_profile.as_ref()
187 }
188
189 /// Returns the default rendering intent for this policy.
190 ///
191 /// # Examples
192 ///
193 /// ```
194 /// use appthere_color::{ColorPolicy, RenderingIntent};
195 ///
196 /// let policy = ColorPolicy::builder().build();
197 /// assert_eq!(policy.default_intent(), RenderingIntent::Perceptual);
198 /// ```
199 pub fn default_intent(&self) -> RenderingIntent {
200 self.default_intent
201 }
202
203 /// Looks up a named embedded profile in the registry.
204 ///
205 /// # Examples
206 ///
207 /// ```
208 /// use appthere_color::{ColorPolicy, IccProfile};
209 ///
210 /// let policy = ColorPolicy::builder()
211 /// .embed_profile("sRGB".to_string(), IccProfile::new_srgb())
212 /// .build();
213 /// assert!(policy.get_profile("sRGB").is_some());
214 /// assert!(policy.get_profile("AdobeRGB").is_none());
215 /// ```
216 pub fn get_profile(&self, name: &str) -> Option<&IccProfile> {
217 self.embedded_profiles.get(name)
218 }
219}
220
221/// Builder for [`ColorPolicy`].
222///
223/// Created via [`ColorPolicy::builder()`].
224#[derive(Debug)]
225pub struct ColorPolicyBuilder {
226 output_intent: Option<OutputIntent>,
227 fallback_profile: Option<IccProfile>,
228 default_intent: RenderingIntent,
229 embedded_profiles: BTreeMap<String, IccProfile>,
230}
231
232impl ColorPolicyBuilder {
233 /// Sets the output intent.
234 ///
235 /// # Examples
236 ///
237 /// ```
238 /// use appthere_color::{ColorPolicy, OutputIntent, IccProfile};
239 ///
240 /// let builder = ColorPolicy::builder()
241 /// .output_intent(OutputIntent::new("sRGB".into(), IccProfile::new_srgb()));
242 /// ```
243 pub fn output_intent(mut self, intent: OutputIntent) -> Self {
244 self.output_intent = Some(intent);
245 self
246 }
247
248 /// Sets the fallback profile.
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// use appthere_color::{ColorPolicy, IccProfile};
254 ///
255 /// let builder = ColorPolicy::builder()
256 /// .fallback_profile(IccProfile::new_srgb());
257 /// ```
258 pub fn fallback_profile(mut self, profile: IccProfile) -> Self {
259 self.fallback_profile = Some(profile);
260 self
261 }
262
263 /// Sets the default rendering intent.
264 ///
265 /// # Examples
266 ///
267 /// ```
268 /// use appthere_color::{ColorPolicy, RenderingIntent};
269 ///
270 /// let builder = ColorPolicy::builder()
271 /// .default_intent(RenderingIntent::Saturation);
272 /// ```
273 pub fn default_intent(mut self, intent: RenderingIntent) -> Self {
274 self.default_intent = intent;
275 self
276 }
277
278 /// Registers a named embedded profile.
279 ///
280 /// # Examples
281 ///
282 /// ```
283 /// use appthere_color::{ColorPolicy, IccProfile};
284 ///
285 /// let builder = ColorPolicy::builder()
286 /// .embed_profile("logo".to_string(), IccProfile::new_srgb());
287 /// ```
288 pub fn embed_profile(mut self, name: String, profile: IccProfile) -> Self {
289 self.embedded_profiles.insert(name, profile);
290 self
291 }
292
293 /// Builds the [`ColorPolicy`].
294 ///
295 /// # Examples
296 ///
297 /// ```
298 /// use appthere_color::ColorPolicy;
299 ///
300 /// let policy = ColorPolicy::builder().build();
301 /// ```
302 pub fn build(self) -> ColorPolicy {
303 ColorPolicy {
304 output_intent: self.output_intent,
305 fallback_profile: self.fallback_profile,
306 default_intent: self.default_intent,
307 embedded_profiles: self.embedded_profiles,
308 }
309 }
310}