Skip to main content

devela/text/translit/ascii/
fns.rs

1// devela::text::translit::ascii::fns
2
3use crate::{Translit, UnicodeScalar, is, whilst};
4
5impl Translit {
6    /// Returns the ASCII transliteration of a Unicode scalar value.
7    ///
8    /// This is a lossy transliteration, not an encoding.
9    /// The result may contain zero, one, or more ASCII bytes.
10    ///
11    /// Returns an empty string if the scalar is not mapped.
12    ///
13    /// Converts Unicode to readable ASCII approximations:
14    /// - `'°'` → `"deg"`
15    /// - `'α'` → `"a"`
16    /// - `'©'` → `"(c)"`
17    /// - `'中'` → `"Zhong "`
18    /// - `'🚀'` → `""`
19    /// - ...
20    #[must_use]
21    pub const fn ascii(char: char) -> &'static str {
22        Self::ascii_scalar(char as u32)
23    }
24    /// Returns the ASCII transliteration of a Unicode scalar code point.
25    ///
26    /// This accepts a raw `u32` for table-oriented use.
27    /// Invalid or unmapped scalar values simply return an empty string.
28    #[doc = crate::_doc_vendor!("transliteration")]
29    #[must_use]
30    pub const fn ascii_scalar(scalar: u32) -> &'static str {
31        let block = (scalar >> 8) as usize;
32        let offset = (scalar & 0xFF) as usize;
33        if block < Translit::ASCII_BLOCKS.len() {
34            let block_data = Translit::ASCII_BLOCKS[block];
35            if offset < block_data.len() {
36                return block_data[offset];
37            }
38        }
39        ""
40    }
41    /// Returns an ASCII approximation of any Unicode scalar type.
42    #[must_use]
43    pub fn ascii_of<S: UnicodeScalar>(scalar: S) -> &'static str {
44        Self::ascii_scalar(scalar.to_scalar())
45    }
46
47    /// Returns whether the scalar has a non-empty ASCII approximation.
48    #[must_use]
49    pub const fn has_ascii(c: char) -> bool {
50        !Self::ascii(c).is_empty()
51    }
52    /// Returns whether the scalar code point has a non-empty ASCII approximation.
53    #[must_use]
54    pub const fn has_ascii_scalar(scalar: u32) -> bool {
55        !Self::ascii_scalar(scalar).is_empty()
56    }
57
58    /// Returns an ASCII approximation, or `fallback` if the scalar is unmapped.
59    #[must_use]
60    pub const fn ascii_or(c: char, fallback: &'static str) -> &'static str {
61        let out = Self::ascii(c);
62        if out.is_empty() { fallback } else { out }
63    }
64    /// Returns an ASCII approximation, or `fallback` if the scalar is unmapped.
65    #[must_use]
66    pub const fn ascii_scalar_or(scalar: u32, fallback: &'static str) -> &'static str {
67        let out = Self::ascii_scalar(scalar);
68        if out.is_empty() { fallback } else { out }
69    }
70
71    /// Returns the single ASCII byte approximation, if it is exactly one byte.
72    #[must_use]
73    pub const fn ascii_byte(c: char) -> Option<u8> {
74        Self::ascii_scalar_byte(c as u32)
75    }
76    /// Returns the single ASCII byte approximation, if it is exactly one byte.
77    #[must_use]
78    pub const fn ascii_scalar_byte(scalar: u32) -> Option<u8> {
79        let out = Self::ascii_scalar(scalar);
80        let bytes = out.as_bytes();
81        if bytes.len() == 1 { Some(bytes[0]) } else { None }
82    }
83
84    /// Returns the length of the ASCII approximation.
85    #[must_use]
86    pub const fn ascii_len(c: char) -> usize {
87        Self::ascii(c).len()
88    }
89    /// Returns the length of the ASCII approximation.
90    #[must_use]
91    pub const fn ascii_scalar_len(scalar: u32) -> usize {
92        Self::ascii_scalar(scalar).len()
93    }
94}
95impl Translit {
96    /// Writes the ASCII approximation of `src` into `dst`.
97    ///
98    /// Unmapped scalars are omitted.
99    ///
100    /// Returns the number of bytes written, or `None` if `dst` is too small.
101    pub fn write_ascii(src: &str, dst: &mut [u8]) -> Option<usize> {
102        let mut written = 0;
103        for c in src.chars() {
104            let out = Self::ascii(c).as_bytes();
105            is! { written + out.len() > dst.len(), return None }
106            whilst! { i in 0..out.len(); { dst[written + i] = out[i]; }}
107            written += out.len();
108        }
109        Some(written)
110    }
111    /// Writes the ASCII approximation of `src` into `dst`, using `fallback`
112    /// for unmapped scalars.
113    ///
114    /// Returns the number of bytes written, or `None` if `dst` is too small.
115    pub fn write_ascii_or(src: &str, dst: &mut [u8], fallback: &str) -> Option<usize> {
116        let mut written = 0;
117        for c in src.chars() {
118            let out = Self::ascii(c);
119            let out = if out.is_empty() { fallback } else { out };
120            let bytes = out.as_bytes();
121            is! { written + bytes.len() > dst.len(), return None }
122            whilst! { i in 0..bytes.len(); { dst[written + i] = bytes[i]; }}
123            written += bytes.len();
124        }
125        Some(written)
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::Translit;
132
133    #[test]
134    fn ascii() {
135        assert_eq![Translit::ascii_scalar('°' as u32), "deg"];
136        assert_eq![Translit::ascii_scalar('α' as u32), "a"];
137        assert_eq![Translit::ascii_scalar('©' as u32), "(c)"];
138        assert_eq![Translit::ascii_scalar('㍱' as u32), "HPA"];
139        assert_eq![Translit::ascii_scalar('㎮' as u32), "rad/s"];
140        assert_eq![Translit::ascii_scalar('中' as u32), "Zhong "];
141        assert_eq![Translit::ascii_scalar('ff' as u32), "ff"];
142        assert_eq![Translit::ascii_scalar('ﬤ' as u32), "k"];
143        assert_eq![Translit::ascii_scalar('�' as u32), ""];
144        assert_eq![Translit::ascii_scalar('🚀' as u32), ""];
145    }
146}