mod bmp;
use anyhow::{Context, Result};
use resvg::usvg;
use std::io::Write;
use std::path::Path;
use crate::{optimize, render};
const RES_TYPE_ICON: u16 = 1;
const ENTRY_LEN: usize = 16;
pub const SIZES: &[u32] = &[16, 32, 48, 64, 128, 256];
const LEGACY_SIZES: &[u32] = &[16, 32];
struct Entry {
px: u32,
bits: u16,
data: Vec<u8>,
}
pub fn write_ico(
tree: &usvg::Tree,
opt: optimize::Opts,
legacy: bool,
out: &Path,
) -> Result<usize> {
let mut entries: Vec<Entry> = Vec::with_capacity(SIZES.len() + LEGACY_SIZES.len());
for &px in SIZES {
let pixmap = render::render_size(tree, px, px)?;
let raw = render::encode_png(&pixmap)?;
entries.push(Entry {
px,
bits: 32,
data: optimize::maybe_optimize(raw, opt)?,
});
if legacy && LEGACY_SIZES.contains(&px) {
entries.push(Entry {
px,
bits: 8,
data: bmp::encode_bmp_8bpp(&pixmap),
});
}
}
let mut buf: Vec<u8> = Vec::new();
buf.extend_from_slice(&0u16.to_le_bytes());
buf.extend_from_slice(&RES_TYPE_ICON.to_le_bytes());
let count = u16::try_from(entries.len()).expect("entry count is a handful");
buf.extend_from_slice(&count.to_le_bytes());
let mut offset =
u32::try_from(6 + ENTRY_LEN * entries.len()).expect("directory is a few hundred bytes");
for e in &entries {
let dim = if e.px == 256 {
0u8
} else {
u8::try_from(e.px).expect("ICO sizes are at most 256")
};
buf.push(dim); buf.push(dim); buf.push(0); buf.push(0); buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&e.bits.to_le_bytes());
let len = u32::try_from(e.data.len()).context("ICO entry exceeds 4 GiB")?;
buf.extend_from_slice(&len.to_le_bytes());
buf.extend_from_slice(&offset.to_le_bytes());
offset += len;
}
for e in &entries {
buf.extend_from_slice(&e.data);
}
let mut file =
std::fs::File::create(out).with_context(|| format!("creating {}", out.display()))?;
file.write_all(&buf)
.with_context(|| format!("writing {}", out.display()))?;
Ok(entries.len())
}