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}