吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 997|回复: 5
收起左侧

[其他原创] [Rust] Morphio: 修改字体,使得一个单词渲染为另一个

  [复制链接]
三滑稽甲苯 发表于 2026-3-27 23:16
本帖最后由 三滑稽甲苯 于 2026-3-27 22:44 编辑

简介

这几天空闲时间搓了个 修改字体的工具 Morphio,利用字体特性,把特定单词渲染为其它单词。例如可以把 Microsoft 渲染为 Microslop {:301_998:}:

Microslop.png

实际上不止单词,特殊符号或者长文本也可以,所以你甚至可以做一个 $\LaTeX$ 字体:

LaTeX.png

当然,中文字符也是支持的:

Chinese.png

网页端可以直接导入/导出 TOML 格式的配方,允许你轻松分享修改流程。一个简单的配方:

[options]
word_match_start = true
word_match_end = true
skip_missing_glyphs = false

[[rules]]
from = "Microsoft"
to = "Microslop"

可以在 tests/recipes 目录下查看更多示例配方,目前包含:

  • coverage.toml: 主要用于测试
  • latex_unicode.toml: 渲染 $\LaTeX$ 表达式为 Unicode 字符。如果字体支持不全,可能需要启用 “Skip missing glyphs”
  • simple.toml: 把 Microsoft 渲染为 Microslop

原理

OpenType 与字形替换表 (GSUB))

OpenType 是现代字体的唯一通用标准。相较于老旧的 TrueType (TTF),它提供了连字、变体字、可变字体等排版特性。实际上,现在绝大部分 TTF 字体用的是 OpenType 标准。

许多字体都利用了 OpenType 的连字功能,例如英文中 fifl 的连字:

Ligature_drawing.png

也有很多编程字体也提供了诸如 !=, == 这些特殊符号的连字。

那么,怎么实现连字呢?就是通过字体内的字形替换表 (GSUB, Glyph Substitution Table)。简单来说,它允许你将一系列字形 (glyph) 替换为另一系列字形。它主要提供了下面 6 类子表,每类都可以添加多条规则:

  1. Single substitution:单一字形替换 (1 -> 1)。把一个字形替换为另一个字形
  2. Multiple substitution:多重字形替换 (1 -> N)。把一个字形替换为多个字形
  3. Alternate substitution:把一个字形替换为一系列字形中的任意一个(允许用户选择)
  4. Ligature substitution:连字替换 (N -> 1)。把多个字形替换为一个字形
  5. Contextual substitution:根据上下文替换字形。指定一系列输入字形,并提供一系列 Sequence Lookup Record。每一条记录指定输入字形的序号以及修改方式(另一个 GSUB 子表的索引)
  6. Chained contexts substitution:根据上下文替换字形,在 Contextual substitution 的基础上允许回溯和前瞻(指定这些字形之前和之后的字形)

为了匹配整个单词,我们需要忽略字形前后有字母、数字和下划线的情况,所以我们采用 Chained contexts substitution。

N -> N

既然如此,若两个单词长度相等 (例如 MicrosoftMicroslop),我们只需要:

  1. 插入一个 Single 类型的表,规则为:
    1. o 替换为 l
    2. f 替换为 o
    3. t 替换为 p
  2. 插入一个 Chained contexts 类型的表,当匹配到 Microsoft 单词时按照下面的规则执行上面的 Single 替换表:
    1. 第 7 个字形执行规则 1
    2. 第 8 个字形执行规则 2
    3. 第 9 个字形执行规则 3

注意到如果字符相同就可以跳过,同时如果替换规则重复也可以重复利用。

N -> 1, 1 -> M

若输入单词 abc,输出单词 d,长度为 1:

  1. 插入 Ligature 类型的表,规则为将 abc 替换为 d
  2. 插入一个 Chained contexts 类型的表,当匹配到 abc 时执行上面的替换表

输入单词长度为 1 的情况类似,使用 Multiple substitution 实现字形替换即可。

N -> M

如果 N > M,则先使用 N -> N 替换前面的字形,再使用 N - M -> 1 替换。例如 abc -> de

  1. 插入 Single 类型的表,规则为将 a 替换为 d
  2. 插入 Ligature 类型的表,规则为将 bc 替换为 e3. 插入一个 Chained contexts 类型的表,当匹配到 abc 时执行上面的替换表:
    1. 第 1 个字形执行 Single 表的替换
    2. 第 2-3 个字形执行 Ligature 表的替换

否则,使用 M -> M 替换前面的字形,再使用 1 -> M - N 替换。方法类似,此处不再赘述。

关键代码

fn append_word_substitution_lookups(
    font: &FontRef<'_>,
    gsub: &mut Gsub,
    rules: &[ResolvedMorphRule],
    word_match_start: bool,
    word_match_end: bool,
) -> Result<Vec<u16>, MorphError> {
    let word_glyph_ranges = if word_match_start || word_match_end {
        word_glyph_ranges(font)?
    } else {
        Vec::new()
    };

    let mut lookup_indices = Vec::new();

    for rule in rules {
        let mut single_cache = SingleSubstitutionCache::default();
        let mut sequence_records = Vec::new();

        if rule.from_glyphs.len() == rule.to_glyphs.len() { // N -> N
            sequence_records.extend(build_n_to_n_records(
                gsub,
                &rule.from_glyphs,
                &rule.to_glyphs,
                0,
                &mut single_cache,
            )?);
        } else if rule.from_glyphs.len() == 1 { // 1 -> M
            sequence_records.push(build_one_to_n_record(
                gsub,
                0,
                rule.from_glyphs[0],
                &rule.to_glyphs,
            )?);
        } else if rule.to_glyphs.len() == 1 { // N -> 1
            sequence_records.push(build_n_to_one_record(
                gsub,
                0,
                &rule.from_glyphs,
                rule.to_glyphs[0],
            )?);
        } else if rule.from_glyphs.len() < rule.to_glyphs.len() { // N < M
            let prefix_len = rule.from_glyphs.len() - 1;
            sequence_records.extend(build_n_to_n_records(
                gsub,
                &rule.from_glyphs[..prefix_len],
                &rule.to_glyphs[..prefix_len],
                0,
                &mut single_cache,
            )?);
            sequence_records.push(build_one_to_n_record(
                gsub,
                prefix_len,
                rule.from_glyphs[prefix_len],
                &rule.to_glyphs[prefix_len..],
            )?);
        } else { // N > M
            let prefix_len = rule.to_glyphs.len() - 1;
            sequence_records.extend(build_n_to_n_records(
                gsub,
                &rule.from_glyphs[..prefix_len],
                &rule.to_glyphs[..prefix_len],
                0,
                &mut single_cache,
            )?);
            sequence_records.push(build_n_to_one_record(
                gsub,
                prefix_len,
                &rule.from_glyphs[prefix_len..],
                rule.to_glyphs[prefix_len],
            )?);
        }

        if sequence_records.is_empty() {
            continue;
        }

        let contextual_lookup = create_contextual_lookup(
            &rule.from_glyphs,
            word_glyph_ranges.clone(),
            sequence_records,
            word_match_start,
            word_match_end,
        );
        lookup_indices.push(push_lookup(gsub, contextual_lookup)?);
    }

    Ok(lookup_indices)
}

链接

免费评分

参与人数 3威望 +1 吾爱币 +11 热心值 +3 收起 理由
苏紫方璇 + 1 + 10 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
pyjiujiu + 1 + 1 我很赞同!
Issacclark1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Zsl155188 发表于 2026-3-27 23:42
非常有用感谢
Henglie 发表于 2026-3-28 10:48
gaogaogaogaogao 发表于 2026-3-28 12:47
Edcison 发表于 2026-3-29 16:57
挺好用的感谢
a120661 发表于 2026-4-4 20:09
这个应用场景是什么
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-6-30 07:27

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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