pub mod avatar;
pub mod avatar_cache;
pub mod avatar_options;
pub mod core;
pub mod data;
pub mod render;
pub mod svg;
pub mod util;
use std::io;
use std::path::Path;
use std::sync::{Arc, OnceLock};
use avatar::Avatar;
use avatar_cache::{AvatarCache, CacheKey};
use avatar_options::AvatarOptions;
use core::svg_composer::SvgComposer;
use render::AvatarFormat;
use render::Renderers;
use tiny_skia::Pixmap;
pub struct Multiavatar;
fn composer() -> &'static SvgComposer {
static COMPOSER: OnceLock<SvgComposer> = OnceLock::new();
COMPOSER.get_or_init(|| SvgComposer::with_view_box("0 0 231 231"))
}
pub fn multiavatar(input: &str) -> String {
multiavatar_full(input, false, None, None)
}
pub fn multiavatar_with(input: &str, sans_env: bool) -> String {
multiavatar_full(input, sans_env, None, None)
}
pub fn multiavatar_full(
input: &str,
sans_env: bool,
part: Option<&str>,
theme: Option<&str>,
) -> String {
let options = AvatarOptions::builder().sans_env(sans_env).build();
let options = match part {
Some(p) => options_part(&options, p),
None => options,
};
let options = match theme {
Some(t) => AvatarOptions::builder()
.sans_env(options.sans_env())
.part_owned(options.part().map(str::to_string))
.theme(t)
.build(),
None => options,
};
avatar_with(input, &options).svg().to_string()
}
pub fn avatar(input: &str) -> Arc<Avatar> {
avatar_with(input, &AvatarOptions::defaults())
}
pub fn avatar_with(input: &str, options: &AvatarOptions) -> Arc<Avatar> {
let key = CacheKey::new(
input,
options.sans_env(),
options.part(),
options.theme(),
options.svg_width(),
options.svg_height(),
options.optimize_svg(),
);
if let Some(hit) = AvatarCache::get(&key) {
return hit;
}
let mut svg = composer().compose(
input,
options.sans_env(),
options.part(),
options.theme(),
);
if options.optimize_svg() {
svg = crate::core::svg_optimizer::optimize(&svg);
}
let avatar = Arc::new(Avatar::new(input.to_string(), options.clone(), svg));
AvatarCache::put(key, avatar.clone());
avatar
}
pub fn cache_size() -> usize {
AvatarCache::size()
}
pub fn clear_cache() {
AvatarCache::clear();
}
pub fn to_image(input: &str) -> Option<Pixmap> {
to_image_sized(input, AvatarOptions::defaults().size())
}
pub fn to_image_sized(input: &str, size: u32) -> Option<Pixmap> {
let options = AvatarOptions::builder().size(size).build();
to_image_with(input, &options)
}
pub fn to_image_with(input: &str, options: &AvatarOptions) -> Option<Pixmap> {
avatar_with(input, options).to_image()
}
pub fn to_png(input: &str) -> Vec<u8> {
to_png_sized(input, AvatarOptions::defaults().size())
}
pub fn to_png_sized(input: &str, size: u32) -> Vec<u8> {
let options = AvatarOptions::builder().size(size).build();
to_png_with(input, &options)
}
pub fn to_png_with(input: &str, options: &AvatarOptions) -> Vec<u8> {
avatar_with(input, options).png_sized(options.size())
}
pub fn to_jpg(input: &str) -> Vec<u8> {
to_jpg_sized(input, AvatarOptions::defaults().size())
}
pub fn to_jpg_sized(input: &str, size: u32) -> Vec<u8> {
let options = AvatarOptions::builder().size(size).build();
to_jpg_with(input, &options)
}
pub fn to_jpg_with(input: &str, options: &AvatarOptions) -> Vec<u8> {
avatar_with(input, options).jpg_sized(options.size())
}
pub fn to_gif(input: &str) -> Vec<u8> {
to_gif_sized(input, AvatarOptions::defaults().size())
}
pub fn to_gif_sized(input: &str, size: u32) -> Vec<u8> {
let options = AvatarOptions::builder().size(size).build();
to_gif_with(input, &options)
}
pub fn to_gif_with(input: &str, options: &AvatarOptions) -> Vec<u8> {
let a = avatar_with(input, options);
if a.is_empty() {
return Vec::new();
}
Renderers::create(AvatarFormat::Gif).render(a.svg(), options.size())
}
pub fn to_data_uri(input: &str) -> String {
to_data_uri_sized(input, AvatarOptions::defaults().size())
}
pub fn to_data_uri_sized(input: &str, size: u32) -> String {
let options = AvatarOptions::builder().size(size).build();
to_data_uri_with(input, &options)
}
pub fn to_data_uri_with(input: &str, options: &AvatarOptions) -> String {
avatar_with(input, options).data_uri_sized(options.size())
}
pub fn to_favicon_zip(input: &str) -> Vec<u8> {
to_favicon_zip_sized(input, &[])
}
pub fn to_favicon_zip_sized(input: &str, sizes: &[u32]) -> Vec<u8> {
let svg = multiavatar(input);
if svg.is_empty() {
return Vec::new();
}
render::favicons::to_zip(&svg, sizes)
}
pub fn to_favicon_ico(input: &str) -> Vec<u8> {
to_favicon_ico_sized(input, &[])
}
pub fn to_favicon_ico_sized(input: &str, sizes: &[u32]) -> Vec<u8> {
let svg = multiavatar(input);
if svg.is_empty() {
return Vec::new();
}
render::favicons::to_ico(&svg, sizes)
}
pub fn render(input: &str, format: AvatarFormat, options: &AvatarOptions) -> Vec<u8> {
let a = avatar_with(input, options);
if a.is_empty() {
return Vec::new();
}
match format {
AvatarFormat::Svg => a.svg().as_bytes().to_vec(),
AvatarFormat::Png => Renderers::create(AvatarFormat::Png).render(a.svg(), options.size()),
AvatarFormat::Jpg => Renderers::create(AvatarFormat::Jpg).render(a.svg(), options.size()),
AvatarFormat::Gif => Renderers::create(AvatarFormat::Gif).render(a.svg(), options.size()),
}
}
pub fn write_png(input: &str, path: impl AsRef<Path>) -> io::Result<()> {
write_png_sized(input, AvatarOptions::defaults().size(), path)
}
pub fn write_png_sized(input: &str, size: u32, path: impl AsRef<Path>) -> io::Result<()> {
let bytes = to_png_sized(input, size);
std::fs::write(path, bytes)
}
pub fn write_favicon_zip(input: &str, path: impl AsRef<Path>) -> io::Result<()> {
let bytes = to_favicon_zip(input);
std::fs::write(path, bytes)
}
pub fn write_favicon_ico(input: &str, path: impl AsRef<Path>) -> io::Result<()> {
let bytes = to_favicon_ico(input);
std::fs::write(path, bytes)
}
fn options_part(options: &AvatarOptions, part: &str) -> AvatarOptions {
AvatarOptions::builder()
.sans_env(options.sans_env())
.part(part)
.theme_owned(options.theme().map(str::to_string))
.size(options.size())
.svg_size(options.svg_width(), options.svg_height())
.format(options.format())
.optimize_svg(options.optimize_svg())
.build()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lru_key_ignores_size_and_format() {
clear_cache();
let u = "cache-unit-input";
let _ = to_png_sized(u, 64);
let base = cache_size();
assert!(base >= 1, "首次生成应入缓存");
let _ = to_png_sized(u, 128);
let _ = to_png_sized(u, 512);
let _ = to_gif_sized(u, 256);
let _ = to_jpg_sized(u, 256);
assert_eq!(cache_size(), base, "size/format 不应新增缓存键");
clear_cache();
}
#[test]
fn empty_input_facade() {
assert_eq!(multiavatar(""), "");
assert!(to_png("").is_empty());
assert!(to_jpg("").is_empty());
assert!(to_gif("").is_empty());
assert_eq!(to_data_uri(""), "");
assert!(to_image("").is_none());
assert!(to_favicon_zip("").is_empty());
assert!(to_favicon_ico("").is_empty());
}
}