dezoomify_rs/
dezoomer.rs

1use std::borrow::Borrow;
2use std::collections::HashMap;
3use std::error::Error;
4use std::fmt::Debug;
5use std::str::FromStr;
6
7pub use crate::errors::DezoomerError;
8
9pub use super::Vec2d;
10use super::ZoomError;
11use std::fmt;
12use crate::dezoomer::PageContents::Success;
13
14pub enum PageContents {
15    Unknown,
16    Success(Vec<u8>),
17    Error(ZoomError),
18}
19
20impl From<Result<Vec<u8>, ZoomError>> for PageContents {
21    fn from(res: Result<Vec<u8>, ZoomError>) -> Self {
22        res.map(Self::Success).unwrap_or_else(Self::Error)
23    }
24}
25
26impl std::fmt::Debug for PageContents {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            Self::Unknown => f.write_str("<not yet available>"),
30            Success(contents) => f.write_str(&String::from_utf8_lossy(contents)),
31            PageContents::Error(e) => write!(f, "{}", e)
32        }
33    }
34}
35
36pub struct DezoomerInput {
37    pub uri: String,
38    pub contents: PageContents,
39}
40
41pub struct DezoomerInputWithContents<'a> {
42    pub uri: &'a str,
43    pub contents: &'a [u8],
44}
45
46impl DezoomerInput {
47    pub fn with_contents(&self) -> Result<DezoomerInputWithContents, DezoomerError> {
48        match &self.contents {
49            PageContents::Unknown => Err(DezoomerError::NeedsData {
50                uri: self.uri.clone(),
51            }),
52            Success(contents) => Ok(DezoomerInputWithContents {
53                uri: &self.uri,
54                contents,
55            }),
56            PageContents::Error(e) => {
57                Err(DezoomerError::DownloadError{msg: e.to_string()})
58            }
59        }
60    }
61}
62
63/// A single image with a given width and height
64pub type ZoomLevel = Box<dyn TileProvider + Sync>;
65
66/// A collection of multiple resolutions at which an image is available
67pub type ZoomLevels = Vec<ZoomLevel>;
68
69pub trait IntoZoomLevels {
70    fn into_zoom_levels(self) -> ZoomLevels;
71}
72
73impl<I, Z> IntoZoomLevels for I
74where
75    I: Iterator<Item = Z>,
76    Z: TileProvider + Sync + 'static,
77{
78    fn into_zoom_levels(self) -> ZoomLevels {
79        self.map(|x| Box::new(x) as ZoomLevel).collect()
80    }
81}
82
83/// A trait that should be implemented by every zoomable image dezoomer
84pub trait Dezoomer {
85    /// The name of the image format. Used for dezoomer selection
86    fn name(&self) -> &'static str;
87
88    /// List of the various sizes at which an image is available
89    fn zoom_levels(&mut self, data: &DezoomerInput) -> Result<ZoomLevels, DezoomerError>;
90    fn assert(&self, c: bool) -> Result<(), DezoomerError> {
91        if c {
92            Ok(())
93        } else {
94            Err(self.wrong_dezoomer())
95        }
96    }
97    fn wrong_dezoomer(&self) -> DezoomerError {
98        DezoomerError::WrongDezoomer { name: self.name() }
99    }
100}
101
102#[derive(Clone, Copy)]
103pub struct TileFetchResult {
104    pub count: u64,
105    pub successes: u64,
106    pub tile_size: Option<Vec2d>,
107}
108
109impl TileFetchResult {
110    pub fn is_success(&self) -> bool {
111        self.tile_size
112            .filter(|&Vec2d { x, y }| x > 0 && y > 0)
113            .is_some()
114            && self.successes > 0
115    }
116}
117
118type PostProcessResult = Result<Vec<u8>, Box<dyn Error + Send>>;
119// TODO : fix
120// see: https://github.com/rust-lang/rust/issues/63033
121#[derive(Clone, Copy)]
122pub enum PostProcessFn {
123    Fn(fn(&TileReference, Vec<u8>) -> PostProcessResult),
124    None,
125}
126
127/// A single tiled image
128pub trait TileProvider: Debug {
129    /// Provide a list of image tiles. Should be called repetitively until it returns
130    /// an empty list. Each new call takes the results of the previous tile fetch as a parameter.
131    fn next_tiles(&mut self, previous: Option<TileFetchResult>) -> Vec<TileReference>;
132
133    /// A function that takes the downloaded tile bytes and decodes them
134    fn post_process_fn(&self) -> PostProcessFn {
135        PostProcessFn::None
136    }
137
138    /// The name of the format
139    fn name(&self) -> String {
140        format!("{:?}", self)
141    }
142
143    /// The title of the image
144    fn title(&self) -> Option<String> { None }
145
146    /// The width and height of the image. Can be unknown when dezooming starts
147    fn size_hint(&self) -> Option<Vec2d> {
148        None
149    }
150
151    /// A collection of http headers to use when requesting the tiles
152    fn http_headers(&self) -> HashMap<String, String> {
153        HashMap::new()
154    }
155}
156
157/// Used to iterate over all the batches of tiles in a zoom level
158pub struct ZoomLevelIter<'a> {
159    zoom_level: &'a mut ZoomLevel,
160    previous: Option<TileFetchResult>,
161    waiting_results: bool,
162}
163
164impl<'a> ZoomLevelIter<'a> {
165    pub fn new(zoom_level: &'a mut ZoomLevel) -> Self {
166        ZoomLevelIter { zoom_level, previous: None, waiting_results: false }
167    }
168    pub fn next_tile_references(&mut self) -> Option<Vec<TileReference>> {
169        assert!(!self.waiting_results);
170        self.waiting_results = true;
171        let tiles = self.zoom_level.next_tiles(self.previous);
172        if tiles.is_empty() { None } else { Some(tiles) }
173    }
174    pub fn set_fetch_result(&mut self, result: TileFetchResult) {
175        assert!(self.waiting_results);
176        self.waiting_results = false;
177        self.previous = Some(result)
178    }
179    pub fn size_hint(&self) -> Option<Vec2d> {
180        self.zoom_level.size_hint()
181    }
182}
183
184/// Shortcut to return a single zoom level from a dezoomer
185pub fn single_level<T: TileProvider + Sync + 'static>(
186    level: T,
187) -> Result<ZoomLevels, DezoomerError> {
188    Ok(vec![Box::new(level)])
189}
190
191pub trait TilesRect: Debug {
192    fn size(&self) -> Vec2d;
193    fn tile_size(&self) -> Vec2d;
194    fn tile_url(&self, pos: Vec2d) -> String;
195    fn title(&self) -> Option<String> { None }
196    fn tile_ref(&self, pos: Vec2d) -> TileReference {
197        TileReference {
198            url: self.tile_url(pos),
199            position: self.tile_size() * pos,
200        }
201    }
202    fn post_process_fn(&self) -> PostProcessFn {
203        PostProcessFn::None
204    }
205
206    fn tile_count(&self) -> u32 {
207        let Vec2d { x, y } = self.size().ceil_div(self.tile_size());
208        x * y
209    }
210}
211
212impl<T: TilesRect> TileProvider for T {
213    fn next_tiles(&mut self, previous: Option<TileFetchResult>) -> Vec<TileReference> {
214        // When the dimensions are known in advance, we can always generate
215        // a single batch of tile references. So any subsequent call returns an empty vector.
216        if previous.is_some() {
217            return vec![];
218        }
219
220        let tile_size = self.tile_size();
221        let Vec2d { x: w, y: h } = self.size().ceil_div(tile_size);
222        let this: &T = self.borrow(); // Immutable borrow
223        (0..h)
224            .flat_map(move |y| (0..w).map(move |x| this.tile_ref(Vec2d { x, y })))
225            .collect()
226    }
227
228    fn post_process_fn(&self) -> PostProcessFn {
229        TilesRect::post_process_fn(self)
230    }
231
232    fn name(&self) -> String {
233        let Vec2d { x, y } = self.size();
234        format!(
235            "{:?} ({:>5} x {:>5} pixels, {:>5} tiles)",
236            self,
237            x,
238            y,
239            self.tile_count()
240        )
241    }
242
243    fn title(&self) -> Option<String> { TilesRect::title(self) }
244
245    fn size_hint(&self) -> Option<Vec2d> {
246        Some(self.size())
247    }
248
249    fn http_headers(&self) -> HashMap<String, String> {
250        let mut headers = HashMap::new();
251        // By default, use the first tile as the referer, so that it is on the same domain
252        headers.insert("Referer".into(), self.tile_url(Vec2d::default()));
253        headers
254    }
255}
256
257#[derive(Debug, PartialEq, Eq, Hash, Clone)]
258pub struct TileReference {
259    pub url: String,
260    pub position: Vec2d,
261}
262
263impl FromStr for TileReference {
264    type Err = ZoomError;
265
266    fn from_str(tile_str: &str) -> Result<Self, Self::Err> {
267        let mut parts = tile_str.split(' ');
268        let make_error = || ZoomError::MalformedTileStr {
269            tile_str: String::from(tile_str),
270        };
271
272        if let (Some(x), Some(y), Some(url)) = (parts.next(), parts.next(), parts.next()) {
273            let x: u32 = x.parse().map_err(|_| make_error())?;
274            let y: u32 = y.parse().map_err(|_| make_error())?;
275            Ok(TileReference {
276                url: String::from(url),
277                position: Vec2d { x, y },
278            })
279        } else {
280            Err(make_error())
281        }
282    }
283}
284
285impl fmt::Display for TileReference {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        f.write_str(&self.url)
288    }
289}
290
291#[cfg(test)]
292mod tests {
293    use super::*;
294
295    #[derive(Debug)]
296    struct FakeLvl;
297
298    impl TilesRect for FakeLvl {
299        fn size(&self) -> Vec2d {
300            Vec2d { x: 100, y: 100 }
301        }
302
303        fn tile_size(&self) -> Vec2d {
304            Vec2d { x: 60, y: 60 }
305        }
306
307        fn tile_url(&self, pos: Vec2d) -> String {
308            format!("{},{}", pos.x, pos.y)
309        }
310    }
311
312    #[test]
313    fn assert_tiles() {
314        let mut lvl: ZoomLevel = Box::new(FakeLvl {});
315        let mut all_tiles = vec![];
316        let mut zoom_level_iter = ZoomLevelIter::new(&mut lvl);
317        while let Some(tiles) = zoom_level_iter.next_tile_references() {
318            all_tiles.extend(tiles);
319            zoom_level_iter.set_fetch_result(TileFetchResult {
320                count: 0,
321                successes: 0,
322                tile_size: None,
323            });
324        };
325        assert_eq!(
326            all_tiles,
327            vec![
328                TileReference {
329                    url: "0,0".into(),
330                    position: Vec2d { x: 0, y: 0 },
331                },
332                TileReference {
333                    url: "1,0".into(),
334                    position: Vec2d { x: 60, y: 0 },
335                },
336                TileReference {
337                    url: "0,1".into(),
338                    position: Vec2d { x: 0, y: 60 },
339                },
340                TileReference {
341                    url: "1,1".into(),
342                    position: Vec2d { x: 60, y: 60 },
343                }
344            ]
345        );
346    }
347}