1use std::fmt;
13use std::io::{BufRead, Bytes};
14
15use image::error::{DecodingError, ImageFormatHint, ParameterError, ParameterErrorKind};
16use image::{ColorType, ExtendedColorType, ImageDecoder, ImageError, ImageResult};
17
18#[derive(Clone, Copy, Debug)]
23struct TextLocation {
24 byte: u64,
25 line: u64,
26 column: u64,
27}
28
29struct TextReader<R> {
31 inner: R,
32
33 current: Option<u8>,
34
35 location: TextLocation,
36}
37
38impl<R> TextReader<R>
39where
40 R: Iterator<Item = u8>,
41{
42 fn new(mut r: R) -> TextReader<R> {
44 let current = r.next();
45 TextReader {
46 inner: r,
47 current,
48 location: TextLocation {
49 byte: 0,
50 line: 1,
51 column: 0,
52 },
53 }
54 }
55
56 fn next(&mut self) -> Option<u8> {
58 self.current?;
59
60 let mut current = self.inner.next();
61 std::mem::swap(&mut self.current, &mut current);
62
63 self.location.byte += 1;
64 self.location.column += 1;
65 if let Some(b'\n') = current {
66 self.location.line += 1;
67 self.location.column = 0;
68 }
69 current
70 }
71 fn peek(&self) -> Option<u8> {
73 self.current
74 }
75 fn loc(&self) -> TextLocation {
77 self.location
78 }
79}
80
81struct XbmHeaderData {
83 width: u32,
84 height: u32,
85 hotspot: Option<(i32, i32)>,
86}
87
88struct XbmStreamDecoder<R> {
93 r: TextReader<R>,
94 current_position: u64,
95 header: XbmHeaderData,
97}
98
99struct IoAdapter<R> {
103 reader: Bytes<R>,
104 error: Option<std::io::Error>,
105}
106
107impl<R> Iterator for IoAdapter<R>
108where
109 R: BufRead,
110{
111 type Item = u8;
112 #[inline(always)]
113 fn next(&mut self) -> Option<Self::Item> {
114 if self.error.is_some() {
115 return None;
116 }
117 match self.reader.next() {
118 None => None,
119 Some(Ok(v)) => Some(v),
120 Some(Err(e)) => {
121 self.error = Some(e);
122 None
123 }
124 }
125 }
126}
127
128pub struct XbmDecoder<R> {
130 base: XbmStreamDecoder<IoAdapter<R>>,
131}
132
133#[derive(Debug, Clone, Copy)]
135enum XbmPart {
136 Width,
137 Height,
138 HotspotX,
139 HotspotY,
140 Array,
141 Data,
142 ArrayEnd,
143 Trailing,
144}
145
146#[derive(Debug)]
148enum XbmDecodeError {
149 Parse(XbmPart, TextLocation),
150 DecodeInteger(XbmPart),
151 ZeroWidth,
152 ZeroHeight,
153}
154
155impl fmt::Display for TextLocation {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 f.write_fmt(format_args!(
158 "byte={},line={}:col={}",
159 self.byte, self.line, self.column
160 ))
161 }
162}
163
164impl fmt::Display for XbmPart {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 match self {
167 XbmPart::Width => f.write_str("#define for image width"),
168 XbmPart::Height => f.write_str("#define for image height"),
169 XbmPart::HotspotX => f.write_str("#define for hotspot x coordinate"),
170 XbmPart::HotspotY => f.write_str("#define for hotspot y coordinate"),
171 XbmPart::Array => f.write_str("array definition"),
172 XbmPart::Data => f.write_str("array content"),
173 XbmPart::ArrayEnd => f.write_str("array end"),
174 XbmPart::Trailing => f.write_str("end of file"),
175 }
176 }
177}
178
179impl fmt::Display for XbmDecodeError {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 match self {
182 XbmDecodeError::Parse(part, loc) => f.write_fmt(format_args!(
183 "Failed to parse {}, unexpected character or eof at {}",
184 part, loc
185 )),
186 XbmDecodeError::DecodeInteger(part) => {
187 f.write_fmt(format_args!("Failed to parse integer for {}", part))
188 }
189 XbmDecodeError::ZeroWidth => f.write_str("Invalid image width: should not be zero"),
190 XbmDecodeError::ZeroHeight => f.write_str("Invalid image height: should not be zero"),
191 }
192 }
193}
194
195impl std::error::Error for XbmDecodeError {}
196
197impl From<XbmDecodeError> for ImageError {
198 fn from(e: XbmDecodeError) -> ImageError {
199 ImageError::Decoding(DecodingError::new(ImageFormatHint::Name("XBM".into()), e))
200 }
201}
202
203trait XbmDecoderIoInjectionExt {
206 type Value;
207 fn apply_after(self, err: &mut Option<std::io::Error>) -> Result<Self::Value, ImageError>;
208}
209
210impl<X> XbmDecoderIoInjectionExt for Result<X, XbmDecodeError> {
211 type Value = X;
212 fn apply_after(self, err: &mut Option<std::io::Error>) -> Result<Self::Value, ImageError> {
213 if let Some(err) = err.take() {
214 return Err(ImageError::IoError(err));
215 }
216 match self {
217 Self::Ok(x) => Ok(x),
218 Self::Err(e) => Err(ImageError::Decoding(DecodingError::new(
219 ImageFormatHint::Name("XBM".into()),
220 e,
221 ))),
222 }
223 }
224}
225
226const MAX_IDENTIFIER_LENGTH: usize = 256;
230
231fn read_fixed_string<R: Iterator<Item = u8>>(
233 r: &mut TextReader<R>,
234 s: &[u8],
235 part: XbmPart,
236) -> Result<(), XbmDecodeError> {
237 for c in s {
238 if let Some(b) = r.next() {
239 if b != *c {
240 return Err(XbmDecodeError::Parse(part, r.loc()));
241 }
242 } else {
243 return Err(XbmDecodeError::Parse(part, r.loc()));
244 };
245 }
246 Ok(())
247}
248fn read_byte<R: Iterator<Item = u8>>(
250 r: &mut TextReader<R>,
251 part: XbmPart,
252) -> Result<u8, XbmDecodeError> {
253 match r.next() {
254 None => Err(XbmDecodeError::Parse(part, r.loc())),
255 Some(b) => Ok(b),
256 }
257}
258
259fn read_whitespace_gap<R: Iterator<Item = u8>>(
262 r: &mut TextReader<R>,
263 part: XbmPart,
264) -> Result<(), XbmDecodeError> {
265 let b = read_byte(r, part)?;
266 if !(b == b' ' || b == b'\t') {
267 return Err(XbmDecodeError::Parse(part, r.loc()));
268 }
269 while let Some(b) = r.peek() {
270 if b == b' ' || b == b'\t' {
271 r.next();
272 continue;
273 } else {
274 return Ok(());
275 }
276 }
277 Ok(())
278}
279fn read_optional_whitespace<R: Iterator<Item = u8>>(
281 r: &mut TextReader<R>,
282) -> Result<(), XbmDecodeError> {
283 while let Some(b) = r.peek() {
284 if b == b' ' || b == b'\t' || b == b'\n' {
285 r.next();
286 continue;
287 } else {
288 break;
289 }
290 }
291 Ok(())
292}
293fn read_to_newline<R: Iterator<Item = u8>>(
295 r: &mut TextReader<R>,
296 part: XbmPart,
297) -> Result<(), XbmDecodeError> {
298 while let Some(b) = r.peek() {
299 if b == b' ' || b == b'\t' {
300 r.next();
301 continue;
302 } else {
303 break;
304 }
305 }
306 if read_byte(r, part)? != b'\n' {
307 Err(XbmDecodeError::Parse(part, r.loc()))
308 } else {
309 Ok(())
310 }
311}
312fn read_until_whitespace<'a, R: Iterator<Item = u8>>(
315 r: &mut TextReader<R>,
316 buf: &'a mut [u8],
317 part: XbmPart,
318) -> Result<&'a [u8], XbmDecodeError> {
319 let mut len = 0;
320 while let Some(b) = r.peek() {
321 if b == b' ' || b == b'\t' || b == b'\n' {
322 return Ok(&buf[..len]);
323 } else {
324 if len >= buf.len() {
325 return Err(XbmDecodeError::Parse(part, r.loc()));
327 }
328 buf[len] = b;
329 len += 1;
330 r.next();
331 }
332 }
333 Ok(&buf[..len])
334}
335
336fn read_hex_digit<R: Iterator<Item = u8>>(
338 r: &mut TextReader<R>,
339 part: XbmPart,
340) -> Result<u8, XbmDecodeError> {
341 let b = read_byte(r, part)?;
342 match b {
343 b'0'..=b'9' => Ok(b - b'0'),
344 b'A'..=b'F' => Ok(b - b'A' + 10),
345 b'a'..=b'f' => Ok(b - b'a' + 10),
346 _ => Err(XbmDecodeError::Parse(part, r.loc())),
347 }
348}
349
350fn read_hex_byte<R: Iterator<Item = u8>>(
352 r: &mut TextReader<R>,
353 part: XbmPart,
354) -> Result<u8, XbmDecodeError> {
355 if read_byte(r, part)? != b'0' {
356 return Err(XbmDecodeError::Parse(part, r.loc()));
357 }
358 let x = read_byte(r, part)?;
359 if !(x == b'x' || x == b'X') {
360 return Err(XbmDecodeError::Parse(part, r.loc()));
361 }
362 let mut v = read_hex_digit(r, part)? << 4;
363 v += read_hex_digit(r, part)?;
364 Ok(v)
365}
366
367fn parse_i32(data: &[u8]) -> Option<i32> {
370 if data.starts_with(b"-") {
371 (-(parse_u32(&data[1..])? as i64)).try_into().ok()
372 } else {
373 parse_u32(data)?.try_into().ok()
374 }
375}
376
377fn parse_u32(data: &[u8]) -> Option<u32> {
380 let Some(c1) = data.first() else {
381 return None;
383 };
384 if *c1 == b'0' && data.len() > 1 {
385 return None;
387 }
388 let mut x: u32 = 0;
389 for c in data {
390 if b'0' <= *c && *c <= b'9' {
391 x = x.checked_mul(10)?.checked_add((*c - b'0') as u32)?;
392 } else {
393 return None;
394 }
395 }
396 Some(x)
397}
398
399fn read_xbm_header<'a, R: Iterator<Item = u8>>(
401 r: &mut TextReader<R>,
402 name_width_buf: &'a mut [u8],
403) -> Result<(&'a [u8], XbmHeaderData), XbmDecodeError> {
404 let mut int_buf = [0u8; 11]; read_fixed_string(r, b"#define", XbmPart::Width)?;
416 read_whitespace_gap(r, XbmPart::Width)?;
417 let name_width = read_until_whitespace(r, name_width_buf, XbmPart::Width)?;
418 if !name_width.ends_with(b"_width") {
419 return Err(XbmDecodeError::Parse(XbmPart::Width, r.loc()));
420 }
421 let name = &name_width[..name_width.len() - b"_width".len()];
422 read_whitespace_gap(r, XbmPart::Width)?;
423 let int = read_until_whitespace(r, &mut int_buf, XbmPart::Width)?;
424 read_to_newline(r, XbmPart::Width)?;
425
426 let width = parse_u32(int).ok_or(XbmDecodeError::DecodeInteger(XbmPart::Width))?;
427 if width == 0 {
428 return Err(XbmDecodeError::ZeroWidth);
429 }
430
431 read_fixed_string(r, b"#define", XbmPart::Height)?;
433 read_whitespace_gap(r, XbmPart::Height)?;
434 read_fixed_string(r, name, XbmPart::Height)?;
435 read_fixed_string(r, b"_height", XbmPart::Height)?;
436 read_whitespace_gap(r, XbmPart::Height)?;
437 let int = read_until_whitespace(r, &mut int_buf, XbmPart::Height)?;
438 read_to_newline(r, XbmPart::Height)?;
439
440 let height = parse_u32(int).ok_or(XbmDecodeError::DecodeInteger(XbmPart::Height))?;
441 if height == 0 {
442 return Err(XbmDecodeError::ZeroHeight);
443 }
444
445 let hotspot = match r.peek() {
446 Some(b'#') => {
447 read_fixed_string(r, b"#define", XbmPart::HotspotX)?;
449 read_whitespace_gap(r, XbmPart::HotspotX)?;
450 read_fixed_string(r, name, XbmPart::HotspotX)?;
451 read_fixed_string(r, b"_x_hot", XbmPart::HotspotX)?;
452 read_whitespace_gap(r, XbmPart::HotspotX)?;
453 let int = read_until_whitespace(r, &mut int_buf, XbmPart::HotspotX)?;
454 read_to_newline(r, XbmPart::HotspotX)?;
455
456 let hotspot_x =
457 parse_i32(int).ok_or(XbmDecodeError::DecodeInteger(XbmPart::HotspotX))?;
458
459 read_fixed_string(r, b"#define", XbmPart::HotspotY)?;
460 read_whitespace_gap(r, XbmPart::HotspotY)?;
461 read_fixed_string(r, name, XbmPart::HotspotY)?;
462 read_fixed_string(r, b"_y_hot", XbmPart::HotspotY)?;
463 read_whitespace_gap(r, XbmPart::HotspotY)?;
464 let int = read_until_whitespace(r, &mut int_buf, XbmPart::HotspotY)?;
465 read_to_newline(r, XbmPart::HotspotY)?;
466
467 let hotspot_y =
468 parse_i32(int).ok_or(XbmDecodeError::DecodeInteger(XbmPart::HotspotY))?;
469
470 Some((hotspot_x, hotspot_y))
471 }
472 Some(b's') => None,
473 _ => {
474 r.next();
475 return Err(XbmDecodeError::Parse(XbmPart::Array, r.loc()));
476 }
477 };
478
479 read_fixed_string(r, b"static", XbmPart::Array)?;
480 read_whitespace_gap(r, XbmPart::Array)?;
481 match r.peek() {
482 Some(b'c') => {
483 read_fixed_string(r, b"char", XbmPart::Array)?;
484 }
485 Some(b'u') => {
486 read_fixed_string(r, b"unsigned", XbmPart::Array)?;
487 read_whitespace_gap(r, XbmPart::Array)?;
488 read_fixed_string(r, b"char", XbmPart::Array)?;
489 }
490 _ => {
491 r.next();
492 return Err(XbmDecodeError::Parse(XbmPart::Array, r.loc()));
493 }
494 }
495 read_whitespace_gap(r, XbmPart::Array)?;
496 read_fixed_string(r, name, XbmPart::Array)?;
497 read_fixed_string(r, b"_bits[]", XbmPart::Array)?;
498 read_whitespace_gap(r, XbmPart::Array)?;
499 read_fixed_string(r, b"=", XbmPart::Array)?;
500 read_whitespace_gap(r, XbmPart::Array)?;
501 read_fixed_string(r, b"{", XbmPart::Array)?;
502
503 Ok((
504 name,
505 XbmHeaderData {
506 width,
507 height,
508 hotspot,
509 },
510 ))
511}
512
513impl<R> XbmStreamDecoder<R>
514where
515 R: Iterator<Item = u8>,
516{
517 pub fn new(reader: R) -> Result<XbmStreamDecoder<R>, (R, XbmDecodeError)> {
519 let mut r = TextReader::new(reader);
520
521 let mut name_width_buf = [0u8; MAX_IDENTIFIER_LENGTH];
522 match read_xbm_header(&mut r, &mut name_width_buf) {
523 Err(e) => Err((r.inner, e)),
524 Ok((_name, header)) => Ok(XbmStreamDecoder {
525 r,
526 current_position: 0,
527 header,
528 }),
529 }
530 }
531
532 pub fn next_byte(&mut self) -> Result<Option<u8>, XbmDecodeError> {
537 let data_size = (self.header.width.div_ceil(8) as u64) * (self.header.height as u64);
538 if self.current_position < data_size {
539 let first = self.current_position == 0;
540 self.current_position += 1;
541
542 if !first {
543 read_optional_whitespace(&mut self.r)?;
544 read_fixed_string(&mut self.r, b",", XbmPart::Data)?;
545 }
546 read_optional_whitespace(&mut self.r)?;
547 Ok(Some(read_hex_byte(&mut self.r, XbmPart::Data)?))
548 } else {
549 read_optional_whitespace(&mut self.r)?;
551 match self.r.peek() {
552 Some(b',') => {
553 read_fixed_string(&mut self.r, b",", XbmPart::Data)?;
554 read_optional_whitespace(&mut self.r)?;
555 }
556 Some(b'}') => (),
557 _ => {
558 self.r.next();
559 return Err(XbmDecodeError::Parse(XbmPart::ArrayEnd, self.r.loc()));
560 }
561 }
562 read_fixed_string(&mut self.r, b"}", XbmPart::ArrayEnd)?;
563 read_optional_whitespace(&mut self.r)?;
564 read_fixed_string(&mut self.r, b";", XbmPart::ArrayEnd)?;
565 read_optional_whitespace(&mut self.r)?;
566
567 if self.r.next().is_some() {
568 return Err(XbmDecodeError::Parse(XbmPart::Trailing, self.r.loc()));
570 };
571
572 Ok(None)
573 }
574 }
575}
576
577impl<R> XbmDecoder<R>
578where
579 R: BufRead,
580{
581 pub fn new(reader: R) -> Result<XbmDecoder<R>, ImageError> {
583 match XbmStreamDecoder::new(IoAdapter {
584 reader: reader.bytes(),
585 error: None,
586 }) {
587 Err((mut r, e)) => Err(e).apply_after(&mut r.error),
588 Ok(x) => Ok(XbmDecoder { base: x }),
589 }
590 }
591
592 pub fn hotspot(&self) -> Option<(i32, i32)> {
594 self.base.header.hotspot
595 }
596}
597
598impl<R: BufRead> ImageDecoder for XbmDecoder<R> {
599 fn dimensions(&self) -> (u32, u32) {
600 (self.base.header.width, self.base.header.height)
601 }
602 fn color_type(&self) -> ColorType {
603 ColorType::L8
604 }
605 fn original_color_type(&self) -> ExtendedColorType {
606 ExtendedColorType::L1
607 }
608 fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()>
609 where
610 Self: Sized,
611 {
612 for row in buf.chunks_exact_mut(self.base.header.width as usize) {
613 for chunk in row.chunks_mut(8) {
615 let nxt = self
616 .base
617 .next_byte()
618 .apply_after(&mut self.base.r.inner.error)?;
619 let val = nxt.ok_or_else(|| {
620 ImageError::Parameter(ParameterError::from_kind(
621 ParameterErrorKind::DimensionMismatch,
622 ))
623 })?;
624 for (i, p) in chunk.iter_mut().enumerate() {
625 *p = if val & (1 << i) == 0 { 0xff } else { 0 };
627 }
628 }
629 }
630
631 let val = self
632 .base
633 .next_byte()
634 .apply_after(&mut self.base.r.inner.error)?;
635 if val.is_some() {
636 return Err(ImageError::Parameter(ParameterError::from_kind(
637 ParameterErrorKind::DimensionMismatch,
638 )));
639 }
640
641 Ok(())
642 }
643 fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
644 (*self).read_image(buf)
645 }
646}
647
648#[cfg(test)]
649mod tests {
650 use super::*;
651 use std::fs::File;
652 use std::io::BufReader;
653
654 #[test]
655 fn image_without_hotspot() {
656 let decoder = XbmDecoder::new(BufReader::new(
657 File::open("tests/images/xbm/1x1.xbm").unwrap(),
658 ))
659 .expect("Unable to read XBM file");
660
661 assert_eq!((1, 1), decoder.dimensions());
662 assert_eq!(None, decoder.hotspot());
663 }
664
665 #[test]
666 fn image_with_hotspot() {
667 let decoder = XbmDecoder::new(BufReader::new(
668 File::open("tests/images/xbm/hotspot.xbm").unwrap(),
669 ))
670 .expect("Unable to read XBM file");
671
672 assert_eq!((5, 5), decoder.dimensions());
673 assert_eq!(Some((-1, 2)), decoder.hotspot());
674 }
675}