Skip to content
Snippets Groups Projects
favicon.rs 2.5 KiB
Newer Older
Stephen D's avatar
Stephen D committed
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 {
Stephen D's avatar
Stephen D committed
        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">
Stephen D's avatar
Stephen D committed
"#
    }
}