找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4086

积分

0

好友

568

主题
发表于 昨天 07:30 | 查看: 7| 回复: 0

你是否觉得终端里 echo 出来的文字太过单调?有没有想过让普通的 HELLO 以更酷炫、更醒目的方式显示出来?比如像下面这样:

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

这个想法并不复杂,说干就干,让我们用 Rust 亲手实现一个!

核心思路

实现这样一个工具的核心逻辑非常简单,主要分为三步:

  1. 准备字库:预先将每个字符(A-Z、0-9等)用 Unicode 方块字符“画”好,存储为一个映射表。
  2. 逐字转换:接收用户输入的文字,然后遍历每个字符,通过查表将其转换成对应的 ASCII 艺术字。
  3. 按行拼接:将所有转换后的字符的每一行拼接起来,最后再添加一个漂亮的边框。

项目结构

这个项目非常小巧,只需要三个 Rust 源文件:

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 对应的 6 行,同样追加到每一行的末尾。
  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 宽度

这里有一个容易踩坑的地方:字符串的“长度”和它的“显示宽度”不是一回事。比如 这个字符,它占用 3 个字节(len() 返回 3),但在终端里显示时只占 1 个字符的宽度。

如果直接用 len() 计算来画边框,宽度就不对了。为了解决这个问题,我们引入 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 开源:https://github.com/lispking/ascii-banner。欢迎 Star 和 Fork,如果你有更好的想法或发现了 Bug,也期待你的贡献。这类动手实践是理解一门语言和提升工程能力的好方法,开源实战 社区里也有很多类似的有趣项目。

如果你也想让自己的终端输出与众不同,不妨动手试试这个工具,或者基于这个思路创造你自己的版本。欢迎来 云栈社区 分享你的作品和心得。




上一篇:Rust实战:从零搭建最小可运行的MySQL Proxy(第一篇)
下一篇:SpacetimeDB架构解析:用内存优先与WASM模块简化游戏后端开发
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-10 10:11 , Processed in 0.594577 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表