< 返回版块

lispking 发表于 2026-03-06 23:35

Tags:Rust,ASCII-art,text-banner

起因

某天我在终端里敲命令,突然想:能不能让输出更炫一点?

比如把普通的 HELLO 变成这种大字:

╔═════════════════════════════════════════════════════╗
║                                                     ║
║  ██╗  ██╗  ███████╗  ██╗       ██╗        ██████╗   ║
║  ██║  ██║  ██╔════╝  ██║       ██║       ██╔═══██╗  ║
║  ███████║  █████╗    ██║       ██║       ██║   ██║  ║
║  ██╔══██║  ██╔══╝    ██║       ██║       ██║   ██║  ║
║  ██║  ██║  ███████╗  ███████╗  ███████╗  ╚██████╔╝  ║
║  ╚═╝  ╚═╝  ╚══════╝  ╚══════╝  ╚══════╝   ╚═════╝   ║
║                                                     ║
╚═════════════════════════════════════════════════════╝

说干就干,用 Rust 实现一个!

核心思路

其实原理特别简单,就三步:

  1. 准备字库:把每个字符(A-Z、0-9等)都用 Unicode 方块字符画好,存成一个映射表
  2. 逐字转换:输入文字后,查表把每个字符转成对应的 ASCII 艺术字
  3. 按行拼接:把所有字符的每一行拼在一起,最后加个漂亮的边框

项目结构

项目很小,就三个文件:

src/
├── main.rs    # 命令行入口,处理参数
├── banner.rs  # 核心转换逻辑
└── font.rs    # 字库定义

字库设计

首先,我们需要定义每个字符长什么样。用 Unicode 方块字符(█、╗、╔ 等)来画,比传统的 #* 更美观。

每个字符高度固定为 6 行,比如字母 A

vec![
    "   █████╗  ",  // 第1行
    "  ██╔══██╗ ",  // 第2行
    "  ██║  ██║ ",  // 第3行
    "  ███████║ ",  // 第4行
    "  ██╔══██║ ",  // 第5行
    "  ╚═╝  ╚═╝ ",  // 第6行
]

用一个 HashMap<char, Vec<&str>> 存所有字符,查起来很方便。

核心转换逻辑

关键函数 text_to_ascii,把输入文字转成多行 ASCII 艺术:

pub fn text_to_ascii(text: &str) -> Option<Vec<String>> {
    let font_map = get_font_map();
    let mut result: Vec<String> = vec![String::new(); FONT_HEIGHT];  // 6个空字符串

    for c in text.to_uppercase().chars() {
        if let Some(lines) = font_map.get(&c) {
            // 把每个字符的每一行追加到结果中
            for (i, line) in lines.iter().enumerate() {
                result[i].push_str(line);
                result[i].push_str(" ");  // 字符间距
            }
        }
    }

    Some(result)
}

举个例子,输入 "AB":

  1. 先查 A,得到 6 行字符串,分别追加到 result[0]~result[5]
  2. 再查 B,同样追加到每行后面
  3. 最终得到 6 行拼好的大字

加个边框更帅气

用 Unicode 制表符画边框:

pub fn generate_banner(text: &str, padding: usize) -> String {
    let ascii_lines = text_to_ascii(text)?;

    // 画上边框
    let top = format!("╔{}╗", "═".repeat(width));

    // 画内容行(左右加空格 padding)
    // ...

    // 画下边框
    let bottom = format!("╚{}╝", "═".repeat(width));
}

一个小坑:Unicode 宽度

用普通 len() 计算字符串长度会翻车!因为 这种字符占 3 个字节,但显示宽度只有 1。

所以用了 unicode-width 这个 crate:

use unicode_width::UnicodeWidthStr;

let width = "█████╗".width();  // 正确得到显示宽度

命令行参数处理

没有用 clap 这样的库,直接手写简单处理:

fn main() {
    let args: Vec<String> = std::env::args().collect();

    if args.len() < 2 || args.contains(&"--help".to_string()) {
        print_help();
        return;
    }

    let no_box = args.contains(&"--no-box".to_string());
    // ...
}

使用效果

# 编译
cargo build --release

# 运行
./target/release/ascii-banner "RUST"

# 不要边框
./target/release/ascii-banner "2026" --no-box

输出:

╔══════════════════════════════════════════════╗
║                                              ║
║  ██████╗    ██╗   ██╗  ███████╗   ████████╗  ║
║  ██╔══██╗   ██║   ██║  ██╔════╝   ╚══██╔══╝  ║
║  ██████╔╝   ██║   ██║  ██████╗       ██║     ║
║  ██╔══██╗   ██║   ██║  ╚════██╗      ██║     ║
║  ██║  ██║   ╚██████╔╝  ███████║      ██║     ║
║  ╚═╝  ╚═╝    ╚═════╝   ╚══════╝      ╚═╝     ║
║                                              ║
╚══════════════════════════════════════════════╝

总结

这个项目虽然简单,但涵盖了几个有趣的点:

  1. Unicode 方块字符 — 让终端输出更美观
  2. HashMap 字库 — 灵活可扩展的字符映射
  3. Unicode 宽度计算 — 处理多字节字符的正确姿势
  4. 按行拼接思路 — 把二维数组转成一维输出

完整代码已开源:github.com/lispking/ascii-banner

如果你也想让终端输出更酷,不妨试试这个小工具!


Ext Link: https://github.com/lispking/ascii-banner

评论区

写评论
xbee 2026-03-07 08:31

要是可以支持中文就很棒了!!!这主要是中文字体的问题,用 ascii 字符在终端中显示中文!!

1 共 1 条评论, 1 页