BlueGit

golden-age paperdoll-renderer

Files

paperdoll-renderer / tests / app_icon.rs

app_icon.rs

app_icon.rs 5.8 KiB Download Raw
1//! Packaged icons are rasters of Tim Falken's original PNG.
2
3use std::fs;
4use std::io::Cursor;
5use std::path::PathBuf;
6
7use paperdoll_renderer::icon::{decode_rgba, hicolor_rgba, HICOLOR_SIZES};
8
9fn root() -> PathBuf {
10 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
11}
12
13fn encode_png(width: u32, height: u32, pixels: &[u8]) -> Vec<u8> {
14 let mut bytes = Vec::new();
15 let mut encoder = png::Encoder::new(Cursor::new(&mut bytes), width, height);
16 encoder.set_color(png::ColorType::Rgba);
17 encoder.set_depth(png::BitDepth::Eight);
18 let mut writer = encoder.write_header().expect("png header");
19 writer.write_image_data(pixels).expect("png pixels");
20 drop(writer);
21 bytes
22}
23
24fn write_packages() {
25 let manifest = root();
26 for size in HICOLOR_SIZES {
27 let pixels = hicolor_rgba(size);
28 let path = manifest.join(format!(
29 "packaging/linux/hicolor/{size}x{size}/apps/paperdoll-renderer.png"
30 ));
31 fs::create_dir_all(path.parent().expect("icon dir")).expect("icon dir");
32 fs::write(&path, encode_png(size, size, &pixels)).expect("write png");
33 }
34 let mut images = Vec::new();
35 for size in [256, 64, 48, 32, 24, 16] {
36 images.push((size, encode_png(size, size, &hicolor_rgba(size))));
37 }
38 let ico = manifest.join("packaging/windows/paperdoll-renderer.ico");
39 fs::create_dir_all(ico.parent().expect("ico dir")).expect("ico dir");
40 fs::write(&ico, ico_bytes(&images)).expect("write ico");
41}
42
43fn ico_bytes(images: &[(u32, Vec<u8>)]) -> Vec<u8> {
44 let count = u16::try_from(images.len()).expect("ico count");
45 let mut bytes = Vec::new();
46 bytes.extend_from_slice(&0u16.to_le_bytes());
47 bytes.extend_from_slice(&1u16.to_le_bytes());
48 bytes.extend_from_slice(&count.to_le_bytes());
49 let mut offset = 6 + images.len() * 16;
50 for (size, png) in images {
51 let dim = if *size >= 256 { 0 } else { *size as u8 };
52 bytes.push(dim);
53 bytes.push(dim);
54 bytes.push(0);
55 bytes.push(0);
56 bytes.extend_from_slice(&1u16.to_le_bytes());
57 bytes.extend_from_slice(&32u16.to_le_bytes());
58 bytes.extend_from_slice(&(u32::try_from(png.len()).expect("png len")).to_le_bytes());
59 bytes.extend_from_slice(&(u32::try_from(offset).expect("ico offset")).to_le_bytes());
60 offset += png.len();
61 }
62 for (_, png) in images {
63 bytes.extend_from_slice(png);
64 }
65 bytes
66}
67
68fn read(path: &str) -> String {
69 fs::read_to_string(root().join(path)).unwrap_or_else(|err| panic!("{path}: {err}"))
70}
71
72#[test]
73fn packaged_icons_match_tims_png() {
74 if std::env::var("PAPERDOLL_WRITE_ICONS").ok().as_deref() == Some("1") {
75 write_packages();
76 }
77 for size in HICOLOR_SIZES {
78 let path = format!("packaging/linux/hicolor/{size}x{size}/apps/paperdoll-renderer.png");
79 let bytes = fs::read(root().join(&path)).unwrap_or_else(|err| panic!("{path}: {err}"));
80 let (width, height, pixels) = decode_rgba(&bytes).unwrap();
81 assert_eq!((width, height), (size, size), "{path}");
82 assert_eq!(pixels, hicolor_rgba(size), "{path}");
83 }
84
85 let ico = fs::read(root().join("packaging/windows/paperdoll-renderer.ico")).expect("ico");
86 let count = u16::from_le_bytes([ico[4], ico[5]]) as usize;
87 assert_eq!(u16::from_le_bytes([ico[0], ico[1]]), 0);
88 assert_eq!(u16::from_le_bytes([ico[2], ico[3]]), 1);
89 assert_eq!(count, 6);
90 for index in 0..count {
91 let entry = 6 + index * 16;
92 let width = match ico[entry] {
93 0 => 256,
94 value => u32::from(value),
95 };
96 let bytes_len = u32::from_le_bytes(ico[entry + 8..entry + 12].try_into().unwrap()) as usize;
97 let offset = u32::from_le_bytes(ico[entry + 12..entry + 16].try_into().unwrap()) as usize;
98 let png = &ico[offset..offset + bytes_len];
99 assert_eq!(&png[..8], b"\x89PNG\r\n\x1a\n");
100 let (got_w, got_h, pixels) = decode_rgba(png).unwrap();
101 assert_eq!((got_w, got_h), (width, width));
102 assert_eq!(pixels, hicolor_rgba(width));
103 }
104}
105
106#[test]
107fn desktop_and_appdata_name_the_same_icon() {
108 let desktop = read("packaging/linux/paperdoll-renderer.desktop");
109 let metainfo = read("packaging/linux/paperdoll-renderer.metainfo.xml");
110 assert!(desktop
111 .lines()
112 .any(|line| line == "Icon=paperdoll-renderer"));
113 // Wayland maps the window app_id to the desktop entry (and its icon).
114 let app_id = paperdoll_renderer::icon::APP_ID;
115 assert_eq!(app_id, "paperdoll-renderer");
116 assert!(desktop
117 .lines()
118 .any(|line| line == format!("StartupWMClass={app_id}")));
119 let generated =
120 paperdoll_renderer::file_assoc::linux_desktop_entry(std::path::Path::new("/opt/p/app"))
121 .unwrap();
122 assert!(generated
123 .lines()
124 .any(|line| line == format!("StartupWMClass={app_id}")));
125 let main = read("src/main.rs");
126 assert!(main.contains(".with_app_id(paperdoll_renderer::icon::APP_ID)"));
127 assert!(main.contains(".with_icon("));
128 assert!(read("Cargo.toml").contains("\"wayland\""));
129 assert!(metainfo.contains("<icon type=\"stock\">paperdoll-renderer</icon>"));
130 assert!(metainfo.contains("paperdoll-renderer.desktop"));
131 for script in [
132 "scripts/package/deb.sh",
133 "scripts/package/appimage.sh",
134 "packaging/arch/PKGBUILD",
135 ] {
136 let text = read(script);
137 assert!(
138 text.contains("hicolor") && text.contains("paperdoll-renderer.metainfo.xml"),
139 "{script} does not install the hicolor icon and the metainfo"
140 );
141 assert!(
142 !text.contains("icon-64.png"),
143 "{script} still installs the old placeholder icon"
144 );
145 }
146 let windows = read("scripts/package/windows.sh");
147 assert!(windows.contains("pe_has_icon.py"));
148 assert!(!windows.contains(".ico"));
149}