qubit_codec_text/decode/charset_decoder.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 qubit_codec::{
9 BufferedDecodeEngine,
10 BufferedDecoder,
11 BufferedTranscoder,
12 CapacityError,
13 FinishError,
14 TranscodeProgress,
15};
16
17use crate::{
18 CharsetCodec,
19 CharsetDecodeError,
20 MalformedAction,
21};
22
23use super::{
24 charset_decode_hooks::CharsetDecodeHooks,
25 charset_decode_policy::CharsetDecodePolicy,
26};
27
28/// Converts units of one charset into Unicode scalar values.
29///
30/// `CharsetDecoder` wraps a low-level [`CharsetCodec`] and applies the
31/// configured [`MalformedAction`] whenever the codec reports malformed input.
32/// The decoder asks the wrapped codec whether one value can be decoded from the
33/// currently available units. If the codec reports a valid incomplete prefix,
34/// the tail is left in the caller-provided input slice and
35/// [`crate::TranscodeStatus::NeedInput`] is returned. Callers must handle
36/// incomplete EOF tails before calling [`BufferedTranscoder::finish`].
37///
38/// # Type Parameters
39///
40/// - `C`: Low-level charset codec used to decode source storage units into one
41/// Unicode scalar value.
42#[derive(Clone, Debug, Eq, PartialEq)]
43pub struct CharsetDecoder<C>
44where
45 C: CharsetCodec,
46{
47 /// Common buffered decode engine.
48 engine: BufferedDecodeEngine<C, CharsetDecodeHooks>,
49 /// Public malformed-input policy metadata.
50 policy: CharsetDecodePolicy,
51}
52
53impl<C> CharsetDecoder<C>
54where
55 C: CharsetCodec,
56{
57 /// Creates a decoder with default replacement policy.
58 ///
59 /// # Parameters
60 ///
61 /// - `codec`: Low-level charset codec used to decode input units.
62 ///
63 /// # Returns
64 ///
65 /// Returns a decoder whose malformed action is [`MalformedAction::Replace`]
66 /// and whose replacement character is `U+FFFD`.
67 #[must_use]
68 #[inline(always)]
69 pub fn new(codec: C) -> Self {
70 Self::with_policy(codec, CharsetDecodePolicy::default())
71 }
72
73 /// Creates a decoder with an explicit malformed-input policy.
74 ///
75 /// # Parameters
76 ///
77 /// - `codec`: Low-level charset codec used to decode input units.
78 /// - `policy`: Malformed-input policy used by the decoder.
79 ///
80 /// # Returns
81 ///
82 /// Returns a decoder configured with `policy`.
83 #[must_use]
84 #[inline]
85 pub fn with_policy(codec: C, policy: CharsetDecodePolicy) -> Self {
86 let hooks = CharsetDecodeHooks::from_policy(policy);
87 Self {
88 engine: BufferedDecodeEngine::new(codec, hooks),
89 policy,
90 }
91 }
92
93 /// Returns the configured malformed-input action.
94 ///
95 /// # Returns
96 ///
97 /// Returns the action used when source input is malformed.
98 #[must_use]
99 #[inline(always)]
100 pub const fn malformed_action(&self) -> MalformedAction {
101 self.policy.malformed_action()
102 }
103
104 /// Returns the configured replacement character.
105 ///
106 /// # Returns
107 ///
108 /// Returns the character emitted when [`MalformedAction::Replace`] is used.
109 #[must_use]
110 #[inline(always)]
111 pub const fn replacement(&self) -> char {
112 self.policy.replacement()
113 }
114}
115
116impl<C> BufferedDecoder<C::Unit, char> for CharsetDecoder<C> where
117 C: CharsetCodec
118{
119}
120
121impl<C> BufferedTranscoder<C::Unit, char> for CharsetDecoder<C>
122where
123 C: CharsetCodec,
124{
125 type Error = CharsetDecodeError;
126
127 /// Returns the maximum number of characters decoded from `input_len` units.
128 #[inline(always)]
129 fn max_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
130 self.engine.max_output_len(input_len)
131 }
132
133 /// Returns the maximum number of characters emitted by finishing internal
134 /// state.
135 #[inline(always)]
136 fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
137 Ok(self.engine.max_finish_output_len())
138 }
139
140 /// Clears hook-owned state while keeping decoder policy.
141 #[inline(always)]
142 fn reset(&mut self) {
143 self.engine.reset();
144 }
145
146 /// Decodes source units into Unicode scalar values while applying malformed
147 /// policy.
148 #[inline(always)]
149 fn transcode(
150 &mut self,
151 input: &[C::Unit],
152 input_index: usize,
153 output: &mut [char],
154 output_index: usize,
155 ) -> Result<TranscodeProgress, Self::Error> {
156 self.engine
157 .transcode(input, input_index, output, output_index)
158 }
159
160 /// Finishes decoder-owned final output after EOF.
161 #[inline(always)]
162 fn finish(
163 &mut self,
164 output: &mut [char],
165 output_index: usize,
166 ) -> Result<usize, FinishError<Self::Error>> {
167 self.engine.finish(output, output_index)
168 }
169}