Skip to main content

qubit_codec_text/codec/
latin1_codec.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0.
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8use crate::{
9    Charset,
10    CharsetCodec,
11    CharsetDecodeError,
12    CharsetDecodeErrorKind,
13    CharsetDecodeResult,
14    CharsetEncodeError,
15    CharsetEncodeErrorKind,
16    CharsetEncodeProbe,
17    CharsetEncodeResult,
18    Unicode,
19};
20use qubit_codec::Codec;
21
22/// Single-byte ISO-8859-1 codec for bytes.
23///
24/// `Latin1Codec` converts between ISO-8859-1 bytes and Unicode scalar values.
25#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
26pub struct Latin1Codec;
27
28impl Latin1Codec {
29    /// Returns the ISO-8859-1 charset descriptor.
30    ///
31    /// # Returns
32    ///
33    /// Returns [`Charset::ISO_8859_1`].
34    #[must_use]
35    #[inline(always)]
36    pub const fn charset(self) -> Charset {
37        Charset::ISO_8859_1
38    }
39}
40
41impl CharsetCodec for Latin1Codec {
42    /// Returns the charset descriptor for this codec.
43    ///
44    /// # Returns
45    ///
46    /// Returns [`Charset::ISO_8859_1`].
47    #[inline(always)]
48    fn charset(&self) -> Charset {
49        Charset::ISO_8859_1
50    }
51}
52
53impl CharsetEncodeProbe for Latin1Codec {
54    /// Encodes one `char` into one ISO-8859-1 byte.
55    ///
56    /// # Parameters
57    ///
58    /// - `ch`: The character to encode.
59    /// - `index`: Input character index used for error context.
60    ///
61    /// # Returns
62    ///
63    /// `Ok(1)` when one byte is needed.
64    ///
65    /// # Errors
66    ///
67    /// * `CharsetEncodeErrorKind::UnmappableCharacter` if `ch` > `U+00FF`.
68    #[inline]
69    fn encode_len(&self, ch: char, index: usize) -> CharsetEncodeResult<usize> {
70        let value = ch as u32;
71        if value > Unicode::LATIN1_MAX {
72            let kind = CharsetEncodeErrorKind::UnmappableCharacter { value };
73            return Err(CharsetEncodeError::new(
74                Charset::ISO_8859_1,
75                kind,
76                index,
77            ));
78        }
79
80        Ok(1)
81    }
82}
83
84unsafe impl Codec for Latin1Codec {
85    type Value = char;
86    type Unit = u8;
87    type DecodeError = CharsetDecodeError;
88    type EncodeError = CharsetEncodeError;
89
90    #[inline(always)]
91    fn min_units_per_value(&self) -> core::num::NonZeroUsize {
92        core::num::NonZeroUsize::MIN
93    }
94
95    #[inline(always)]
96    fn max_units_per_value(&self) -> core::num::NonZeroUsize {
97        core::num::NonZeroUsize::MIN
98    }
99
100    #[inline]
101    unsafe fn decode_unchecked(
102        &self,
103        input: &[u8],
104        index: usize,
105    ) -> CharsetDecodeResult<(char, core::num::NonZeroUsize)> {
106        if index > input.len() {
107            let kind = CharsetDecodeErrorKind::InvalidInputIndex {
108                input_len: input.len(),
109            };
110            return Err(CharsetDecodeError::new(
111                Charset::ISO_8859_1,
112                kind,
113                index,
114            ));
115        }
116        if index == input.len() {
117            let kind = CharsetDecodeErrorKind::IncompleteSequence {
118                required: 1,
119                available: 0,
120            };
121            return Err(CharsetDecodeError::new(
122                Charset::ISO_8859_1,
123                kind,
124                index,
125            ));
126        }
127
128        let value = input[index] as u32;
129        debug_assert!(index < input.len());
130        Ok((
131            Unicode::to_char(value)
132                .expect("valid Latin-1 byte decodes to Unicode scalar"),
133            core::num::NonZeroUsize::MIN,
134        ))
135    }
136
137    #[inline]
138    unsafe fn encode_unchecked(
139        &self,
140        ch: &char,
141        output: &mut [u8],
142        index: usize,
143    ) -> CharsetEncodeResult<usize> {
144        let value = *ch as u32;
145        if value > Unicode::LATIN1_MAX {
146            let kind = CharsetEncodeErrorKind::UnmappableCharacter { value };
147            return Err(CharsetEncodeError::new(
148                Charset::ISO_8859_1,
149                kind,
150                index,
151            ));
152        }
153        if index >= output.len() {
154            let kind = CharsetEncodeErrorKind::BufferTooSmall {
155                required: required_index(index, 1),
156                available: 0,
157            };
158            return Err(CharsetEncodeError::new(
159                Charset::ISO_8859_1,
160                kind,
161                index,
162            ));
163        }
164        output[index] = value as u8;
165        Ok(1)
166    }
167}
168
169#[inline(always)]
170const fn required_index(index: usize, required_units: usize) -> usize {
171    match index.checked_add(required_units) {
172        Some(required) => required,
173        None => usize::MAX,
174    }
175}