bingus/
lib.rs

1// #![deny(unused_crate_dependencies)]
2
3#[cfg(feature = "binary")]
4pub mod bin;
5#[cfg(feature = "documents")]
6pub mod doc;
7#[cfg(feature = "fonts")]
8pub mod fnt;
9#[cfg(feature = "pictures")]
10pub mod img;
11#[cfg(feature = "music")]
12pub mod snd;
13#[cfg(feature = "text")]
14pub mod txt;
15
16pub type Bytes = Vec<u8>;
17
18pub mod dynamic {
19    #[cfg(feature = "text")]
20    use std::string::FromUtf8Error;
21    use std::{
22        borrow::Cow,
23        fs::File,
24        io::{self, Cursor, Read, Write},
25        path::Path,
26    };
27
28    #[cfg(all(feature = "documents", feature = "shiva"))]
29    use super::doc::ShivaDocument;
30    #[cfg(all(feature = "fonts", feature = "font-kit"))]
31    use super::fnt::Font as FontKitFont;
32    #[cfg(all(feature = "pictures", feature = "image"))]
33    use super::img::{self, DynamicImage};
34    #[cfg(feature = "music")]
35    use super::snd::{self, Audio};
36    #[cfg(feature = "text")]
37    use super::txt::Text;
38    use super::{Bendable, Bytes, IntoDataBytes, TryFromDataBytes};
39    #[cfg(not(feature = "text"))]
40    use std::marker::PhantomData;
41
42    use cfg_if::cfg_if;
43    #[cfg(all(feature = "fonts", feature = "font-kit"))]
44    use font_kit::error::FontLoadingError;
45    pub use infer::*;
46    #[cfg(all(feature = "documents", feature = "printpdf"))]
47    use printpdf::PdfDocument;
48    #[cfg(all(feature = "documents", feature = "shiva"))]
49    use shiva::core::{bytes, Document, DocumentType};
50    use thiserror::Error;
51
52    pub enum DynamicBendable<'a> {
53        #[cfg(all(feature = "pictures", feature = "image"))]
54        Image(DynamicImage),
55        #[cfg(feature = "binary")]
56        Binary(Bytes),
57        #[cfg(feature = "music")]
58        Sound(Audio),
59        #[cfg(feature = "text")]
60        Text(Text<'a>),
61        #[cfg(not(feature = "text"))]
62        Phantom(PhantomData<&'a ()>),
63        #[cfg(all(feature = "documents", feature = "shiva"))]
64        Doc(ShivaDocument),
65        #[cfg(all(feature = "documents", feature = "printpdf"))]
66        Archive(PdfDocument),
67        Meta,
68        #[cfg(all(feature = "fonts", feature = "font-kit"))]
69        Font(FontKitFont),
70    }
71
72    #[cfg(feature = "shiva")]
73    #[derive(Debug, Error)]
74    #[error("extension is unknown by Shiva")]
75    pub struct ShivaUnknownExtensionError;
76
77    #[cfg(feature = "shiva")]
78    #[derive(Debug, Error)]
79    pub enum ShivaError {
80        #[error("{0}")]
81        UnknownExtension(#[from] ShivaUnknownExtensionError),
82        #[error("{0}")]
83        Anyhow(#[from] anyhow::Error),
84    }
85
86    #[derive(Debug, Error)]
87    pub enum OpenError {
88        #[error("io: {0}")]
89        Io(#[from] io::Error),
90        #[cfg(all(feature = "pictures", feature = "image"))]
91        #[error("image: {0}")]
92        Image(#[from] img::ImageError),
93        #[cfg(feature = "music")]
94        #[error("audio: {0}")]
95        Audio(#[from] snd::AudioOpenError),
96        #[cfg(all(feature = "documents", feature = "printpdf"))]
97        #[error("pdf: {0}")]
98        Pdf(String),
99        #[cfg(feature = "text")]
100        #[error("text: {0}")]
101        Text(#[from] FromUtf8Error),
102        #[cfg(all(feature = "documents", feature = "shiva"))]
103        #[error("document: {0}")]
104        Document(#[from] ShivaError),
105        #[cfg(all(feature = "fonts", feature = "font-kit"))]
106        #[error("font: {0:?}")]
107        Font(#[from] FontLoadingError),
108    }
109
110    impl TryFromDataBytes for File {
111        type Error = io::Error;
112        type Format = Box<dyn AsRef<Path>>;
113
114        fn try_from_data_bytes(
115            bytes: Bytes,
116            format: Self::Format,
117            _crop: crate::Crop,
118        ) -> Result<Self, Self::Error>
119        where
120            Self: Sized,
121        {
122            let mut file = File::create(format.as_ref())?;
123            file.write_all(&bytes)?;
124            Ok(file)
125        }
126    }
127
128    impl IntoDataBytes for File {
129        fn into_data_bytes(self) -> Bytes {
130            self.bytes().flatten().collect() // !! will return an empty vec if it can't read the file!
131        }
132    }
133
134    impl Bendable for File {
135        type Unit = u8;
136
137        /// /!\ may panic with io errors /!\
138        fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(mut self, f: F) -> Self {
139            let mut bytes = Vec::new();
140            self.read_to_end(&mut bytes).expect("couldn't read file");
141            cfg_if! {
142                if #[cfg(feature = "binary")] {
143                    self.write_all(&bytes.map(f)).expect("couldn't write file");
144                } else {
145                    self.write_all(&bytes.into_iter().map(|e| f(Cow::Owned(e))).collect::<Vec<u8>>()).expect("couldn't write file");
146                }
147            }
148            self
149        }
150    }
151
152    pub type DynamicResult = Result<Option<DynamicBendable<'static>>, OpenError>;
153
154    pub fn guess(t: Option<infer::Type>, bytes: Bytes) -> DynamicResult {
155        use MatcherType::*;
156        t.map(|t| (t.matcher_type(), t.extension()))
157        .map(
158            |(matcher, extension)| -> Result<DynamicBendable, OpenError> {
159                Ok(match matcher {
160                    #[cfg(all(feature = "pictures", feature = "image"))]
161                    Image => DynamicBendable::Image(img::load_from_memory(&bytes)?),
162                    #[cfg(feature = "music")]
163                    Audio => DynamicBendable::Sound(crate::snd::Audio::open(Cursor::new(bytes), None)?),
164                    #[cfg(all(feature = "documents", feature = "printpdf"))]
165                    Archive if extension == "pdf" => DynamicBendable::Archive(
166                        PdfDocument::try_from_data_bytes(
167                            bytes,
168                            (),
169                            Default::default(),
170                        )
171                        .map_err(OpenError::Pdf)?,
172                    ),
173                    #[cfg(all(feature = "documents", feature = "shiva"))]
174                    Archive | Doc => {
175                        let document_type = DocumentType::from_extension(extension)
176                            .ok_or(ShivaUnknownExtensionError)
177                            .map_err(ShivaError::UnknownExtension)?;
178                        DynamicBendable::Doc(ShivaDocument::new(
179                            Document::parse(
180                                &bytes::Bytes::from(bytes),
181                                document_type,
182                            )
183                            .map_err(ShivaError::Anyhow)?,
184                            document_type,
185                        ))
186                    }
187                    #[cfg(all(feature = "fonts", feature = "font-kit"))]
188                    Font => DynamicBendable::Font(FontKitFont::try_from_data_bytes(
189                        bytes,
190                        (),
191                        Default::default(),
192                    )?),
193                    #[cfg(feature = "text")]
194                    Text => DynamicBendable::Text(crate::txt::Text::try_from_data_bytes(
195                        bytes,
196                        (),
197                        Default::default(),
198                    ).unwrap()),
199                    #[cfg(feature = "binary")]
200                    _ => DynamicBendable::Binary(bytes),
201                    #[cfg(not(feature = "binary"))]
202                    _ => unimplemented!("no format reader available to open this thing (turn on the 'binary' feature to default to binary data)"),
203                })
204            },
205        )
206        .transpose()
207    }
208
209    pub fn open_file(path: impl AsRef<Path>) -> DynamicResult {
210        open(&mut File::open(path)?)
211    }
212
213    pub fn open(source: &mut impl Read) -> DynamicResult {
214        let contents = {
215            let mut c = Vec::new();
216            source.read_to_end(&mut c)?;
217            c
218        };
219        guess(infer::get(&contents), contents)
220    }
221}
222
223use std::{borrow::Cow, convert::Infallible};
224
225pub trait Bendable: TryFromDataBytes + IntoDataBytes {
226    type Unit;
227    fn bend_into<T: TryFromDataBytes>(
228        self,
229        format: <T as TryFromDataBytes>::Format,
230        crop: Crop,
231    ) -> Result<T, <T as TryFromDataBytes>::Error> {
232        T::try_from_data_bytes(self.into_data_bytes(), format, crop)
233    }
234    fn bend_from<T: IntoDataBytes>(
235        b: T,
236        format: <Self as TryFromDataBytes>::Format,
237        crop: Crop,
238    ) -> Result<Self, <Self as TryFromDataBytes>::Error> {
239        Self::try_from_data_bytes(b.into_data_bytes(), format, crop)
240    }
241    fn map<F: Fn(Cow<Self::Unit>) -> Self::Unit + Sync>(self, f: F) -> Self;
242}
243
244pub trait IntoDataBytes: Sized {
245    fn into_data_bytes(self) -> Bytes;
246}
247
248#[derive(Default)]
249pub enum Crop {
250    Start,
251    #[default]
252    End,
253}
254
255pub trait TryFromDataBytes {
256    type Error;
257    type Format;
258    fn try_from_data_bytes(
259        bytes: Bytes,
260        format: Self::Format,
261        crop: Crop,
262    ) -> Result<Self, Self::Error>
263    where
264        Self: Sized;
265}
266
267pub trait FromDataBytes {
268    type Format;
269    fn from_data_bytes(bytes: Bytes, format: Self::Format, crop: Crop) -> Self
270    where
271        Self: Sized;
272}
273
274impl<F, T> FromDataBytes for T
275where
276    T: TryFromDataBytes<Error = Infallible, Format = F>,
277{
278    type Format = <T as TryFromDataBytes>::Format;
279    fn from_data_bytes(bytes: Bytes, format: Self::Format, crop: Crop) -> Self {
280        T::try_from_data_bytes(bytes, format, crop).unwrap_or_else(|_| unreachable!())
281    }
282}