use std::path::Path;
use std::sync::{Arc, OnceLock};
use anyhow::{Context, Result};
use image::DynamicImage;
use resvg::tiny_skia::Pixmap;
use resvg::usvg::fontdb;
use resvg::usvg::{Options, Tree};
static FONT_DB: OnceLock<Arc<fontdb::Database>> = OnceLock::new();
fn shared_fontdb() -> Arc<fontdb::Database> {
FONT_DB
.get_or_init(|| {
let mut db = fontdb::Database::new();
db.load_system_fonts();
Arc::new(db)
})
.clone()
}
pub fn create_thumbnail(path: &Path, max_dim: u32) -> Result<DynamicImage> {
let data = std::fs::read(path).with_context(|| format!("读取 SVG 失败: {:?}", path))?;
let opt = Options {
fontdb: shared_fontdb(),
..Options::default()
};
let tree = Tree::from_data(&data, &opt).context("解析 SVG 失败")?;
let size_f = tree.size();
let scale = (max_dim as f32 / size_f.width().max(size_f.height())).min(1.0);
let w = (size_f.width() * scale).ceil() as u32;
let h = (size_f.height() * scale).ceil() as u32;
let mut pixmap = Pixmap::new(w, h).context("创建 pixmap 失败")?;
resvg::render(
&tree,
resvg::tiny_skia::Transform::from_scale(scale, scale),
&mut pixmap.as_mut(),
);
let img = DynamicImage::ImageRgba8(
image::RgbaImage::from_raw(w, h, pixmap.data().to_vec()).context("构建 RGBA 失败")?,
);
Ok(img.thumbnail(max_dim, max_dim))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_with_sans_serif_renders() {
let path = std::env::temp_dir().join("media_decode_font_test.svg");
std::fs::write(
&path,
r##"<svg xmlns="http://www.w3.org/2000/svg" width="100" height="40">
<text x="5" y="30" font-family="sans-serif" font-size="24">Test</text>
</svg>"##,
)
.unwrap();
let img = create_thumbnail(&path, 64).unwrap();
let has_ink = img.to_rgba8().pixels().any(|p| p.0[3] > 0);
assert!(has_ink, "sans-serif 文本未渲染:系统字体库未生效");
}
}