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