Skip to main content

qubit_text_codec/codec/
charset_decoder.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    CharsetDecodeError,
12    CharsetDecodeErrorKind,
13    Coder,
14    CoderProgress,
15    CoderStatus,
16};
17
18use super::{
19    charset_codec::CharsetCodec,
20    decode_status::DecodeStatus,
21    malformed_action::MalformedAction,
22};
23
24/// Converts units of one charset into Unicode scalar values.
25///
26/// `CharsetDecoder` wraps a low-level [`CharsetCodec`] and applies the
27/// configured [`MalformedAction`] whenever the codec reports malformed input.
28///
29/// # Type Parameters
30///
31/// - `C`: Low-level charset codec used to decode source storage units into one
32///   Unicode scalar value.
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct CharsetDecoder<C>
35where
36    C: CharsetCodec,
37{
38    /// Low-level codec used for source decoding.
39    codec: C,
40    /// Action used for malformed input units.
41    malformed_action: MalformedAction,
42    /// Replacement character used by [`MalformedAction::Replace`].
43    replacement: char,
44}
45
46impl<C> CharsetDecoder<C>
47where
48    C: CharsetCodec,
49{
50    /// Default replacement character used when malformed input is replaced.
51    pub const DEFAULT_REPLACEMENT: char = '\u{fffd}';
52
53    /// Creates a decoder with default replacement policy.
54    ///
55    /// # Parameters
56    ///
57    /// - `codec`: Low-level charset codec used to decode input units.
58    ///
59    /// # Returns
60    ///
61    /// Returns a decoder whose malformed action is [`MalformedAction::Replace`]
62    /// and whose replacement character is `U+FFFD`.
63    #[must_use]
64    #[inline]
65    pub const fn new(codec: C) -> Self {
66        Self {
67            codec,
68            malformed_action: MalformedAction::Replace,
69            replacement: Self::DEFAULT_REPLACEMENT,
70        }
71    }
72
73    /// Creates a decoder with a custom replacement character.
74    ///
75    /// This method performs no codec-level validation because malformed-input
76    /// replacement for decoding writes directly to the output `char` buffer.
77    ///
78    /// # Parameters
79    ///
80    /// - `replacement`: Replacement character for malformed sequences.
81    ///
82    /// # Returns
83    ///
84    /// Returns a new decoder configured with the provided replacement.
85    #[inline]
86    pub fn with_replacement(mut self, replacement: char) -> Self {
87        self.replacement = replacement;
88        self
89    }
90
91    /// Returns the wrapped low-level codec.
92    ///
93    /// # Returns
94    ///
95    /// Returns a shared reference to the configured codec.
96    #[must_use]
97    #[inline]
98    pub const fn codec(&self) -> &C {
99        &self.codec
100    }
101
102    /// Returns a mutable reference to the wrapped codec.
103    ///
104    /// # Returns
105    ///
106    /// Returns a mutable reference to the configured codec.
107    #[must_use]
108    #[inline]
109    pub fn codec_mut(&mut self) -> &mut C {
110        &mut self.codec
111    }
112
113    /// Returns the configured malformed-input action.
114    ///
115    /// # Returns
116    ///
117    /// Returns the action used when source input is malformed.
118    #[must_use]
119    #[inline]
120    pub const fn malformed_action(&self) -> MalformedAction {
121        self.malformed_action
122    }
123
124    /// Sets the malformed-input action.
125    ///
126    /// # Parameters
127    ///
128    /// - `action`: New policy for malformed input units.
129    #[inline]
130    pub fn set_malformed_action(&mut self, action: MalformedAction) {
131        self.malformed_action = action;
132    }
133
134    /// Returns the configured replacement character.
135    ///
136    /// # Returns
137    ///
138    /// Returns the character emitted when [`MalformedAction::Replace`] is used.
139    #[must_use]
140    #[inline]
141    pub const fn replacement(&self) -> char {
142        self.replacement
143    }
144
145    /// Sets the replacement character.
146    ///
147    /// # Parameters
148    ///
149    /// - `replacement`: New replacement character used by replace policy.
150    #[inline]
151    pub fn set_replacement(&mut self, replacement: char) {
152        self.replacement = replacement;
153    }
154}
155
156impl<C> Coder<C::Unit, char> for CharsetDecoder<C>
157where
158    C: CharsetCodec,
159{
160    type Error = CharsetDecodeError;
161
162    /// Returns the maximum number of characters decoded from `input_len` units.
163    #[inline]
164    fn max_output_len(&self, input_len: usize) -> Option<usize> {
165        Some(input_len)
166    }
167
168    /// Decodes source units into Unicode scalar values while applying malformed policy.
169    fn convert(
170        &mut self,
171        input: &[C::Unit],
172        input_index: usize,
173        output: &mut [char],
174        output_index: usize,
175    ) -> Result<CoderProgress, Self::Error> {
176        if input_index > input.len() {
177            let kind = CharsetDecodeErrorKind::MalformedSequence { value: None };
178            return Err(CharsetDecodeError::new(self.codec.charset(), kind, input_index));
179        }
180        if output_index > output.len() {
181            let status = CoderStatus::NeedOutput {
182                output_index,
183                required: 1,
184                available: 0,
185            };
186            return Ok(CoderProgress::new(status, 0, 0));
187        }
188
189        let mut input_cursor = input_index;
190        let mut output_cursor = output_index;
191        while input_cursor < input.len() {
192            if output_cursor == output.len() {
193                let status = CoderStatus::NeedOutput {
194                    output_index: output_cursor,
195                    required: 1,
196                    available: 0,
197                };
198                return Ok(CoderProgress::new(
199                    status,
200                    input_cursor - input_index,
201                    output_cursor - output_index,
202                ));
203            }
204            match self.codec.decode_one(input, input_cursor) {
205                Ok(DecodeStatus::Complete { value, consumed }) => {
206                    output[output_cursor] = value;
207                    input_cursor += consumed;
208                    output_cursor += 1;
209                }
210                Ok(DecodeStatus::NeedMore { required, available }) => {
211                    let needed = required.saturating_sub(input_cursor);
212                    let status = CoderStatus::NeedInput {
213                        input_index: input_cursor,
214                        required: needed,
215                        available,
216                    };
217                    return Ok(CoderProgress::new(
218                        status,
219                        input_cursor - input_index,
220                        output_cursor - output_index,
221                    ));
222                }
223                Err(error)
224                    if matches!(
225                        error.kind(),
226                        CharsetDecodeErrorKind::MalformedSequence { .. }
227                            | CharsetDecodeErrorKind::InvalidCodePoint { .. }
228                    ) =>
229                {
230                    let skip = malformed_skip(input_cursor, input.len(), error.index());
231                    match self.malformed_action {
232                        MalformedAction::Report => return Err(error),
233                        MalformedAction::Ignore => {
234                            input_cursor += skip;
235                        }
236                        MalformedAction::Replace => {
237                            output[output_cursor] = self.replacement;
238                            input_cursor += skip;
239                            output_cursor += 1;
240                        }
241                    }
242                }
243                Err(error) => return Err(error),
244            }
245        }
246        Ok(CoderProgress::complete(
247            input_cursor - input_index,
248            output_cursor - output_index,
249        ))
250    }
251}
252
253/// Calculates how many malformed input units should be skipped.
254///
255/// # Parameters
256///
257/// - `input_index`: Absolute index where decoding of the current character started.
258/// - `input_len`: Length of the complete input slice.
259/// - `error_index`: Absolute index reported by the low-level codec.
260///
261/// # Returns
262///
263/// Returns at least one unit when input remains. When the codec reports an error
264/// after the start index, the skipped range includes the reported failing unit.
265#[inline]
266fn malformed_skip(input_index: usize, input_len: usize, error_index: usize) -> usize {
267    let available = input_len.saturating_sub(input_index);
268    let end = error_index.saturating_add(1).min(input_len);
269    end.saturating_sub(input_index).max(1).min(available)
270}