qubit_text_codec/codec/latin1_codec.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 Charset,
12 CharsetCodec,
13 CharsetDecodeError,
14 CharsetDecodeErrorKind,
15 CharsetDecodeResult,
16 CharsetEncodeError,
17 CharsetEncodeErrorKind,
18 CharsetEncodeResult,
19 DecodeStatus,
20 Unicode,
21};
22
23/// Single-byte ISO-8859-1 codec for bytes.
24///
25/// `Latin1Codec` converts between ISO-8859-1 bytes and Unicode scalar values.
26#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
27pub struct Latin1Codec;
28
29impl Latin1Codec {
30 /// Returns the ISO-8859-1 charset descriptor.
31 ///
32 /// # Returns
33 ///
34 /// Returns [`Charset::ISO_8859_1`].
35 #[must_use]
36 #[inline]
37 pub const fn charset(self) -> Charset {
38 Charset::ISO_8859_1
39 }
40
41 /// Returns the maximum number of bytes needed for one Latin-1 character.
42 ///
43 /// # Returns
44 ///
45 /// Returns `1`.
46 #[must_use]
47 #[inline]
48 pub const fn max_units_per_char(self) -> usize {
49 1
50 }
51}
52
53impl CharsetCodec for Latin1Codec {
54 type Unit = u8;
55 /// Returns the charset descriptor for this codec.
56 ///
57 /// # Returns
58 ///
59 /// Returns [`Charset::ISO_8859_1`].
60 #[inline]
61 fn charset(&self) -> Charset {
62 Charset::ISO_8859_1
63 }
64
65 /// Returns the maximum number of output bytes for one character.
66 ///
67 /// # Returns
68 ///
69 /// Returns `1`.
70 #[inline]
71 fn max_units_per_char(&self) -> usize {
72 1
73 }
74
75 /// Decodes one ISO-8859-1 byte into a `char`.
76 ///
77 /// # Parameters
78 ///
79 /// - `input`: Complete input byte slice.
80 /// - `index`: Absolute byte index at which decoding starts.
81 ///
82 /// # Returns
83 ///
84 /// `Ok(DecodeStatus::Complete { value, consumed: 1 })` always when input exists.
85 ///
86 /// # Errors
87 ///
88 /// Returns [`CharsetDecodeErrorKind::MalformedSequence`] when `index` is out
89 /// of range.
90 #[inline]
91 fn decode_one(&self, input: &[u8], index: usize) -> CharsetDecodeResult<DecodeStatus> {
92 if index > input.len() {
93 let kind = CharsetDecodeErrorKind::MalformedSequence { value: None };
94 return Err(CharsetDecodeError::new(Charset::ISO_8859_1, kind, index));
95 }
96
97 if index == input.len() {
98 return Ok(DecodeStatus::NeedMore {
99 required: index + 1,
100 available: 0,
101 });
102 }
103
104 let value = input[index] as u32;
105 Ok(DecodeStatus::Complete {
106 value: Unicode::to_char(value).expect("valid Latin-1 byte decodes to Unicode scalar"),
107 consumed: 1,
108 })
109 }
110
111 /// Encodes one `char` into one ISO-8859-1 byte.
112 ///
113 /// # Parameters
114 ///
115 /// - `ch`: The character to encode.
116 /// - `output`: Output byte slice.
117 /// - `index`: Absolute output index where writing starts.
118 ///
119 /// # Returns
120 ///
121 /// `Ok(1)` when one byte is written.
122 ///
123 /// # Errors
124 ///
125 /// * `CharsetEncodeErrorKind::BufferTooSmall` if `index >= output.len()`.
126 /// * `CharsetEncodeErrorKind::UnmappableCharacter` if `ch` > `U+00FF`.
127 #[inline]
128 fn encode_one(&self, ch: char, output: &mut [u8], index: usize) -> CharsetEncodeResult<usize> {
129 if index >= output.len() {
130 let kind = CharsetEncodeErrorKind::BufferTooSmall {
131 required: index + 1,
132 available: 0,
133 };
134 return Err(CharsetEncodeError::new(Charset::ISO_8859_1, kind, index));
135 }
136
137 let value = ch as u32;
138 if value > Unicode::LATIN1_MAX {
139 let kind = CharsetEncodeErrorKind::UnmappableCharacter { value };
140 return Err(CharsetEncodeError::new(Charset::ISO_8859_1, kind, index));
141 }
142
143 // Since we validated `value`, cast is safe for 0..=0xFF.
144 output[index] = value as u8;
145 Ok(1)
146 }
147}