1use std::io::Write;
7use typf_core::{
8 error::{ExportError, Result},
9 traits::Exporter,
10 types::{BitmapData, BitmapFormat, RenderOutput},
11};
12
13pub mod json;
14pub mod png;
15pub mod svg;
16
17pub use json::JsonExporter;
18pub use png::{encode_bitmap_to_png, PngExporter};
19pub use svg::SvgExporter;
20
21pub struct PnmExporter {
23 format: PnmFormat,
25}
26
27#[derive(Debug, Clone, Copy)]
28pub enum PnmFormat {
29 Pbm,
31 Pgm,
33 Ppm,
35}
36
37impl PnmExporter {
38 pub fn new(format: PnmFormat) -> Self {
40 Self { format }
41 }
42
43 pub fn ppm() -> Self {
45 Self::new(PnmFormat::Ppm)
46 }
47
48 pub fn pgm() -> Self {
50 Self::new(PnmFormat::Pgm)
51 }
52
53 fn export_bitmap(&self, bitmap: &BitmapData) -> Result<Vec<u8>> {
55 let mut output = Vec::new();
56
57 match self.format {
58 PnmFormat::Ppm => {
59 writeln!(&mut output, "P3")?; writeln!(&mut output, "{} {}", bitmap.width, bitmap.height)?;
62 writeln!(&mut output, "255")?; match bitmap.format {
66 BitmapFormat::Rgba8 => {
67 for y in 0..bitmap.height {
69 for x in 0..bitmap.width {
70 let idx = ((y * bitmap.width + x) * 4) as usize;
71 write!(
72 &mut output,
73 "{} {} {} ",
74 bitmap.data[idx], bitmap.data[idx + 1], bitmap.data[idx + 2] )?;
78 }
79 writeln!(&mut output)?; }
81 },
82 BitmapFormat::Rgb8 => {
83 for y in 0..bitmap.height {
85 for x in 0..bitmap.width {
86 let idx = ((y * bitmap.width + x) * 3) as usize;
87 write!(
88 &mut output,
89 "{} {} {} ",
90 bitmap.data[idx],
91 bitmap.data[idx + 1],
92 bitmap.data[idx + 2]
93 )?;
94 }
95 writeln!(&mut output)?;
96 }
97 },
98 BitmapFormat::Gray8 => {
99 for y in 0..bitmap.height {
101 for x in 0..bitmap.width {
102 let idx = (y * bitmap.width + x) as usize;
103 let gray = bitmap.data[idx];
104 write!(&mut output, "{} {} {} ", gray, gray, gray)?;
105 }
106 writeln!(&mut output)?;
107 }
108 },
109 BitmapFormat::Gray1 => {
110 for y in 0..bitmap.height {
112 for x in 0..bitmap.width {
113 let byte_idx = ((y * bitmap.width + x) / 8) as usize;
114 let bit_idx = ((y * bitmap.width + x) % 8) as usize;
115 let bit = (bitmap.data[byte_idx] >> (7 - bit_idx)) & 1;
116 let value = if bit == 1 { 255 } else { 0 };
117 write!(&mut output, "{} {} {} ", value, value, value)?;
118 }
119 writeln!(&mut output)?;
120 }
121 },
122 }
123 },
124 PnmFormat::Pgm => {
125 writeln!(&mut output, "P2")?; writeln!(&mut output, "{} {}", bitmap.width, bitmap.height)?;
128 writeln!(&mut output, "255")?; match bitmap.format {
132 BitmapFormat::Gray8 => {
133 for y in 0..bitmap.height {
135 for x in 0..bitmap.width {
136 let idx = (y * bitmap.width + x) as usize;
137 write!(&mut output, "{} ", bitmap.data[idx])?;
138 }
139 writeln!(&mut output)?;
140 }
141 },
142 BitmapFormat::Rgba8 => {
143 for y in 0..bitmap.height {
145 for x in 0..bitmap.width {
146 let idx = ((y * bitmap.width + x) * 4) as usize;
147 let r = bitmap.data[idx] as u32;
148 let g = bitmap.data[idx + 1] as u32;
149 let b = bitmap.data[idx + 2] as u32;
150 let gray = ((r * 299 + g * 587 + b * 114) / 1000) as u8;
152 write!(&mut output, "{} ", gray)?;
153 }
154 writeln!(&mut output)?;
155 }
156 },
157 BitmapFormat::Rgb8 => {
158 for y in 0..bitmap.height {
160 for x in 0..bitmap.width {
161 let idx = ((y * bitmap.width + x) * 3) as usize;
162 let r = bitmap.data[idx] as u32;
163 let g = bitmap.data[idx + 1] as u32;
164 let b = bitmap.data[idx + 2] as u32;
165 let gray = ((r * 299 + g * 587 + b * 114) / 1000) as u8;
166 write!(&mut output, "{} ", gray)?;
167 }
168 writeln!(&mut output)?;
169 }
170 },
171 BitmapFormat::Gray1 => {
172 for y in 0..bitmap.height {
174 for x in 0..bitmap.width {
175 let byte_idx = ((y * bitmap.width + x) / 8) as usize;
176 let bit_idx = ((y * bitmap.width + x) % 8) as usize;
177 let bit = (bitmap.data[byte_idx] >> (7 - bit_idx)) & 1;
178 let value = if bit == 1 { 255 } else { 0 };
179 write!(&mut output, "{} ", value)?;
180 }
181 writeln!(&mut output)?;
182 }
183 },
184 }
185 },
186 PnmFormat::Pbm => {
187 writeln!(&mut output, "P1")?; writeln!(&mut output, "{} {}", bitmap.width, bitmap.height)?;
190
191 match bitmap.format {
193 BitmapFormat::Gray1 => {
194 for y in 0..bitmap.height {
196 for x in 0..bitmap.width {
197 let byte_idx = ((y * bitmap.width + x) / 8) as usize;
198 let bit_idx = ((y * bitmap.width + x) % 8) as usize;
199 let bit = (bitmap.data[byte_idx] >> (7 - bit_idx)) & 1;
200 write!(&mut output, "{} ", bit)?;
201 }
202 writeln!(&mut output)?;
203 }
204 },
205 _ => {
206 for y in 0..bitmap.height {
208 for x in 0..bitmap.width {
209 let gray = match bitmap.format {
210 BitmapFormat::Gray8 => {
211 bitmap.data[(y * bitmap.width + x) as usize]
212 },
213 BitmapFormat::Rgba8 => {
214 let idx = ((y * bitmap.width + x) * 4) as usize;
215 let r = bitmap.data[idx] as u32;
216 let g = bitmap.data[idx + 1] as u32;
217 let b = bitmap.data[idx + 2] as u32;
218 ((r * 299 + g * 587 + b * 114) / 1000) as u8
219 },
220 BitmapFormat::Rgb8 => {
221 let idx = ((y * bitmap.width + x) * 3) as usize;
222 let r = bitmap.data[idx] as u32;
223 let g = bitmap.data[idx + 1] as u32;
224 let b = bitmap.data[idx + 2] as u32;
225 ((r * 299 + g * 587 + b * 114) / 1000) as u8
226 },
227 _ => 0,
228 };
229 let bit = if gray > 127 { 1 } else { 0 };
231 write!(&mut output, "{} ", bit)?;
232 }
233 writeln!(&mut output)?;
234 }
235 },
236 }
237 },
238 }
239
240 Ok(output)
241 }
242}
243
244impl Exporter for PnmExporter {
245 fn name(&self) -> &'static str {
246 match self.format {
247 PnmFormat::Pbm => "pbm",
248 PnmFormat::Pgm => "pgm",
249 PnmFormat::Ppm => "ppm",
250 }
251 }
252
253 fn export(&self, output: &RenderOutput) -> Result<Vec<u8>> {
254 match output {
255 RenderOutput::Bitmap(bitmap) => self.export_bitmap(bitmap),
256 _ => Err(ExportError::FormatNotSupported(
257 "PNM exporter only supports bitmap output".into(),
258 )
259 .into()),
260 }
261 }
262
263 fn extension(&self) -> &'static str {
264 match self.format {
265 PnmFormat::Pbm => "pbm",
266 PnmFormat::Pgm => "pgm",
267 PnmFormat::Ppm => "ppm",
268 }
269 }
270
271 fn mime_type(&self) -> &'static str {
272 match self.format {
273 PnmFormat::Pbm => "image/x-portable-bitmap",
274 PnmFormat::Pgm => "image/x-portable-graymap",
275 PnmFormat::Ppm => "image/x-portable-pixmap",
276 }
277 }
278}
279
280impl Default for PnmExporter {
281 fn default() -> Self {
282 Self::ppm() }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_ppm_export() {
292 let exporter = PnmExporter::ppm();
293
294 let bitmap = BitmapData {
296 width: 2,
297 height: 2,
298 format: BitmapFormat::Rgba8,
299 data: vec![
300 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255, ],
305 };
306
307 let output = RenderOutput::Bitmap(bitmap);
308 let exported = exporter.export(&output).unwrap();
309
310 let text = String::from_utf8(exported).unwrap();
311 assert!(text.starts_with("P3"));
312 assert!(text.contains("2 2")); assert!(text.contains("255")); }
315
316 #[test]
317 fn test_pgm_export() {
318 let exporter = PnmExporter::pgm();
319
320 let bitmap = BitmapData {
321 width: 2,
322 height: 1,
323 format: BitmapFormat::Gray8,
324 data: vec![128, 255],
325 };
326
327 let output = RenderOutput::Bitmap(bitmap);
328 let exported = exporter.export(&output).unwrap();
329
330 let text = String::from_utf8(exported).unwrap();
331 assert!(text.starts_with("P2"));
332 assert!(text.contains("2 1"));
333 assert!(text.contains("128"));
334 assert!(text.contains("255"));
335 }
336
337 #[test]
338 fn test_extension_and_mime() {
339 let ppm = PnmExporter::ppm();
340 assert_eq!(ppm.extension(), "ppm");
341 assert_eq!(ppm.mime_type(), "image/x-portable-pixmap");
342
343 let pgm = PnmExporter::pgm();
344 assert_eq!(pgm.extension(), "pgm");
345 assert_eq!(pgm.mime_type(), "image/x-portable-graymap");
346 }
347}