//! Packaged icons are rasters of Tim Falken's original PNG. use std::fs; use std::io::Cursor; use std::path::PathBuf; use paperdoll_renderer::icon::{decode_rgba, hicolor_rgba, HICOLOR_SIZES}; fn root() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) } fn encode_png(width: u32, height: u32, pixels: &[u8]) -> Vec { let mut bytes = Vec::new(); let mut encoder = png::Encoder::new(Cursor::new(&mut bytes), width, height); encoder.set_color(png::ColorType::Rgba); encoder.set_depth(png::BitDepth::Eight); let mut writer = encoder.write_header().expect("png header"); writer.write_image_data(pixels).expect("png pixels"); drop(writer); bytes } fn write_packages() { let manifest = root(); for size in HICOLOR_SIZES { let pixels = hicolor_rgba(size); let path = manifest.join(format!( "packaging/linux/hicolor/{size}x{size}/apps/paperdoll-renderer.png" )); fs::create_dir_all(path.parent().expect("icon dir")).expect("icon dir"); fs::write(&path, encode_png(size, size, &pixels)).expect("write png"); } let mut images = Vec::new(); for size in [256, 64, 48, 32, 24, 16] { images.push((size, encode_png(size, size, &hicolor_rgba(size)))); } let ico = manifest.join("packaging/windows/paperdoll-renderer.ico"); fs::create_dir_all(ico.parent().expect("ico dir")).expect("ico dir"); fs::write(&ico, ico_bytes(&images)).expect("write ico"); } fn ico_bytes(images: &[(u32, Vec)]) -> Vec { let count = u16::try_from(images.len()).expect("ico count"); let mut bytes = Vec::new(); bytes.extend_from_slice(&0u16.to_le_bytes()); bytes.extend_from_slice(&1u16.to_le_bytes()); bytes.extend_from_slice(&count.to_le_bytes()); let mut offset = 6 + images.len() * 16; for (size, png) in images { let dim = if *size >= 256 { 0 } else { *size as u8 }; bytes.push(dim); bytes.push(dim); bytes.push(0); bytes.push(0); bytes.extend_from_slice(&1u16.to_le_bytes()); bytes.extend_from_slice(&32u16.to_le_bytes()); bytes.extend_from_slice(&(u32::try_from(png.len()).expect("png len")).to_le_bytes()); bytes.extend_from_slice(&(u32::try_from(offset).expect("ico offset")).to_le_bytes()); offset += png.len(); } for (_, png) in images { bytes.extend_from_slice(png); } bytes } fn read(path: &str) -> String { fs::read_to_string(root().join(path)).unwrap_or_else(|err| panic!("{path}: {err}")) } #[test] fn packaged_icons_match_tims_png() { if std::env::var("PAPERDOLL_WRITE_ICONS").ok().as_deref() == Some("1") { write_packages(); } for size in HICOLOR_SIZES { let path = format!("packaging/linux/hicolor/{size}x{size}/apps/paperdoll-renderer.png"); let bytes = fs::read(root().join(&path)).unwrap_or_else(|err| panic!("{path}: {err}")); let (width, height, pixels) = decode_rgba(&bytes).unwrap(); assert_eq!((width, height), (size, size), "{path}"); assert_eq!(pixels, hicolor_rgba(size), "{path}"); } let ico = fs::read(root().join("packaging/windows/paperdoll-renderer.ico")).expect("ico"); let count = u16::from_le_bytes([ico[4], ico[5]]) as usize; assert_eq!(u16::from_le_bytes([ico[0], ico[1]]), 0); assert_eq!(u16::from_le_bytes([ico[2], ico[3]]), 1); assert_eq!(count, 6); for index in 0..count { let entry = 6 + index * 16; let width = match ico[entry] { 0 => 256, value => u32::from(value), }; let bytes_len = u32::from_le_bytes(ico[entry + 8..entry + 12].try_into().unwrap()) as usize; let offset = u32::from_le_bytes(ico[entry + 12..entry + 16].try_into().unwrap()) as usize; let png = &ico[offset..offset + bytes_len]; assert_eq!(&png[..8], b"\x89PNG\r\n\x1a\n"); let (got_w, got_h, pixels) = decode_rgba(png).unwrap(); assert_eq!((got_w, got_h), (width, width)); assert_eq!(pixels, hicolor_rgba(width)); } } #[test] fn desktop_and_appdata_name_the_same_icon() { let desktop = read("packaging/linux/paperdoll-renderer.desktop"); let metainfo = read("packaging/linux/paperdoll-renderer.metainfo.xml"); assert!(desktop .lines() .any(|line| line == "Icon=paperdoll-renderer")); assert!(metainfo.contains("paperdoll-renderer")); assert!(metainfo.contains("paperdoll-renderer.desktop")); for script in [ "scripts/package/deb.sh", "scripts/package/appimage.sh", "packaging/arch/PKGBUILD", ] { let text = read(script); assert!( text.contains("hicolor") && text.contains("paperdoll-renderer.metainfo.xml"), "{script} does not install the hicolor icon and the metainfo" ); assert!( !text.contains("icon-64.png"), "{script} still installs the old placeholder icon" ); } let windows = read("scripts/package/windows.sh"); assert!(windows.contains("pe_has_icon.py")); assert!(!windows.contains(".ico")); }