1use std::io::{Cursor, Read};
8use std::path::Path;
9
10use base64::Engine;
11use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
12use flate2::read::GzDecoder;
13use png::Encoder;
14
15use crate::convert::{ConvertOptions, PageConsumer, read_limited_file};
16use crate::document::html::{HtmlBlock, render_blocks_to_pages_with_warnings};
17use crate::error::{Error, Result};
18
19const MAX_MRC_INPUT_BYTES: u64 = 128 * 1024 * 1024;
20const MAX_MRC_DECOMPRESSED_BYTES: usize = 256 * 1024 * 1024;
21const MAX_MRC_DIMENSION: usize = 8192;
22const MAX_MRC_VOXELS: u64 = 300_000_000;
23const MAX_MRC_PLANES: usize = 1_000;
24const MAX_MRC_PNG_BYTES: usize = 16 * 1024 * 1024;
25const MAX_MRC_URI_BYTES: usize = 512 * 1024 * 1024;
26
27#[derive(Clone, Copy)]
28enum Endian {
29 Little,
30 Big,
31}
32
33pub(crate) fn looks_like_prefix(bytes: &[u8]) -> bool {
34 if bytes.len() < 216 {
35 return false;
36 }
37 let map = bytes.get(208..212) == Some(b"MAP ");
38 let little = read_i32(bytes, 0, Endian::Little).is_some_and(|value| value > 0);
39 let big = read_i32(bytes, 0, Endian::Big).is_some_and(|value| value > 0);
40 map && (little || big)
41}
42
43pub(crate) fn convert(
44 path: &Path,
45 options: &ConvertOptions,
46 sink: &mut dyn PageConsumer,
47) -> Result<Vec<String>> {
48 let compressed = read_limited_file(
49 path,
50 options.max_input_bytes.min(MAX_MRC_INPUT_BYTES),
51 "MRC input",
52 )?;
53 let bytes = if path
54 .file_name()
55 .and_then(|name| name.to_str())
56 .is_some_and(|name| name.to_ascii_lowercase().ends_with(".mrc.gz"))
57 {
58 let decoder = GzDecoder::new(Cursor::new(compressed));
59 let mut decompressed = Vec::new();
60 decoder
61 .take(MAX_MRC_DECOMPRESSED_BYTES as u64 + 1)
62 .read_to_end(&mut decompressed)?;
63 if decompressed.len() > MAX_MRC_DECOMPRESSED_BYTES {
64 return Err(Error::LimitExceeded(format!(
65 "MRC gzip stream exceeds {MAX_MRC_DECOMPRESSED_BYTES} decompressed bytes"
66 )));
67 }
68 decompressed
69 } else {
70 compressed
71 };
72 let (blocks, warnings) = parse_map(&bytes, options.max_pages)?;
73 render_blocks_to_pages_with_warnings(&blocks, sink, options, &warnings)?;
74 Ok(warnings)
75}
76
77fn parse_map(bytes: &[u8], max_pages: usize) -> Result<(Vec<HtmlBlock>, Vec<String>)> {
78 if bytes.len() < 1024 {
79 return Err(Error::InvalidInput(
80 "MRC input is shorter than the 1024-byte header".into(),
81 ));
82 }
83 let endian = detect_endian(bytes)?;
84 let nx = dimension(bytes, 0, endian)?;
85 let ny = dimension(bytes, 4, endian)?;
86 let nz = dimension(bytes, 8, endian)?;
87 let mode = read_i32(bytes, 12, endian)
88 .ok_or_else(|| Error::InvalidInput("MRC mode is missing".into()))?;
89 if !matches!(mode, 0 | 1 | 2 | 6) {
90 return Err(Error::Unsupported(format!(
91 "MRC mode {mode} is unsupported"
92 )));
93 }
94 if nz > MAX_MRC_PLANES || nz > max_pages {
95 return Err(Error::LimitExceeded(format!(
96 "MRC has {nz} planes but max_pages is {max_pages}"
97 )));
98 }
99 let voxels = u64::try_from(nx)
100 .unwrap_or(u64::MAX)
101 .saturating_mul(u64::try_from(ny).unwrap_or(u64::MAX))
102 .saturating_mul(u64::try_from(nz).unwrap_or(u64::MAX));
103 if voxels > MAX_MRC_VOXELS {
104 return Err(Error::LimitExceeded(format!(
105 "MRC voxel count exceeds {MAX_MRC_VOXELS}"
106 )));
107 }
108 let bytes_per_value = match mode {
109 0 | 6 => 1,
110 1 => 2,
111 2 => 4,
112 _ => unreachable!(),
113 };
114 let total_data = usize::try_from(voxels)
115 .ok()
116 .and_then(|count| count.checked_mul(bytes_per_value))
117 .ok_or_else(|| Error::LimitExceeded("MRC data size overflowed".into()))?;
118 let nsymbt = read_i32(bytes, 92, endian)
119 .ok_or_else(|| Error::InvalidInput("MRC nsymbt is missing".into()))?;
120 if nsymbt < 0 {
121 return Err(Error::InvalidInput("MRC nsymbt is negative".into()));
122 }
123 let data_offset = 1024usize
124 .checked_add(nsymbt as usize)
125 .ok_or_else(|| Error::LimitExceeded("MRC data offset overflowed".into()))?;
126 let data_end = data_offset
127 .checked_add(total_data)
128 .ok_or_else(|| Error::LimitExceeded("MRC data range overflowed".into()))?;
129 if data_end > bytes.len() {
130 return Err(Error::InvalidInput("MRC voxel data is truncated".into()));
131 }
132 let values = decode_values(&bytes[data_offset..data_end], mode, endian)?;
133 let (min, max) = values
134 .iter()
135 .copied()
136 .fold((f64::INFINITY, f64::NEG_INFINITY), |(min, max), value| {
137 (min.min(value), max.max(value))
138 });
139 let mut warnings = vec![
140 "MRC axis mapping, origin, labels, symmetry and extended metadata are omitted".into(),
141 "MRC planes are rendered as grayscale PNG images".into(),
142 ];
143 let mut blocks = Vec::new();
144 let mut uri_bytes = 0usize;
145 let plane_size = nx.saturating_mul(ny);
146 for plane in 0..nz {
147 let start = plane.saturating_mul(plane_size);
148 let pixels = values[start..start + plane_size]
149 .iter()
150 .map(|value| scale(*value, min, max))
151 .collect::<Vec<_>>();
152 let png = encode_png(nx as u32, ny as u32, &pixels)?;
153 if png.len() > MAX_MRC_PNG_BYTES {
154 return Err(Error::LimitExceeded(format!(
155 "MRC plane PNG exceeds {MAX_MRC_PNG_BYTES} bytes"
156 )));
157 }
158 let href = format!("data:image/png;base64,{}", BASE64_STANDARD.encode(&png));
159 uri_bytes = uri_bytes.saturating_add(href.len());
160 if uri_bytes > MAX_MRC_URI_BYTES {
161 return Err(Error::LimitExceeded(format!(
162 "MRC data URI bytes exceed {MAX_MRC_URI_BYTES}"
163 )));
164 }
165 blocks.push(HtmlBlock::Heading {
166 level: 2,
167 text: format!("MRC plane {}", plane + 1),
168 });
169 blocks.push(HtmlBlock::Image {
170 href,
171 pixel_width: nx as u32,
172 pixel_height: ny as u32,
173 alt: format!("MRC grayscale plane {}", plane + 1),
174 });
175 if plane + 1 < nz {
176 blocks.push(HtmlBlock::PageBreak);
177 }
178 }
179 Ok((blocks, std::mem::take(&mut warnings)))
180}
181
182fn detect_endian(bytes: &[u8]) -> Result<Endian> {
183 let little = read_i32(bytes, 0, Endian::Little);
184 let big = read_i32(bytes, 0, Endian::Big);
185 if bytes.get(208..212) == Some(b"MAP ")
186 && little.is_some_and(|value| value > 0 && value <= MAX_MRC_DIMENSION as i32)
187 {
188 Ok(Endian::Little)
189 } else if bytes.get(208..212) == Some(b"MAP ")
190 && big.is_some_and(|value| value > 0 && value <= MAX_MRC_DIMENSION as i32)
191 {
192 Ok(Endian::Big)
193 } else {
194 Err(Error::InvalidInput(
195 "MRC header lacks a valid MAP signature/dimension".into(),
196 ))
197 }
198}
199
200fn dimension(bytes: &[u8], offset: usize, endian: Endian) -> Result<usize> {
201 let value = read_i32(bytes, offset, endian)
202 .ok_or_else(|| Error::InvalidInput("MRC dimension is missing".into()))?;
203 if value <= 0 || value as usize > MAX_MRC_DIMENSION {
204 return Err(Error::LimitExceeded(format!(
205 "MRC dimension exceeds {MAX_MRC_DIMENSION}"
206 )));
207 }
208 Ok(value as usize)
209}
210
211fn decode_values(bytes: &[u8], mode: i32, endian: Endian) -> Result<Vec<f64>> {
212 let width = match mode {
213 0 | 6 => 1,
214 1 => 2,
215 2 => 4,
216 _ => unreachable!(),
217 };
218 if !bytes.len().is_multiple_of(width) {
219 return Err(Error::InvalidInput("MRC data has an invalid stride".into()));
220 }
221 let mut values = Vec::with_capacity(bytes.len() / width);
222 for chunk in bytes.chunks_exact(width) {
223 let value = match mode {
224 0 => (chunk[0] as i8) as f64,
225 6 => f64::from(chunk[0]),
226 1 => read_i16(chunk, 0, endian) as f64,
227 2 => read_f32(chunk, 0, endian) as f64,
228 _ => unreachable!(),
229 };
230 values.push(value);
231 }
232 Ok(values)
233}
234
235fn scale(value: f64, min: f64, max: f64) -> u8 {
236 if !value.is_finite() || !min.is_finite() || !max.is_finite() || max <= min {
237 return 0;
238 }
239 (((value - min) / (max - min)).clamp(0.0, 1.0) * 255.0).round() as u8
240}
241
242fn encode_png(width: u32, height: u32, pixels: &[u8]) -> Result<Vec<u8>> {
243 let mut png = Vec::new();
244 let mut encoder = Encoder::new(&mut png, width, height);
245 encoder.set_color(png::ColorType::Grayscale);
246 encoder.set_depth(png::BitDepth::Eight);
247 let mut writer = encoder
248 .write_header()
249 .map_err(|error| Error::InvalidInput(format!("cannot encode MRC PNG: {error}")))?;
250 writer
251 .write_image_data(pixels)
252 .map_err(|error| Error::InvalidInput(format!("cannot encode MRC PNG: {error}")))?;
253 drop(writer);
254 Ok(png)
255}
256
257fn read_i32(bytes: &[u8], offset: usize, endian: Endian) -> Option<i32> {
258 let data = bytes.get(offset..offset + 4)?;
259 let value = match endian {
260 Endian::Little => i32::from_le_bytes(data.try_into().ok()?),
261 Endian::Big => i32::from_be_bytes(data.try_into().ok()?),
262 };
263 Some(value)
264}
265
266fn read_i16(bytes: &[u8], offset: usize, endian: Endian) -> i16 {
267 let data = [bytes[offset], bytes[offset + 1]];
268 match endian {
269 Endian::Little => i16::from_le_bytes(data),
270 Endian::Big => i16::from_be_bytes(data),
271 }
272}
273
274fn read_f32(bytes: &[u8], offset: usize, endian: Endian) -> f32 {
275 let data = [
276 bytes[offset],
277 bytes[offset + 1],
278 bytes[offset + 2],
279 bytes[offset + 3],
280 ];
281 f32::from_bits(match endian {
282 Endian::Little => u32::from_le_bytes(data),
283 Endian::Big => u32::from_be_bytes(data),
284 })
285}