br-code 0.1.7

This is an code
Documentation
use imageproc::drawing::draw_text_mut;
use image;
use image::{imageops, imageops::FilterType, DynamicImage, Luma, Rgba};
use qrcode::render::svg;
use qrcode::QrCode;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, fs};
use ab_glyph::{FontArc, PxScale};

/// 生成二维码
pub fn qrcode_create(text: &str, mode: &str) -> Result<String, String> {
    let temp_dir = env::temp_dir();
    let filename = format!("{}.{mode}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
    let filepath = temp_dir.join(filename);

    let code = match QrCode::new(text.as_bytes()) {
        Ok(e) => e,
        Err(e) => return Err(format!("Error QrCode file: {}", e)),
    };
    match mode {
        "svg" => {
            let string = code.render().min_dimensions(200, 200).dark_color(svg::Color("#000000")).light_color(svg::Color("#ffffff")).build();
            match fs::write(filepath.clone(), string) {
                Ok(_) => Ok(filepath.to_str().unwrap().to_string()),
                Err(e) => Err(format!("Error writing svg file: {}", e)),
            }
        }
        "png" => {
            let image = code.render::<Luma<u8>>().build();
            match image.save(filepath.clone()) {
                Ok(_) => Ok(filepath.to_str().unwrap().to_string()),
                Err(e) => Err(format!("Error writing png file: {}", e)),
            }
        }
        _ => {
            let image = code.render::<Luma<u8>>().min_dimensions(200, 200).build();
            match image.save(filepath.clone()) {
                Ok(_) => Ok(filepath.to_str().unwrap().to_string()),
                Err(e) => Err(format!("Error writing file: {}", e)),
            }
        }
    }
}

pub fn qrcode_read(filename: &str) -> Result<Vec<String>, String> {
    let mut qrcode = vec![];

    let img = image::open(filename);
    let img_gray = match img {
        Ok(e) => e.into_luma8(),
        Err(e) => return Err(format!("Error reading image {}", e))
    };

    let mut decoder = quircs::Quirc::default();
    let codes = decoder.identify(img_gray.width() as usize, img_gray.height() as usize, &img_gray);
    for code in codes {
        let code = match code {
            Ok(e) => e,
            Err(e) => return Err(format!("Error reading image {}", e))
        };
        let decoded = match code.decode() {
            Ok(e) => e,
            Err(e) => return Err(format!("Error reading image {}", e))
        };
        qrcode.push(std::str::from_utf8(&decoded.payload.clone()).unwrap().to_string());
    }
    Ok(qrcode)
}

/// 生成 svg 二维码
pub fn create_qrcode(code: &str) -> String {
    let code = QrCode::new(code).unwrap();
    let string = code.render().min_dimensions(200, 200).dark_color(svg::Color("#000000")).light_color(svg::Color("#ffffff")).build();
    string.replace("width=\"203\"", "width=\"100%\"").replace("height=\"203\"", "height=\"100%\"")
}

/// 合成二维码 + 图片
pub fn compose_qrcode(
    bg_data: &[u8],
    qrcode_data: &[u8],
    pos_x: i64,
    pos_y: i64,
    size: u32,
) -> Result<String, String> {
    let mut bg_img = image::load_from_memory(bg_data).map_err(|e| format!("无法加载背景图: {}", e))?.to_rgba8();

    let mut qr_img = image::load_from_memory(qrcode_data).map_err(|e| format!("无法加载二维码: {}", e))?.to_rgba8();

    // 白色透明化
    for pixel in qr_img.pixels_mut() {
        if pixel[0] > 240 && pixel[1] > 240 && pixel[2] > 240 {
            pixel[3] = 0;
        }
    }

    let qr_resized = DynamicImage::ImageRgba8(qr_img).resize_exact(size, size, FilterType::Lanczos3);

    imageops::overlay(&mut bg_img, &qr_resized, pos_x, pos_y);

    // 保存到临时文件
    let temp_dir = env::temp_dir();
    let filename = format!("{}.png", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
    let filepath = temp_dir.join(filename);
    bg_img.save(&filepath).map_err(|e| format!("保存二维码合成图片失败: {}", e))?;

    Ok(filepath.to_str().unwrap().to_string())
}

/// 合成文字 + 图片
pub fn compose_text(
    bg_data: &[u8],
    font_data: &[u8],
    text: &str,
    pos_x: i32,
    pos_y: i32,
    font_size: f32,
) -> Result<String, String> {
    // 加载背景图
    let mut bg_img = image::load_from_memory(bg_data).map_err(|e| format!("无法加载背景图: {}", e))?.to_rgba8();

    // 用 ab_glyph 加载字体
    let font = FontArc::try_from_vec(font_data.to_vec()).map_err(|_| "加载字体失败")?;

    // 字体大小
    let scale = PxScale::from(font_size);

    // 绘制文字
    draw_text_mut(
        &mut bg_img,
        Rgba([0, 0, 0, 255]),
        pos_x,
        pos_y,
        scale,
        &font,
        text,
    );

    // 保存临时文件
    let temp_dir = env::temp_dir();
    let filename = format!("{}.png", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
    let filepath = temp_dir.join(filename);

    bg_img.save(&filepath).map_err(|e| format!("保存文字合成图片失败: {}", e))?;

    Ok(filepath.to_str().unwrap().to_string())
}

#[cfg(test)]
mod tests {
    use std::fs;
    #[test]
    fn qrcode_create() {
        let i = match crate::qrcode::qrcode_create("otpauth://totp/br:t?secret=VIDKKSUNCJGXELPWK6MG2BWZOTQJVTQINRLYUSNV6YQNRRJMVZHA&issuer=br", "png") {
            Ok(e) => e,
            Err(_) => {
                return;
            }
        };
        let res = fs::read(i).unwrap();
        fs::write("./examples/444.png", res).unwrap();
    }
    #[test]
    fn qrcode_read() {
        let i = match crate::qrcode::qrcode_read("./examples/444.png") {
            Ok(e) => e,
            Err(_) => {
                return;
            }
        };
        for item in i {
            assert_eq!(item, "otpauth://totp/br:t?secret=VIDKKSUNCJGXELPWK6MG2BWZOTQJVTQINRLYUSNV6YQNRRJMVZHA&issuer=br");
        }
    }
}