use std::fs;
fn main() {
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=README.md");
println!("cargo:rerun-if-changed=CHANGELOG.md");
sync_version_in_docs();
#[cfg(windows)]
embed_windows_icon();
}
fn get_display_version(version: &str) -> String {
if let Some(stripped) = version.strip_suffix("+000") {
stripped.to_string()
} else {
version.to_string()
}
}
fn sync_version_in_docs() {
let version = match std::env::var("CARGO_PKG_VERSION") {
Ok(v) => v,
Err(_) => return,
};
let display_version = get_display_version(&version);
update_readme(&display_version);
update_changelog(&display_version);
}
fn update_readme(display_version: &str) {
let path = "README.md";
let content = match fs::read_to_string(path) {
Ok(c) => c,
Err(_) => return,
};
let new_content = replace_version_in_line(&content, "# QReader v", display_version);
if new_content != content {
let _ = fs::write(path, &new_content);
}
}
fn update_changelog(display_version: &str) {
let path = "CHANGELOG.md";
let content = match fs::read_to_string(path) {
Ok(c) => c,
Err(_) => return,
};
let mut replaced = false;
let new_content: String = content
.lines()
.map(|line| {
if !replaced && line.starts_with("## [") && !line.starts_with("## [Unreleased]") {
replaced = true;
let after_bracket = line.find(']').map(|i| &line[i + 1..]).unwrap_or("");
format!("## [{}]{}", display_version, after_bracket)
} else {
line.to_string()
}
})
.collect::<Vec<_>>()
.join("\n");
let new_content = if content.ends_with('\n') {
format!("{}\n", new_content)
} else {
new_content
};
if new_content != content {
let _ = fs::write(path, &new_content);
}
}
fn replace_version_in_line(content: &str, prefix: &str, new_version: &str) -> String {
let mut replaced = false;
content
.lines()
.map(|line| {
if !replaced && line.starts_with(prefix) {
replaced = true;
format!("{}{}", prefix, new_version)
} else {
line.to_string()
}
})
.collect::<Vec<_>>()
.join("\n")
+ if content.ends_with('\n') { "\n" } else { "" }
}
#[cfg(windows)]
fn ico_fill_rect(buf: &mut [u8], size: u32, x1: u32, y1: u32, x2: u32, y2: u32, color: [u8; 4]) {
for y in y1..y2.min(size) {
for x in x1..x2.min(size) {
let i = ((y * size + x) * 4) as usize;
buf[i..i + 4].copy_from_slice(&color);
}
}
}
#[cfg(windows)]
fn ico_round_corners(buf: &mut [u8], size: u32, x1: u32, y1: u32, x2: u32, y2: u32, r: u32) {
if r == 0 || x2 == 0 || y2 == 0 {
return;
}
let rf = r as f32;
for dy in 0..r + 3 {
for dx in 0..r + 3 {
let dist = ((dx as f32 - rf).powi(2) + (dy as f32 - rf).powi(2)).sqrt();
let coverage = ((rf + 1.5 - dist) / 3.0).clamp(0.0, 1.0);
if coverage >= 1.0 {
continue;
}
let px_r = x2.saturating_sub(1).saturating_sub(dx);
let py_b = y2.saturating_sub(1).saturating_sub(dy);
for (px, py) in [
(x1 + dx, y1 + dy),
(px_r, y1 + dy),
(x1 + dx, py_b),
(px_r, py_b),
] {
if px < size && py < size {
let i = ((py * size + px) * 4) as usize;
let new_a = (buf[i + 3] as f32 * coverage) as u8;
buf[i + 3] = new_a;
if new_a == 0 {
buf[i] = 0;
buf[i + 1] = 0;
buf[i + 2] = 0;
}
}
}
}
}
}
#[cfg(windows)]
fn ico_generate_rgba(size: u32) -> Vec<u8> {
let mut rgba = vec![0u8; (size * size * 4) as usize];
let sc = |v: f32| (v * size as f32 / 24.0) as u32;
let body_color = [74u8, 144, 226, 255];
let clip_color = [38u8, 90, 168, 255];
let line_color = [220u8, 235, 255, 210];
let (bx1, by1, bx2, by2) = (sc(2.0), sc(3.5), sc(22.0), sc(23.0));
ico_fill_rect(&mut rgba, size, bx1, by1, bx2, by2, body_color);
ico_round_corners(&mut rgba, size, bx1, by1, bx2, by2, sc(2.5));
let (cx1, cy1, cx2, cy2) = (sc(7.0), sc(0.5), sc(17.0), sc(6.5));
ico_fill_rect(&mut rgba, size, cx1, cy1, cx2, cy2, clip_color);
ico_round_corners(&mut rgba, size, cx1, cy1, cx2, cy2, sc(2.0));
let hcx = sc(12.0) as i32;
let hcy = sc(3.5) as i32;
let hr = sc(1.5) as i32;
for dy in -hr..=hr {
for dx in -hr..=hr {
if dx * dx + dy * dy <= hr * hr {
let px = hcx + dx;
let py = hcy + dy;
if px >= 0 && py >= 0 && (px as u32) < size && (py as u32) < size {
let i = ((py as u32 * size + px as u32) * 4) as usize;
rgba[i..i + 4].copy_from_slice(&body_color);
}
}
}
}
let lh = sc(1.5).max(3);
ico_fill_rect(
&mut rgba,
size,
sc(5.5),
sc(10.5),
sc(18.5),
sc(10.5) + lh,
line_color,
);
ico_fill_rect(
&mut rgba,
size,
sc(5.5),
sc(14.0),
sc(18.5),
sc(14.0) + lh,
line_color,
);
ico_fill_rect(
&mut rgba,
size,
sc(5.5),
sc(17.5),
sc(13.5),
sc(17.5) + lh,
line_color,
);
rgba
}
#[cfg(windows)]
fn ico_rgba_to_bmp(rgba: &[u8], size: u32) -> Vec<u8> {
let mut bmp = Vec::new();
bmp.extend_from_slice(&40u32.to_le_bytes()); bmp.extend_from_slice(&(size as i32).to_le_bytes()); bmp.extend_from_slice(&((size * 2) as i32).to_le_bytes()); bmp.extend_from_slice(&1u16.to_le_bytes()); bmp.extend_from_slice(&32u16.to_le_bytes()); bmp.extend_from_slice(&0u32.to_le_bytes()); bmp.extend_from_slice(&0u32.to_le_bytes()); bmp.extend_from_slice(&0i32.to_le_bytes()); bmp.extend_from_slice(&0i32.to_le_bytes()); bmp.extend_from_slice(&0u32.to_le_bytes()); bmp.extend_from_slice(&0u32.to_le_bytes());
for y in (0..size).rev() {
for x in 0..size {
let i = ((y * size + x) * 4) as usize;
bmp.push(rgba[i + 2]); bmp.push(rgba[i + 1]); bmp.push(rgba[i]); bmp.push(rgba[i + 3]); }
}
let row_bytes = size.div_ceil(32) * 4;
bmp.extend(vec![0u8; (row_bytes * size) as usize]);
bmp
}
#[cfg(windows)]
fn ico_build(sizes: &[u32]) -> Vec<u8> {
let images: Vec<Vec<u8>> = sizes
.iter()
.map(|&s| ico_rgba_to_bmp(&ico_generate_rgba(s), s))
.collect();
let dir_offset = 6 + 16 * sizes.len();
let mut ico = Vec::new();
ico.extend_from_slice(&0u16.to_le_bytes()); ico.extend_from_slice(&1u16.to_le_bytes()); ico.extend_from_slice(&(sizes.len() as u16).to_le_bytes());
let mut data_offset = dir_offset as u32;
for (i, &size) in sizes.iter().enumerate() {
ico.push(if size >= 256 { 0 } else { size as u8 }); ico.push(if size >= 256 { 0 } else { size as u8 }); ico.push(0); ico.push(0); ico.extend_from_slice(&1u16.to_le_bytes()); ico.extend_from_slice(&32u16.to_le_bytes()); ico.extend_from_slice(&(images[i].len() as u32).to_le_bytes()); ico.extend_from_slice(&data_offset.to_le_bytes()); data_offset += images[i].len() as u32;
}
for img in &images {
ico.extend_from_slice(img);
}
ico
}
#[cfg(windows)]
fn embed_windows_icon() {
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
let ico_path = std::path::Path::new(&out_dir).join("app_icon.ico");
let ico_data = ico_build(&[16, 32, 48, 256]);
fs::write(&ico_path, &ico_data).expect("Failed to write app_icon.ico");
let mut res = winres::WindowsResource::new();
res.set_icon(ico_path.to_str().unwrap());
res.compile().expect("Failed to compile Windows resources");
}