use anyhow::bail; use image::{ codecs::ico::{IcoEncoder, IcoFrame}, io::Reader as ImageReader, ImageOutputFormat, }; use std::{ io::{Cursor, ErrorKind}, path::Path, }; use crate::resp::Response; pub struct Favicon { pub responses: Vec<(&'static str, Response)>, } impl Favicon { pub fn load_if_exists<P: AsRef<Path>>(path: P) -> anyhow::Result<Option<Self>> { let original = match ImageReader::open(path) { Ok(x) => x, Err(e) if e.kind() == ErrorKind::NotFound => { return Ok(None); } Err(e) => { return Err(e.into()); } } .decode()?; if original.width() != original.height() { bail!("Favicon must have the same width and height"); } // 180x180 png let mut png_180 = vec![]; original .thumbnail(180, 180) .write_to(&mut Cursor::new(&mut png_180), ImageOutputFormat::Png)?; // 32x32 png let mut png_32 = vec![]; original .thumbnail(32, 32) .write_to(&mut Cursor::new(&mut png_32), ImageOutputFormat::Png)?; // 16x16 png let mut png_16 = vec![]; original .thumbnail(16, 16) .write_to(&mut Cursor::new(&mut png_16), ImageOutputFormat::Png)?; // 48x48, 32x32, 16x16 ico let mut png_48 = vec![]; original .thumbnail(48, 48) .write_to(&mut Cursor::new(&mut png_48), ImageOutputFormat::Png)?; let ico_48 = IcoFrame::with_encoded(png_48, 48, 48, original.color())?; let ico_32 = IcoFrame::with_encoded(&png_32, 32, 32, original.color())?; let ico_16 = IcoFrame::with_encoded(&png_16, 16, 16, original.color())?; let mut ico = vec![]; IcoEncoder::new(&mut ico).encode_images(&[ico_48, ico_32, ico_16])?; let responses = vec![ ("apple-touch-icon.png", Response::png(png_180)), ("favicon-32x32.png", Response::png(png_32)), ("favicon-16x16.png", Response::png(png_16)), ("favicon.ico", Response::ico(ico)), ]; Ok(Some(Self { responses })) } pub fn html(&self) -> &'static str { r#"<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="shortcut icon" href="/favicon.ico"> "# } }