dezoomify_rs/iipimage/
mod.rs1use crate::Vec2d;
2use std::str::FromStr;
3use custom_error::custom_error;
4use std::sync::Arc;
5use crate::dezoomer::{TilesRect, Dezoomer, DezoomerInput, ZoomLevels, DezoomerError, IntoZoomLevels, DezoomerInputWithContents};
6use std::convert::TryFrom;
7use std::iter::successors;
8use std::fmt::Debug;
9use regex::Regex;
10
11#[derive(Default)]
14pub struct IIPImage;
15
16const META_REQUEST_PARAMS: &str = "&OBJ=Max-size&OBJ=Tile-size&OBJ=Resolution-number";
17
18impl Dezoomer for IIPImage {
19 fn name(&self) -> &'static str { "IIPImage" }
20
21 fn zoom_levels(&mut self, data: &DezoomerInput) -> Result<ZoomLevels, DezoomerError> {
22 if data.uri.ends_with(META_REQUEST_PARAMS) {
23 let DezoomerInputWithContents { uri, contents } = data.with_contents()?;
24 let iter = iter_levels(uri, contents).map_err(DezoomerError::wrap)?;
25 Ok(iter.into_zoom_levels())
26 } else {
27 let re = Regex::new("(?i)\\?FIF").unwrap();
28 self.assert(re.is_match(&data.uri))?;
29 let mut meta_uri: String = data.uri.chars().take_while(|&c| c != '&').collect();
30 meta_uri += META_REQUEST_PARAMS;
31 Err(DezoomerError::NeedsData { uri: meta_uri })
32 }
33 }
34}
35
36fn arcs<T, U: ?Sized>(v: T) -> impl Iterator<Item=Arc<U>>
37 where Arc<U>: From<T> {
38 successors(Some(Arc::from(v)), |x| Some(Arc::clone(x)))
39}
40
41fn iter_levels(uri: &str, contents: &[u8])
42 -> Result<impl Iterator<Item=Level> + 'static, IIPError> {
43 let base = String::from(uri.trim_end_matches(META_REQUEST_PARAMS));
44 let meta = Metadata::try_from(contents)?;
45 let levels =
46 (0..meta.levels).zip(arcs(base)).zip(arcs(meta))
47 .map(|((level, base), metadata)|
48 Level { metadata, base, level });
49 Ok(levels)
50}
51
52#[derive(PartialEq, Eq)]
53struct Level {
54 metadata: Arc<Metadata>,
55 base: Arc<str>,
56 level: u32,
57}
58
59impl Debug for Level {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(f, "IIPImage")
62 }
63}
64
65impl TilesRect for Level {
66 fn size(&self) -> Vec2d {
67 let reverse_level = self.metadata.levels - self.level - 1;
68 self.metadata.size / 2_u32.pow(reverse_level)
69 }
70
71 fn tile_size(&self) -> Vec2d { self.metadata.tile_size }
72
73 fn tile_url(&self, Vec2d { x, y }: Vec2d) -> String {
74 let Vec2d { x: width, .. } = self.size().ceil_div(self.tile_size());
75 format!("{base}&JTL={level},{tile_index}",
76 base = self.base,
77 level = self.level,
78 tile_index = y * width + x
79 )
80 }
81}
82
83#[derive(Debug, PartialEq, Eq)]
84pub struct Metadata {
85 size: Vec2d,
86 tile_size: Vec2d,
87 levels: u32,
88}
89
90impl FromStr for Metadata {
91 type Err = IIPError;
92
93 fn from_str(s: &str) -> Result<Self, Self::Err> {
94 use IIPError::*;
95 let mut size = Err(MissingKey { key: "Max-size" });
96 let mut tile_size = Err(MissingKey { key: "Tile-size" });
97 let mut levels = Err(MissingKey { key: "Resolution-number" });
98 for line in s.lines() {
99 let mut parts = line.split(':');
100 let key: &str = parts.next().unwrap_or("").trim();
101 let val: &str = parts.next().unwrap_or("").trim();
102 let mut nums = val.split_ascii_whitespace().map(|s| s.parse::<u32>().ok());
103 let n1 = nums.next().flatten();
104 let n2 = nums.next().flatten();
105 if key.eq_ignore_ascii_case("max-size") {
106 if let (Some(x), Some(y)) = (n1, n2) {
107 size = Ok(Vec2d { x, y })
108 }
109 } else if key.eq_ignore_ascii_case("tile-size") {
110 if let (Some(x), Some(y)) = (n1, n2) {
111 tile_size = Ok(Vec2d { x, y })
112 }
113 } else if key.eq_ignore_ascii_case("resolution-number") {
114 if let Some(n) = n1 { levels = Ok(n) }
115 }
116 }
117 Ok(Metadata { size: size?, tile_size: tile_size?, levels: levels? })
118 }
119}
120
121impl TryFrom<&[u8]> for Metadata {
122 type Error = IIPError;
123
124 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
125 let s = std::str::from_utf8(value)?;
126 Metadata::from_str(s)
127 }
128}
129
130custom_error! {#[derive(PartialEq, Eq)] pub IIPError
131 MissingKey{key: &'static str} = "missing key '{key}' in the IIPImage metadata file",
132 Utf8{source: std::str::Utf8Error} = "Invalid IIPImage metadata file: {source}",
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::dezoomer::PageContents;
139
140 #[test]
141 fn test_lowercase() {
142 let uri = "https://publications-images.artic.edu/fcgi-bin/iipsrv.fcgi?fif=osci/Renoir_11/Color_Corrected/G39094sm2.ptif&jtl=4,11".to_string();
143 let metadata_uri = "https://publications-images.artic.edu/fcgi-bin/iipsrv.fcgi?fif=osci/Renoir_11/Color_Corrected/G39094sm2.ptif&OBJ=Max-size&OBJ=Tile-size&OBJ=Resolution-number";
144 let data = DezoomerInput { uri, contents: PageContents::Unknown };
145 match IIPImage::default().zoom_levels(&data) {
146 Err(DezoomerError::NeedsData { uri }) => assert_eq!(uri, metadata_uri),
147 _ => panic!("Unexpected result")
148 }
149 }
150
151 #[test]
152 fn test_parse_metadata() {
153 let contents = &b"Max-size:512 512\nTile-size:256 256\nResolution-number:2"[..];
154 let base: Arc<str> = Arc::from("http://test.com/");
155 let levels: Vec<Level> = iter_levels(&base, contents).unwrap().collect();
156 assert_eq!(&levels, &[
157 Level {
158 metadata: Arc::from(Metadata {
159 size: Vec2d { x: 512, y: 512 },
160 tile_size: Vec2d { x: 256, y: 256 },
161 levels: 2,
162 }),
163 base: base.clone(),
164 level: 0,
165 },
166 Level {
167 metadata: Arc::from(Metadata {
168 size: Vec2d { x: 512, y: 512 },
169 tile_size: Vec2d { x: 256, y: 256 },
170 levels: 2,
171 }),
172 base,
173 level: 1,
174 }
175 ]);
176 assert_eq!(levels[0].tile_url(Vec2d { x: 0, y: 0 }), "http://test.com/&JTL=0,0");
177 assert_eq!(levels[1].tile_url(Vec2d { x: 0, y: 1 }), "http://test.com/&JTL=1,2");
178 }
179
180 #[test]
181 fn test_zoom_levels() {
182 let source = "
183 Max-size:23235 23968
184 Tile-size:256 256
185 Resolution-number:9
186 ";
187 assert_eq!(source.parse(), Ok(Metadata {
188 size: Vec2d { x: 23235, y: 23968 },
189 tile_size: Vec2d { x: 256, y: 256 },
190 levels: 9,
191 }))
192 }
193}