ptero/method/
trailing_unicode.rs

1//! # Description
2//!
3//! This method encodes n-bits using available set of unicode whitespace. Larger sets provide better capacity
4//! but might not work for every channel - it depends which characters get sanitized or are actually used.
5//!
6//! The whitespace are added at the end of the line as some of them are actually visible and might
7//! lower the imperceptibility of this method.
8//!
9//! The amount of bits encoded by this implementation depends on used Unicode character set.
10//! It it at most 5 bits per line with [FULL_UNICODE_CHARACTER_SET].
11//!
12//! Encoder does not inform if there is not data left!
13pub mod character_sets;
14
15use std::error::Error;
16
17use crate::{
18    binary::BitVec,
19    context::{Context, ContextError},
20    decoder::Decoder,
21    encoder::{Capacity, Encoder, EncoderResult},
22};
23use log::trace;
24
25use crate::binary::Bit;
26
27use self::character_sets::{CharacterSetType, GetCharacterSet};
28
29use super::Method;
30
31/// Trailing unicode encoder for generic Unicode character sets.
32/// It uses the [UnicodeSet] to get the character given the n-bits
33/// (where n is the binary logarithm of the set size).
34///
35/// Accepts any [Context](crate::context::Context).
36#[derive(Debug, PartialEq)]
37pub struct TrailingUnicodeMethod {
38    character_set: CharacterSetType,
39}
40
41impl Default for TrailingUnicodeMethod {
42    fn default() -> Self {
43        Self::new(CharacterSetType::FullUnicodeSet)
44    }
45}
46
47impl TrailingUnicodeMethod {
48    pub fn new(unicode_set: CharacterSetType) -> Self {
49        TrailingUnicodeMethod {
50            character_set: unicode_set,
51        }
52    }
53}
54
55impl Capacity for TrailingUnicodeMethod {
56    fn bitrate(&self) -> usize {
57        let amount_of_bits = std::mem::size_of::<usize>() * 8;
58        amount_of_bits - self.character_set.size().leading_zeros() as usize
59    }
60}
61
62impl<E> Encoder<E> for TrailingUnicodeMethod
63where
64    E: Context,
65{
66    fn partial_encode(
67        &self,
68        context: &mut E,
69        data: &mut dyn Iterator<Item = Bit>,
70    ) -> Result<EncoderResult, Box<dyn Error>> {
71        let set_capacity = self.bitrate();
72        let next_n_bits = data.take(set_capacity).collect::<Vec<Bit>>();
73        // We might not take exactly 5 bits, lets ensure we properly pad with 0 bits
74        let amount_bits_taken = next_n_bits.len();
75        let mut number: u32 = BitVec::from(next_n_bits).into();
76        number <<= set_capacity - amount_bits_taken;
77
78        trace!(
79            "Took {} bits and assembled a number: {}",
80            set_capacity,
81            number
82        );
83        if let Some(character) = self.character_set.get_character(number) {
84            trace!(
85                "Putting unicode character {:?} at the end of the line",
86                character
87            );
88            context.get_current_text_mut()?.push(*character);
89        }
90
91        Ok(EncoderResult::Success)
92    }
93}
94
95impl<D> Decoder<D> for TrailingUnicodeMethod
96where
97    D: Context,
98{
99    fn partial_decode(&self, context: &D) -> Result<Vec<Bit>, ContextError> {
100        if let Some(character) = context.get_current_text()?.chars().last() {
101            let decoded_number = self.character_set.character_to_bits(&character);
102            trace!(
103                "Found {:?} at the end of the line, decoded into {}",
104                &character,
105                decoded_number
106            );
107            let data: Vec<Bit> = BitVec::from(decoded_number).into();
108            let data_length = data.len();
109            // Skip the unnecessary zeroes from the beginning
110            let data_iter = data.into_iter().skip(data_length - self.bitrate());
111            let decoded_data = data_iter.collect::<Vec<Bit>>();
112            return Ok(decoded_data);
113        }
114
115        Ok(BitVec::filled_with(0, self.bitrate()).into())
116    }
117}
118
119impl<E, D> Method<E, D> for TrailingUnicodeMethod
120where
121    E: Context,
122    D: Context,
123{
124    fn method_name(&self) -> String {
125        "TrailingUnicodeMethod".to_string()
126    }
127}