lcms2/
mlu.rs

1use crate::ffi::wchar_t;
2use crate::{ffi, Error, LCMSResult, Locale};
3use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
4use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
5use std::ffi::CString;
6use std::fmt;
7use std::mem;
8use std::ptr;
9
10foreign_type! {
11    /// This represents owned Multi Localized Unicode type. Most methods are implemented on `MLURef`.
12    /// This is a borrwed Multi Localized Unicode type. It holds Unicode strings associated with `Locale`.
13    pub unsafe type MLU {
14        type CType = ffi::MLU;
15        fn drop = ffi::cmsMLUfree;
16    }
17}
18
19impl MLU {
20    /// Allocates an empty multilocalized unicode object.
21    #[track_caller]
22    #[inline]
23    #[must_use]
24    pub fn new(items: usize) -> Self {
25        unsafe {
26            let handle = ffi::cmsMLUalloc(ptr::null_mut(), items as u32);
27            assert!(!handle.is_null());
28            MLU::from_ptr(handle)
29        }
30    }
31}
32
33impl MLURef {
34    /// Fills an ASCII (7 bit) entry for the given Language and country.
35    pub fn set_text_ascii(&mut self, text: &str, locale: Locale) -> bool {
36        let Ok(cstr) = CString::new(text) else { return false };
37        unsafe {
38            ffi::cmsMLUsetASCII(
39                (self as *mut Self).cast(),
40                locale.language_ptr(),
41                locale.country_ptr(),
42                cstr.as_ptr(),
43            ) != 0
44        }
45    }
46
47    /// Fills a UNICODE wide char (16 bit) entry for the given Language and country.
48    pub fn set_text(&mut self, text: &str, locale: Locale) -> bool {
49        let chars: Vec<_> = text
50            .chars()
51            .map(|c| c as wchar_t)
52            .chain([0 as wchar_t])
53            .collect();
54
55        unsafe {
56            ffi::cmsMLUsetWide(
57                (self as *mut Self).cast(),
58                locale.language_ptr(),
59                locale.country_ptr(),
60                chars[..].as_ptr(),
61            ) != 0
62        }
63    }
64
65    /// Gets an ASCII (7 bit) entry for the given Language and country.
66    pub fn text_ascii(&self, locale: Locale) -> LCMSResult<String> {
67        let len = unsafe {
68            ffi::cmsMLUgetASCII(
69                self.as_ptr(),
70                locale.language_ptr(),
71                locale.country_ptr(),
72                ptr::null_mut(),
73                0,
74            )
75        };
76        if len == 0 {
77            return Err(Error::MissingData);
78        }
79        let mut buf = vec![0u8; len as usize];
80        unsafe {
81            ffi::cmsMLUgetASCII(
82                self.as_ptr(),
83                locale.language_ptr(),
84                locale.country_ptr(),
85                buf[..].as_mut_ptr().cast(),
86                len,
87            );
88            if let Some(0) = buf.pop() { // terminating zero
89                String::from_utf8(buf).map_err(|_| Error::InvalidString)
90            } else {
91                Err(Error::InvalidString)
92            }
93        }
94    }
95
96    /// Gets a Unicode entry for the given Language and country
97    pub fn text(&self, locale: Locale) -> LCMSResult<String> {
98        let len_bytes = unsafe {
99            ffi::cmsMLUgetWide(
100                self.as_ptr(),
101                locale.language_ptr(),
102                locale.country_ptr(),
103                ptr::null_mut(),
104                0,
105            )
106        };
107        let len_wchars = len_bytes as usize / mem::size_of::<wchar_t>();
108        if len_wchars == 0 || (len_bytes & 1) != 0 {
109            return Err(Error::MissingData);
110        }
111        let mut buf = vec![0 as wchar_t; len_wchars];
112        unsafe {
113            ffi::cmsMLUgetWide(
114                self.as_ptr(),
115                locale.language_ptr(),
116                locale.country_ptr(),
117                buf[..].as_mut_ptr().cast::<wchar_t>(),
118                len_bytes,
119            );
120            if let Some(0) = buf.pop() { // terminating zero
121                Ok(decode_utf16(buf.into_iter().map(|c| c as u16))
122                    .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
123                    .collect())
124            } else {
125                Err(Error::InvalidString)
126            }
127        }
128    }
129
130    /// Obtains the translations stored in a given multilocalized unicode object.
131    #[must_use]
132    pub fn tanslations(&self) -> Vec<Locale> {
133        let count = unsafe { ffi::cmsMLUtranslationsCount(self.as_ptr()) };
134        let mut out = vec![Locale::none(); count as usize];
135
136        let mut i = 0;
137        out.retain_mut(|locale| {
138            let ok = unsafe {
139                ffi::cmsMLUtranslationsCodes(
140                    self.as_ptr(),
141                    i,
142                    locale.language_ptr_mut(),
143                    locale.country_ptr_mut(),
144                ) != 0
145            };
146            i += 1;
147            ok
148        });
149        out
150    }
151
152    /// Obtains the translation rule for given multilocalized unicode object.
153    pub fn tanslation(&self, locale: Locale) -> LCMSResult<Locale> {
154        let mut out = Locale::none();
155        if unsafe {
156            ffi::cmsMLUgetTranslation(
157                self.as_ptr(),
158                locale.language_ptr(),
159                locale.country_ptr(),
160                out.language_ptr_mut(),
161                out.country_ptr_mut(),
162            ) != 0
163        } {
164            Ok(out)
165        } else {
166            Err(Error::MissingData)
167        }
168    }
169}
170
171impl fmt::Debug for MLURef {
172    #[cold]
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        let t = self.text(Locale::none());
175        write!(
176            f,
177            "MLU({:?} {:?})",
178            if let Ok(ref t) = t { t } else { "None" },
179            self.tanslations()
180        )
181    }
182}
183
184#[test]
185fn mlu() {
186    let _ = MLU::new(0);
187    let mut m = MLU::new(1);
188    assert!(m.set_text("Hello 世界!", Locale::none()));
189    assert_eq!(Ok("Hello 世界!".to_owned()), m.text(Locale::none()));
190    assert!(!m.set_text_ascii("エッロル", Locale::none()));
191
192    assert!(m.set_text("a", Locale::new("en_US")));
193    assert_eq!("a", m.text_ascii(Locale::new("en_US")).unwrap());
194
195    let mut m = MLU::new(1);
196    assert!(m.set_text_ascii("OK", Locale::none()));
197    assert_eq!("OK", m.text_ascii(Locale::none()).unwrap());
198}