Skip to main content

qubit_text_codec/codec/
charset_converter.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 super::{
11    charset_codec::CharsetCodec,
12    charset_convert_error::CharsetConvertError,
13    charset_decoder::CharsetDecoder,
14    charset_encoder::CharsetEncoder,
15};
16use crate::{
17    Coder,
18    CoderProgress,
19    CoderStatus,
20};
21
22/// Converts units encoded with one charset into units encoded with another charset.
23///
24/// The converter owns a [`CharsetDecoder`] for the source charset and a
25/// [`CharsetEncoder`] for the target charset. A decoded character may be kept
26/// pending between calls when the target output buffer is full.
27///
28/// # Type Parameters
29///
30/// - `D`: Low-level charset codec used by the source decoder.
31/// - `E`: Low-level charset codec used by the target encoder.
32///
33/// ```rust
34/// use qubit_text_codec::{Coder, CharsetDecoder, CharsetEncoder, CharsetConverter, Utf8Codec, Utf16U16Codec};
35///
36/// let mut converter = CharsetConverter::new(
37///     CharsetDecoder::new(Utf8Codec),
38///     CharsetEncoder::new(Utf16U16Codec),
39/// );
40/// let mut output = [0_u16; 2];
41///
42/// let progress = converter
43///     .convert("AB".as_bytes(), 0, &mut output, 0)
44///     .expect("convert bytes to utf-16");
45///
46/// assert_eq!(2, progress.read());
47/// assert_eq!(2, progress.written());
48/// assert_eq!([65, 66], output);
49/// ```
50#[derive(Clone, Debug, Eq, PartialEq)]
51pub struct CharsetConverter<D, E>
52where
53    D: CharsetCodec,
54    E: CharsetCodec,
55{
56    /// Source charset decoder.
57    decoder: CharsetDecoder<D>,
58    /// Target charset encoder.
59    encoder: CharsetEncoder<E>,
60    /// Decoded character waiting for target output capacity.
61    pending: Option<char>,
62}
63
64impl<D, E> CharsetConverter<D, E>
65where
66    D: CharsetCodec,
67    E: CharsetCodec,
68{
69    /// Creates a charset converter from raw source and target codecs.
70    ///
71    /// # Parameters
72    ///
73    /// - `source`: Source charset codec.
74    /// - `target`: Target charset codec.
75    ///
76    /// # Returns
77    ///
78    /// Returns a converter with default decoder and encoder policies.
79    #[must_use]
80    #[inline]
81    pub fn from_codecs(source: D, target: E) -> Self {
82        Self::new(CharsetDecoder::new(source), CharsetEncoder::new(target))
83    }
84
85    /// Creates a charset converter from a decoder and an encoder.
86    ///
87    /// # Parameters
88    ///
89    /// - `decoder`: Decoder configured for the source charset.
90    /// - `encoder`: Encoder configured for the target charset.
91    ///
92    /// # Returns
93    ///
94    /// Returns a converter that composes the supplied decoder and encoder.
95    #[must_use]
96    #[inline]
97    pub fn new(decoder: CharsetDecoder<D>, encoder: CharsetEncoder<E>) -> Self {
98        Self {
99            decoder,
100            encoder,
101            pending: None,
102        }
103    }
104
105    /// Returns the source decoder.
106    ///
107    /// # Returns
108    ///
109    /// Returns a shared reference to the configured decoder.
110    #[must_use]
111    #[inline]
112    pub const fn decoder(&self) -> &CharsetDecoder<D> {
113        &self.decoder
114    }
115
116    /// Returns the target encoder.
117    ///
118    /// # Returns
119    ///
120    /// Returns a shared reference to the configured encoder.
121    #[must_use]
122    #[inline]
123    pub const fn encoder(&self) -> &CharsetEncoder<E> {
124        &self.encoder
125    }
126
127    /// Returns a mutable source decoder.
128    ///
129    /// # Returns
130    ///
131    /// Returns a mutable reference to the configured decoder.
132    #[must_use]
133    #[inline]
134    pub fn decoder_mut(&mut self) -> &mut CharsetDecoder<D> {
135        &mut self.decoder
136    }
137
138    /// Returns a mutable target encoder.
139    ///
140    /// # Returns
141    ///
142    /// Returns a mutable reference to the configured encoder.
143    #[must_use]
144    #[inline]
145    pub fn encoder_mut(&mut self) -> &mut CharsetEncoder<E> {
146        &mut self.encoder
147    }
148
149    /// Writes the pending character through the target encoder.
150    ///
151    /// # Parameters
152    ///
153    /// - `ch`: Pending character to encode.
154    /// - `output`: Complete output slice visible to the converter.
155    /// - `output_index`: Absolute output index where this conversion call started.
156    /// - `written`: Number of output units already written by this conversion call.
157    ///
158    /// # Returns
159    ///
160    /// Returns [`CoderStatus::Complete`] when the pending character was written.
161    /// Returns [`CoderStatus::NeedOutput`] when it must stay pending for a later call.
162    ///
163    /// # Errors
164    ///
165    /// Returns [`CharsetConvertError::Encode`] when target encoding fails
166    /// according to the configured encoder policy.
167    #[inline]
168    fn write_pending(
169        &mut self,
170        ch: char,
171        output: &mut [E::Unit],
172        output_index: usize,
173        written: &mut usize,
174    ) -> Result<CoderProgress, CharsetConvertError> {
175        let single = [ch];
176        let encode_progress = self.encoder.convert(&single, 0, output, output_index + *written)?;
177        if encode_progress.status() == CoderStatus::Complete && encode_progress.read() == 1 {
178            self.pending = None;
179        }
180        *written += encode_progress.written();
181        Ok(encode_progress)
182    }
183}
184
185impl<D, E> Coder<D::Unit, E::Unit> for CharsetConverter<D, E>
186where
187    D: CharsetCodec,
188    E: CharsetCodec,
189{
190    type Error = CharsetConvertError;
191
192    /// Returns the target-side upper bound for converted output units.
193    #[inline]
194    fn max_output_len(&self, input_len: usize) -> Option<usize> {
195        input_len.checked_mul(self.encoder.codec().max_units_per_char())
196    }
197
198    /// Clears any pending decoded character.
199    #[inline]
200    fn reset(&mut self) {
201        self.pending = None;
202        self.decoder.reset();
203        self.encoder.reset();
204    }
205
206    /// Converts source units to target units through the configured decoder and encoder.
207    fn convert(
208        &mut self,
209        input: &[D::Unit],
210        input_index: usize,
211        output: &mut [E::Unit],
212        output_index: usize,
213    ) -> Result<CoderProgress, Self::Error> {
214        let mut read = 0;
215        let mut written = 0;
216
217        if let Some(ch) = self.pending {
218            let status = self.write_pending(ch, output, output_index, &mut written)?;
219            if matches!(status.status(), CoderStatus::NeedOutput { .. }) {
220                let status = CoderStatus::NeedOutput {
221                    output_index: output_index + written,
222                    required: status.required(),
223                    available: status.available(),
224                };
225                return Ok(CoderProgress::new(status, read, written));
226            }
227        }
228
229        while input_index + read < input.len() {
230            let mut decoded = ['\0'; 4];
231            let decode_progress = self.decoder.convert(input, input_index + read, &mut decoded, 0)?;
232            let decode_status = decode_progress.status();
233            let decode_read = decode_progress.read();
234            read += decode_read;
235
236            if decode_progress.written() > 0 {
237                for &ch in decoded.iter().take(decode_progress.written()) {
238                    self.pending = Some(ch);
239                    let status = self.write_pending(ch, output, output_index, &mut written)?;
240                    if matches!(status.status(), CoderStatus::NeedOutput { .. }) {
241                        let status = CoderStatus::NeedOutput {
242                            output_index: output_index + written,
243                            required: status.required(),
244                            available: status.available(),
245                        };
246                        return Ok(CoderProgress::new(status, read, written));
247                    }
248                }
249            }
250
251            match decode_status {
252                CoderStatus::Complete if input_index + read >= input.len() || decode_read == 0 => {
253                    return Ok(CoderProgress::complete(read, written));
254                }
255                CoderStatus::Complete => {}
256                CoderStatus::NeedInput { .. } => {
257                    let status = CoderStatus::NeedInput {
258                        input_index: input_index + read,
259                        required: decode_progress.required(),
260                        available: decode_progress.available(),
261                    };
262                    return Ok(CoderProgress::new(status, read, written));
263                }
264                CoderStatus::NeedOutput { .. } => {
265                    debug_assert!(
266                        decode_read > 0,
267                        "Decoder must consume at least one input unit when reporting NeedOutput"
268                    );
269                }
270            }
271        }
272
273        Ok(CoderProgress::complete(read, written))
274    }
275
276    /// Flushes one pending decoded character, if any.
277    ///
278    /// # Parameters
279    ///
280    /// - `output`: Complete output slice visible to the converter.
281    /// - `output_index`: Absolute output index where writing starts.
282    ///
283    /// # Returns
284    ///
285    /// Returns completed progress when no pending character exists.
286    /// Returns `NeedOutput` when pending output cannot be flushed due to
287    /// missing output capacity.
288    ///
289    /// # Errors
290    ///
291    /// Returns `CharsetConvertError::Encode` when encoding the pending
292    /// character violates target charset policy.
293    #[inline]
294    fn finish(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<CoderProgress, Self::Error> {
295        if let Some(ch) = self.pending {
296            let mut written = 0;
297            let status = self.write_pending(ch, output, output_index, &mut written)?;
298            if matches!(status.status(), CoderStatus::NeedOutput { .. }) {
299                let status = CoderStatus::NeedOutput {
300                    output_index: output_index + written,
301                    required: status.required(),
302                    available: status.available(),
303                };
304                return Ok(CoderProgress::new(status, 0, written));
305            }
306            return Ok(CoderProgress::complete(0, written));
307        }
308        Ok(CoderProgress::complete(0, 0))
309    }
310}