Skip to main content

xmp_writer/
types.rs

1use std::{
2    fmt::{Debug, Write},
3    iter,
4};
5
6use crate::XmpWriter;
7
8/// XML Namespaces for the XMP properties.
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
10#[allow(missing_docs)]
11#[non_exhaustive]
12pub enum Namespace<'a> {
13    Rdf,
14    DublinCore,
15    Xmp,
16    XmpRights,
17    XmpResourceRef,
18    XmpResourceEvent,
19    XmpVersion,
20    XmpJob,
21    XmpJobManagement,
22    XmpColorant,
23    XmpFont,
24    XmpDimensions,
25    XmpMedia,
26    XmpPaged,
27    XmpDynamicMedia,
28    XmpImage,
29    XmpIdq,
30    AdobePdf,
31    #[cfg(feature = "pdfa")]
32    PdfAId,
33    PdfUAId,
34    PdfXId,
35    #[cfg(feature = "pdfa")]
36    PdfAExtension,
37    #[cfg(feature = "pdfa")]
38    PdfASchema,
39    #[cfg(feature = "pdfa")]
40    PdfAProperty,
41    #[cfg(feature = "pdfa")]
42    PdfAType,
43    #[cfg(feature = "pdfa")]
44    PdfAField,
45    Custom(Box<CustomNamespace<'a>>),
46}
47
48/// A custom XML namespace.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
50pub struct CustomNamespace<'a> {
51    name: &'a str,
52    namespace: &'a str,
53    url: &'a str,
54}
55
56impl<'a> CustomNamespace<'a> {
57    /// Creates a new custom namespace.
58    pub fn new(name: &'a str, namespace: &'a str, url: &'a str) -> Self {
59        Self { name, namespace, url }
60    }
61}
62
63impl<'a> Namespace<'a> {
64    /// Returns a human-readable name for the namespace.
65    pub const fn name(&self) -> &'a str {
66        match self {
67            Self::Rdf => "RDF",
68            Self::DublinCore => "Dublin Core",
69            Self::Xmp => "XMP",
70            Self::XmpRights => "XMP Rights",
71            Self::XmpResourceRef => "XMP Resource Reference",
72            Self::XmpResourceEvent => "XMP Resource Event",
73            Self::XmpVersion => "XMP Version",
74            Self::XmpJob => "XMP Job Management",
75            Self::XmpColorant => "XMP Colorant",
76            Self::XmpFont => "XMP Font",
77            Self::XmpDimensions => "XMP Dimensions",
78            Self::XmpMedia => "XMP Media Management",
79            Self::XmpJobManagement => "XMP Job Management",
80            Self::XmpPaged => "XMP Paged Text",
81            Self::XmpDynamicMedia => "XMP Dynamic Media",
82            Self::XmpImage => "XMP Image",
83            Self::AdobePdf => "Adobe PDF",
84            Self::XmpIdq => "XMP Identifier Qualifier",
85            #[cfg(feature = "pdfa")]
86            Self::PdfAId => "PDF/A Identification",
87            Self::PdfUAId => "PDF/UA Identification",
88            Self::PdfXId => "PDF/X Identification",
89            #[cfg(feature = "pdfa")]
90            Self::PdfAExtension => "PDF/A Extension schema container",
91            #[cfg(feature = "pdfa")]
92            Self::PdfASchema => "PDF/A Schema container",
93            #[cfg(feature = "pdfa")]
94            Self::PdfAProperty => "PDF/A Property",
95            #[cfg(feature = "pdfa")]
96            Self::PdfAType => "PDF/A Type",
97            #[cfg(feature = "pdfa")]
98            Self::PdfAField => "PDF/A Field",
99            Self::Custom(custom) => custom.name,
100        }
101    }
102
103    /// Returns the URL for the namespace.
104    pub fn url(&self) -> &'a str {
105        match self {
106            Self::Rdf => "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
107            Self::DublinCore => "http://purl.org/dc/elements/1.1/",
108            Self::Xmp => "http://ns.adobe.com/xap/1.0/",
109            Self::XmpRights => "http://ns.adobe.com/xap/1.0/rights/",
110            Self::XmpResourceRef => "http://ns.adobe.com/xap/1.0/sType/ResourceRef#",
111            Self::XmpResourceEvent => "http://ns.adobe.com/xap/1.0/sType/ResourceEvent#",
112            Self::XmpVersion => "http://ns.adobe.com/xap/1.0/sType/Version#",
113            Self::XmpJob => "http://ns.adobe.com/xap/1.0/sType/Job#",
114            Self::XmpColorant => "http://ns.adobe.com/xap/1.0/g/",
115            Self::XmpFont => "http://ns.adobe.com/xap/1.0/sType/Font#",
116            Self::XmpDimensions => "http://ns.adobe.com/xap/1.0/sType/Dimensions#",
117            Self::XmpMedia => "http://ns.adobe.com/xap/1.0/mm/",
118            Self::XmpJobManagement => "http://ns.adobe.com/xap/1.0/bj/",
119            Self::XmpPaged => "http://ns.adobe.com/xap/1.0/t/pg/",
120            Self::XmpDynamicMedia => "http://ns.adobe.com/xap/1.0/DynamicMedia/",
121            Self::XmpImage => "http://ns.adobe.com/xap/1.0/g/img/",
122            Self::AdobePdf => "http://ns.adobe.com/pdf/1.3/",
123            Self::XmpIdq => "http://ns.adobe.com/xmp/Identifier/qual/1.0/",
124            #[cfg(feature = "pdfa")]
125            Self::PdfAId => "http://www.aiim.org/pdfa/ns/id/",
126            Self::PdfUAId => "http://www.aiim.org/pdfua/ns/id/",
127            Self::PdfXId => "http://www.npes.org/pdfx/ns/id/",
128            #[cfg(feature = "pdfa")]
129            Self::PdfAExtension => "http://www.aiim.org/pdfa/ns/extension/",
130            #[cfg(feature = "pdfa")]
131            Self::PdfASchema => "http://www.aiim.org/pdfa/ns/schema#",
132            #[cfg(feature = "pdfa")]
133            Self::PdfAProperty => "http://www.aiim.org/pdfa/ns/property#",
134            #[cfg(feature = "pdfa")]
135            Self::PdfAType => "http://www.aiim.org/pdfa/ns/type#",
136            #[cfg(feature = "pdfa")]
137            Self::PdfAField => "http://www.aiim.org/pdfa/ns/field#",
138            Self::Custom(custom) => custom.url,
139        }
140    }
141
142    /// Returns the prefix for the namespace.
143    pub fn prefix(&self) -> &'a str {
144        match self {
145            Self::Rdf => "rdf",
146            Self::DublinCore => "dc",
147            Self::Xmp => "xmp",
148            Self::XmpRights => "xmpRights",
149            Self::XmpResourceRef => "stRef",
150            Self::XmpResourceEvent => "stEvt",
151            Self::XmpVersion => "stVer",
152            Self::XmpJob => "stJob",
153            Self::XmpColorant => "xmpG",
154            Self::XmpFont => "stFnt",
155            Self::XmpDimensions => "stDim",
156            Self::XmpMedia => "xmpMM",
157            Self::XmpJobManagement => "xmpBJ",
158            Self::XmpPaged => "xmpTPg",
159            Self::XmpDynamicMedia => "xmpDM",
160            Self::XmpImage => "xmpGImg",
161            Self::AdobePdf => "pdf",
162            Self::XmpIdq => "xmpidq",
163            #[cfg(feature = "pdfa")]
164            Self::PdfAId => "pdfaid",
165            Self::PdfUAId => "pdfuaid",
166            Self::PdfXId => "pdfxid",
167            #[cfg(feature = "pdfa")]
168            Self::PdfAExtension => "pdfaExtension",
169            #[cfg(feature = "pdfa")]
170            Self::PdfASchema => "pdfaSchema",
171            #[cfg(feature = "pdfa")]
172            Self::PdfAProperty => "pdfaProperty",
173            #[cfg(feature = "pdfa")]
174            Self::PdfAType => "pdfaType",
175            #[cfg(feature = "pdfa")]
176            Self::PdfAField => "pdfaField",
177            Self::Custom(custom) => custom.namespace,
178        }
179    }
180}
181
182/// A XMP property.
183///
184/// Created by [`XmpWriter::element`], [`Array::element`],
185/// [`Array::element_with_attrs`], [`Struct::element`],
186/// [`Struct::element_with_attrs`].
187pub struct Element<'a, 'n: 'a> {
188    writer: &'a mut XmpWriter<'n>,
189    name: &'a str,
190    namespace: Namespace<'n>,
191}
192
193impl<'a, 'n: 'a> Element<'a, 'n> {
194    pub(crate) fn start(
195        writer: &'a mut XmpWriter<'n>,
196        name: &'a str,
197        namespace: Namespace<'n>,
198    ) -> Self {
199        Self::with_attrs(writer, name, namespace, iter::empty())
200    }
201
202    fn with_attrs<'b>(
203        writer: &'a mut XmpWriter<'n>,
204        name: &'a str,
205        namespace: Namespace<'n>,
206        attrs: impl IntoIterator<Item = (&'b str, &'b str)>,
207    ) -> Self {
208        write!(writer.buf, "<{}:{}", namespace.prefix(), name).unwrap();
209
210        for (key, value) in attrs {
211            write!(writer.buf, " {}=\"{}\"", key, value).unwrap();
212        }
213
214        writer.namespaces.insert(namespace.clone());
215        Element { writer, name, namespace }
216    }
217
218    /// Sets the property to a primitive value.
219    pub fn value(self, val: impl XmpType) {
220        self.writer.buf.push('>');
221        val.write(&mut self.writer.buf);
222        self.close();
223    }
224
225    /// Start writing a struct as the property value.
226    pub fn obj(self) -> Struct<'a, 'n> {
227        self.writer.namespaces.insert(Namespace::Rdf);
228        self.writer.buf.push_str(" rdf:parseType=\"Resource\">");
229        Struct::start(self.writer, self.name, self.namespace)
230    }
231
232    /// Start writing an array as the property value.
233    pub fn array(self, kind: RdfCollectionType) -> Array<'a, 'n> {
234        self.writer.buf.push('>');
235        Array::start(self.writer, kind, self.name, self.namespace)
236    }
237
238    fn close(self) {
239        write!(self.writer.buf, "</{}:{}>", self.namespace.prefix(), self.name).unwrap();
240    }
241
242    /// Set a language alternative of primitive values as the property value.
243    pub fn language_alternative<'b>(
244        self,
245        items: impl IntoIterator<Item = (Option<LangId<'b>>, &'b str)>,
246    ) {
247        let mut array = self.array(RdfCollectionType::Alt);
248        for (lang, value) in items {
249            array
250                .element_with_attrs(iter::once(("xml:lang", lang.unwrap_or_default().0)))
251                .value(value);
252        }
253        drop(array);
254    }
255
256    /// Start writing an unordered array (`rdf:Bag`) as the property value.
257    pub fn unordered_array(self, items: impl IntoIterator<Item = impl XmpType>) {
258        let mut array = self.array(RdfCollectionType::Bag);
259        for item in items {
260            array.element().value(item);
261        }
262    }
263
264    /// Start writing an ordered array (`rdf:Seq`) as the property value.
265    pub fn ordered_array(self, items: impl IntoIterator<Item = impl XmpType>) {
266        let mut array = self.array(RdfCollectionType::Seq);
267        for item in items {
268            array.element().value(item);
269        }
270    }
271
272    /// Start writing an alternative array (`rdf:Alt`) as the property value.
273    pub fn alternative_array(self, items: impl IntoIterator<Item = impl XmpType>) {
274        let mut array = self.array(RdfCollectionType::Alt);
275        for item in items {
276            array.element().value(item);
277        }
278    }
279}
280
281/// An XMP array value.
282///
283/// Created by [`Element::array`].
284pub struct Array<'a, 'n: 'a> {
285    writer: &'a mut XmpWriter<'n>,
286    kind: RdfCollectionType,
287    name: &'a str,
288    namespace: Namespace<'a>,
289}
290
291impl<'a, 'n: 'a> Array<'a, 'n> {
292    fn start(
293        writer: &'a mut XmpWriter<'n>,
294        kind: RdfCollectionType,
295        name: &'a str,
296        namespace: Namespace<'n>,
297    ) -> Self {
298        writer.namespaces.insert(Namespace::Rdf);
299        write!(writer.buf, "<rdf:{}>", kind.rdf_type()).unwrap();
300        Self { writer, kind, name, namespace }
301    }
302
303    /// Start writing an element in the array.
304    pub fn element(&mut self) -> Element<'_, 'n> {
305        self.element_with_attrs(iter::empty())
306    }
307
308    /// Start writing an element with attributes in the array.
309    pub fn element_with_attrs(
310        &mut self,
311        attrs: impl IntoIterator<Item = (&'a str, &'a str)>,
312    ) -> Element<'_, 'n> {
313        Element::with_attrs(self.writer, "li", Namespace::Rdf, attrs)
314    }
315}
316
317impl Drop for Array<'_, '_> {
318    fn drop(&mut self) {
319        write!(
320            self.writer.buf,
321            "</rdf:{}></{}:{}>",
322            self.kind.rdf_type(),
323            self.namespace.prefix(),
324            self.name
325        )
326        .unwrap();
327    }
328}
329
330/// An XMP struct value.
331///
332/// Created by [`Element::obj`].
333pub struct Struct<'a, 'n: 'a> {
334    writer: &'a mut XmpWriter<'n>,
335    name: &'a str,
336    namespace: Namespace<'a>,
337}
338
339impl<'a, 'n: 'a> Struct<'a, 'n> {
340    fn start(
341        writer: &'a mut XmpWriter<'n>,
342        name: &'a str,
343        namespace: Namespace<'n>,
344    ) -> Self {
345        Self { writer, name, namespace }
346    }
347
348    /// Start writing a property in the struct.
349    pub fn element(
350        &mut self,
351        name: &'a str,
352        namespace: Namespace<'n>,
353    ) -> Element<'_, 'n> {
354        self.element_with_attrs(name, namespace, iter::empty())
355    }
356
357    /// Start writing a property with attributes in the struct.
358    pub fn element_with_attrs<'b>(
359        &mut self,
360        name: &'a str,
361        namespace: Namespace<'n>,
362        attrs: impl IntoIterator<Item = (&'b str, &'b str)>,
363    ) -> Element<'_, 'n> {
364        Element::with_attrs(self.writer, name, namespace, attrs)
365    }
366}
367
368impl Drop for Struct<'_, '_> {
369    fn drop(&mut self) {
370        write!(self.writer.buf, "</{}:{}>", self.namespace.prefix(), self.name).unwrap();
371    }
372}
373
374/// Primitive XMP types.
375pub trait XmpType {
376    /// Write the value to the buffer.
377    fn write(&self, buf: &mut String);
378}
379
380impl XmpType for bool {
381    fn write(&self, buf: &mut String) {
382        if *self {
383            buf.push_str("True");
384        } else {
385            buf.push_str("False");
386        }
387    }
388}
389
390impl XmpType for i32 {
391    fn write(&self, buf: &mut String) {
392        write!(buf, "{}", self).unwrap();
393    }
394}
395
396impl XmpType for i64 {
397    fn write(&self, buf: &mut String) {
398        write!(buf, "{}", self).unwrap();
399    }
400}
401
402impl XmpType for f32 {
403    fn write(&self, buf: &mut String) {
404        write!(buf, "{}", self).unwrap();
405    }
406}
407
408impl XmpType for f64 {
409    fn write(&self, buf: &mut String) {
410        write!(buf, "{}", self).unwrap();
411    }
412}
413
414impl XmpType for &str {
415    fn write(&self, buf: &mut String) {
416        for c in self.chars() {
417            match c {
418                '<' => buf.push_str("&lt;"),
419                '>' => buf.push_str("&gt;"),
420                '&' => buf.push_str("&amp;"),
421                '\'' => buf.push_str("&apos;"),
422                '"' => buf.push_str("&quot;"),
423                _ => buf.push(c),
424            }
425        }
426    }
427}
428
429/// Types of RDF collections.
430pub enum RdfCollectionType {
431    /// An ordered array / sequence.
432    Seq,
433    /// An unordered array / bag.
434    Bag,
435    /// An alternative array.
436    Alt,
437}
438
439impl RdfCollectionType {
440    /// The RDF type name for this collection type.
441    pub fn rdf_type(&self) -> &'static str {
442        match self {
443            RdfCollectionType::Seq => "Seq",
444            RdfCollectionType::Bag => "Bag",
445            RdfCollectionType::Alt => "Alt",
446        }
447    }
448}
449
450/// A language specifier as defined in RFC 3066. Can also be `x-default` if the
451/// language is not known.
452#[derive(Debug, Clone, PartialEq)]
453pub struct LangId<'a>(pub &'a str);
454
455impl XmpType for LangId<'_> {
456    fn write(&self, buf: &mut String) {
457        buf.push_str(self.0);
458    }
459}
460
461impl Default for LangId<'_> {
462    fn default() -> Self {
463        Self("x-default")
464    }
465}
466
467/// A date and time.
468#[derive(Debug, Copy, Clone, PartialEq)]
469#[allow(missing_docs)]
470pub struct DateTime {
471    pub year: u16,
472    pub month: Option<u8>,
473    pub day: Option<u8>,
474    pub hour: Option<u8>,
475    pub minute: Option<u8>,
476    pub second: Option<u8>,
477    /// The timezone of this date and time. No assumptions about the timezone or
478    /// locale should be made if this is `None`.
479    pub timezone: Option<Timezone>,
480}
481
482/// A timezone.
483#[derive(Debug, Copy, Clone, PartialEq)]
484pub enum Timezone {
485    /// UTC time. Use `Local` for British time.
486    Utc,
487    /// A local timezone offset.
488    Local {
489        /// Timezone offset in hours.
490        hour: i8,
491        /// Timezone offset in minutes.
492        minute: i8,
493    },
494}
495
496impl DateTime {
497    /// Create a new date and time with all fields.
498    #[allow(clippy::too_many_arguments)]
499    pub fn new(
500        year: u16,
501        month: u8,
502        day: u8,
503        hour: u8,
504        minute: u8,
505        second: u8,
506        timezone: Timezone,
507    ) -> Self {
508        Self {
509            year,
510            month: Some(month),
511            day: Some(day),
512            hour: Some(hour),
513            minute: Some(minute),
514            second: Some(second),
515            timezone: Some(timezone),
516        }
517    }
518
519    /// Create a new date with a year only.
520    pub fn year(year: u16) -> Self {
521        Self {
522            year,
523            month: None,
524            day: None,
525            hour: None,
526            minute: None,
527            second: None,
528            timezone: None,
529        }
530    }
531
532    /// Create a new date and time without a timezone.
533    pub fn date(year: u16, month: u8, day: u8) -> Self {
534        Self {
535            year,
536            month: Some(month),
537            day: Some(day),
538            hour: None,
539            minute: None,
540            second: None,
541            timezone: None,
542        }
543    }
544
545    /// Create a new date and time without a timezone.
546    pub fn local_time(
547        year: u16,
548        month: u8,
549        day: u8,
550        hour: u8,
551        minute: u8,
552        second: u8,
553    ) -> Self {
554        Self {
555            year,
556            month: Some(month),
557            day: Some(day),
558            hour: Some(hour),
559            minute: Some(minute),
560            second: Some(second),
561            timezone: None,
562        }
563    }
564}
565
566impl XmpType for DateTime {
567    fn write(&self, buf: &mut String) {
568        (|| {
569            write!(buf, "{:04}", self.year).unwrap();
570            write!(buf, "-{:02}", self.month?).unwrap();
571            write!(buf, "-{:02}", self.day?).unwrap();
572            write!(buf, "T{:02}:{:02}", self.hour?, self.minute?).unwrap();
573            write!(buf, ":{:02}", self.second?).unwrap();
574            match self.timezone? {
575                Timezone::Utc => buf.push('Z'),
576                Timezone::Local { hour, minute } => {
577                    write!(buf, "{:+03}:{:02}", hour, minute).unwrap();
578                }
579            }
580            Some(())
581        })();
582    }
583}
584
585/// The intended use of the resource.
586#[derive(Debug, Clone, PartialEq)]
587pub enum RenditionClass<'a> {
588    /// The master resource.
589    Default,
590    /// A review copy.
591    Draft,
592    /// A low-resolution stand-in.
593    LowResolution,
594    /// A proof copy.
595    Proof,
596    /// A copy at screen resolution.
597    Screen,
598    /// A thumbnail.
599    Thumbnail {
600        /// The format of the thumbnail.
601        format: Option<&'a str>,
602        /// The size of the thumbnail.
603        size: Option<(u32, u32)>,
604        /// The color space of the thumbnail.
605        color_space: Option<&'a str>,
606    },
607    /// A custom rendition class.
608    Custom(&'a str),
609}
610
611impl XmpType for RenditionClass<'_> {
612    fn write(&self, buf: &mut String) {
613        match self {
614            Self::Default => buf.push_str("default"),
615            Self::Draft => buf.push_str("draft"),
616            Self::LowResolution => buf.push_str("low-res"),
617            Self::Proof => buf.push_str("proof"),
618            Self::Screen => buf.push_str("screen"),
619            Self::Thumbnail { format, size, color_space } => {
620                buf.push_str("thumbnail");
621                if let Some(format) = format {
622                    buf.push(':');
623                    buf.push_str(format);
624                }
625                if let Some((width, height)) = size {
626                    buf.push(':');
627                    buf.push_str(&width.to_string());
628                    buf.push('x');
629                    buf.push_str(&height.to_string());
630                }
631                if let Some(color_space) = color_space {
632                    buf.push(':');
633                    buf.push_str(color_space);
634                }
635            }
636            Self::Custom(s) => buf.push_str(s),
637        }
638    }
639}
640
641/// A user-assigned rating.
642pub enum Rating {
643    /// The resource has been rejected.
644    Rejected,
645    /// The resource has not been rated.
646    Unknown,
647    /// The resource has been rated 1 star.
648    OneStar,
649    /// The resource has been rated 2 stars.
650    TwoStars,
651    /// The resource has been rated 3 stars.
652    ThreeStars,
653    /// The resource has been rated 4 stars.
654    FourStars,
655    /// The resource has been rated 5 stars.
656    FiveStars,
657}
658
659impl Rating {
660    /// Creates a new `Rating` from the number of stars.
661    pub fn from_stars(stars: Option<u32>) -> Self {
662        match stars {
663            Some(0) => Self::Unknown,
664            Some(1) => Self::OneStar,
665            Some(2) => Self::TwoStars,
666            Some(3) => Self::ThreeStars,
667            Some(4) => Self::FourStars,
668            Some(5) => Self::FiveStars,
669            Some(stars) => {
670                panic!("Invalid number of stars: {} (must be between 0 and 5)", stars)
671            }
672            None => Self::Unknown,
673        }
674    }
675
676    /// Convert the rating to an XMP primitive.
677    pub fn to_xmp(self) -> f32 {
678        match self {
679            Self::Rejected => -1.0,
680            Self::Unknown => 0.0,
681            Self::OneStar => 1.0,
682            Self::TwoStars => 2.0,
683            Self::ThreeStars => 3.0,
684            Self::FourStars => 4.0,
685            Self::FiveStars => 5.0,
686        }
687    }
688}
689
690/// Whether to ignore the markers of an [ingredient.](crate::ResourceRefWriter)
691pub enum MaskMarkers {
692    /// Ignore all markers and those of the children.
693    All,
694    /// Process all markers.
695    None,
696}
697
698impl XmpType for MaskMarkers {
699    fn write(&self, buf: &mut String) {
700        match self {
701            Self::All => buf.push_str("All"),
702            Self::None => buf.push_str("None"),
703        }
704    }
705}
706
707/// The type of a resource event.
708#[allow(missing_docs)]
709pub enum ResourceEventAction<'a> {
710    Converted,
711    Copied,
712    Created,
713    Cropped,
714    Edited,
715    Filtered,
716    Formatted,
717    VersionUpdated,
718    Printed,
719    Published,
720    Managed,
721    Produced,
722    Resized,
723    Saved,
724    Custom(&'a str),
725}
726
727impl XmpType for ResourceEventAction<'_> {
728    fn write(&self, buf: &mut String) {
729        match self {
730            Self::Converted => buf.push_str("converted"),
731            Self::Copied => buf.push_str("copied"),
732            Self::Created => buf.push_str("created"),
733            Self::Cropped => buf.push_str("cropped"),
734            Self::Edited => buf.push_str("edited"),
735            Self::Filtered => buf.push_str("filtered"),
736            Self::Formatted => buf.push_str("formatted"),
737            Self::VersionUpdated => buf.push_str("version_updated"),
738            Self::Printed => buf.push_str("printed"),
739            Self::Published => buf.push_str("published"),
740            Self::Managed => buf.push_str("managed"),
741            Self::Produced => buf.push_str("produced"),
742            Self::Resized => buf.push_str("resized"),
743            Self::Saved => buf.push_str("saved"),
744            Self::Custom(s) => buf.push_str(s),
745        }
746    }
747}
748
749/// The color space in which a colorant is defined.
750#[allow(missing_docs)]
751pub enum ColorantMode {
752    CMYK,
753    RGB,
754    Lab,
755}
756
757impl XmpType for ColorantMode {
758    fn write(&self, buf: &mut String) {
759        buf.push_str(match self {
760            Self::CMYK => "CMYK",
761            Self::RGB => "RGB",
762            Self::Lab => "Lab",
763        });
764    }
765}
766
767/// The type of a colorant.
768pub enum ColorantType {
769    /// Colors inherent to the printing process.
770    Process,
771    /// Special colors.
772    Spot,
773}
774
775impl XmpType for ColorantType {
776    fn write(&self, buf: &mut String) {
777        buf.push_str(match self {
778            Self::Process => "PROCESS",
779            Self::Spot => "SPOT",
780        });
781    }
782}
783
784/// The unit of a physical dimension.
785#[allow(missing_docs)]
786pub enum DimensionUnit<'a> {
787    Inch,
788    Mm,
789    Pixel,
790    Pica,
791    Point,
792    Custom(&'a str),
793}
794
795impl XmpType for DimensionUnit<'_> {
796    fn write(&self, buf: &mut String) {
797        match self {
798            Self::Inch => buf.push_str("inch"),
799            Self::Mm => buf.push_str("mm"),
800            Self::Pixel => buf.push_str("pixel"),
801            Self::Pica => buf.push_str("pica"),
802            Self::Point => buf.push_str("point"),
803            Self::Custom(s) => buf.push_str(s),
804        }
805    }
806}
807
808/// The font file type.
809#[allow(missing_docs)]
810pub enum FontType<'a> {
811    TrueType,
812    OpenType,
813    Type1,
814    Bitmap,
815    Custom(&'a str),
816}
817
818impl XmpType for FontType<'_> {
819    fn write(&self, buf: &mut String) {
820        match self {
821            Self::TrueType => buf.push_str("TrueType"),
822            Self::OpenType => buf.push_str("OpenType"),
823            Self::Type1 => buf.push_str("Type1"),
824            Self::Bitmap => buf.push_str("Bitmap"),
825            Self::Custom(s) => buf.push_str(s),
826        }
827    }
828}