Skip to main content

qubit_text_codec/codec/
latin1_codec.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0.
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use crate::{
11    Charset,
12    CharsetCodec,
13    CharsetDecodeError,
14    CharsetDecodeErrorKind,
15    CharsetDecodeResult,
16    CharsetEncodeError,
17    CharsetEncodeErrorKind,
18    CharsetEncodeResult,
19    DecodeStatus,
20    Unicode,
21};
22
23/// Single-byte ISO-8859-1 codec for bytes.
24///
25/// `Latin1Codec` converts between ISO-8859-1 bytes and Unicode scalar values.
26#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
27pub struct Latin1Codec;
28
29impl Latin1Codec {
30    /// Returns the ISO-8859-1 charset descriptor.
31    ///
32    /// # Returns
33    ///
34    /// Returns [`Charset::ISO_8859_1`].
35    #[must_use]
36    #[inline]
37    pub const fn charset(self) -> Charset {
38        Charset::ISO_8859_1
39    }
40
41    /// Returns the maximum number of bytes needed for one Latin-1 character.
42    ///
43    /// # Returns
44    ///
45    /// Returns `1`.
46    #[must_use]
47    #[inline]
48    pub const fn max_units_per_char(self) -> usize {
49        1
50    }
51}
52
53impl CharsetCodec for Latin1Codec {
54    type Unit = u8;
55    /// Returns the charset descriptor for this codec.
56    ///
57    /// # Returns
58    ///
59    /// Returns [`Charset::ISO_8859_1`].
60    #[inline]
61    fn charset(&self) -> Charset {
62        Charset::ISO_8859_1
63    }
64
65    /// Returns the maximum number of output bytes for one character.
66    ///
67    /// # Returns
68    ///
69    /// Returns `1`.
70    #[inline]
71    fn max_units_per_char(&self) -> usize {
72        1
73    }
74
75    /// Decodes one ISO-8859-1 byte into a `char`.
76    ///
77    /// # Parameters
78    ///
79    /// - `input`: Complete input byte slice.
80    /// - `index`: Absolute byte index at which decoding starts.
81    ///
82    /// # Returns
83    ///
84    /// `Ok(DecodeStatus::Complete { value, consumed: 1 })` always when input exists.
85    ///
86    /// # Errors
87    ///
88    /// Returns [`CharsetDecodeErrorKind::MalformedSequence`] when `index` is out
89    /// of range.
90    #[inline]
91    fn decode_one(&self, input: &[u8], index: usize) -> CharsetDecodeResult<DecodeStatus> {
92        if index > input.len() {
93            let kind = CharsetDecodeErrorKind::MalformedSequence { value: None };
94            return Err(CharsetDecodeError::new(Charset::ISO_8859_1, kind, index));
95        }
96
97        if index == input.len() {
98            return Ok(DecodeStatus::NeedMore {
99                required: index + 1,
100                available: 0,
101            });
102        }
103
104        let value = input[index] as u32;
105        Ok(DecodeStatus::Complete {
106            value: Unicode::to_char(value).expect("valid Latin-1 byte decodes to Unicode scalar"),
107            consumed: 1,
108        })
109    }
110
111    /// Encodes one `char` into one ISO-8859-1 byte.
112    ///
113    /// # Parameters
114    ///
115    /// - `ch`: The character to encode.
116    /// - `output`: Output byte slice.
117    /// - `index`: Absolute output index where writing starts.
118    ///
119    /// # Returns
120    ///
121    /// `Ok(1)` when one byte is written.
122    ///
123    /// # Errors
124    ///
125    /// * `CharsetEncodeErrorKind::BufferTooSmall` if `index >= output.len()`.
126    /// * `CharsetEncodeErrorKind::UnmappableCharacter` if `ch` > `U+00FF`.
127    #[inline]
128    fn encode_one(&self, ch: char, output: &mut [u8], index: usize) -> CharsetEncodeResult<usize> {
129        if index >= output.len() {
130            let kind = CharsetEncodeErrorKind::BufferTooSmall {
131                required: index + 1,
132                available: 0,
133            };
134            return Err(CharsetEncodeError::new(Charset::ISO_8859_1, kind, index));
135        }
136
137        let value = ch as u32;
138        if value > Unicode::LATIN1_MAX {
139            let kind = CharsetEncodeErrorKind::UnmappableCharacter { value };
140            return Err(CharsetEncodeError::new(Charset::ISO_8859_1, kind, index));
141        }
142
143        // Since we validated `value`, cast is safe for 0..=0xFF.
144        output[index] = value as u8;
145        Ok(1)
146    }
147}