另一种 Rime 过滤生僻字的方法:使用 lua 脚本自定义字符集

为了保证输入法能够输出所有你想要的汉字,Rime 默认使用了超大字符集。但超大字符集同时也会带来一个问题:「系统自带字体不一定能够显示所有的汉字」。
因此,会造成 Rime 输入法候选词中,出现很多方块问号,这些就是系统无法显示的生僻字。

RIME生僻字

解决这个问题最简单粗暴的一个方法就是使用支持超大字符集的字体。这样所有的汉字都可以被正确显示,但是这样的话,候选词中会出现很多我们通常不会输入的生僻字词。

2013 年,国务院公布了由教育部、国家语言文字工作委员会组织制定的「通用规范汉字表」。其中共收录了 8105 个汉字,其中包含 3500 个日常基本用字,3000 个次常用字,以及 1605 个人名、地名、专业术语、教材文言文中的常用汉字。而通常我们平时使用的汉字一般在 3000 个左右,「规范汉字表」中的汉字完全可以满足绝大多数人的简体中文输入需求。

因此,我希望能够让 Rime 输入法仅输出「规范汉字表」中的汉字,这样不至于让输入法候选字词过多,又能兼顾我的常用输入需求。

而除了这 8105 个常用汉字以外,我还希望 Rime 能够像系统输入法一样,可以支持输出并显示 Emoji 表情字符,Emoji 字符也是我日常输入中用到比较多的字符。如果能够直接将 Emoji 字符集成到输入法中,那么是最好不过了的。

因此,我希望我的 Rime 输入法能够支持的字符有:

  1. 「规范汉字表」中的 8105 字
  2. Emoji 表情字符
  3. 有可能需要输入其他语言 (非中文) 的一些字符

而通过网络搜索,我找到了几种过滤生僻字的方法:

  1. 通过字符集过滤

    这种方法能够通过 Rime 内置的字符集(比如 GB2312, GBK, UTF-8 等等)过滤汉字。但是这种方式不能使用自定义字符集。

  2. 通过词典过滤

    通过词典过滤就是将所有可能用到的字、词全部制作成 Rime 的词典。这种方法太不优雅了,工作量大,而且后续维护起来也很不方便。

以上的方法都不能很好地满足我的需求,因此我找到了另外一种方法:「lua_filter」,通过 lua 脚本来过滤候选词。

librime-lua 是一个给 librime 添加了 lua 脚本的 Rime 插件。
并且官方发行的「小狼毫」(Windows) 和「鼠须管」(macOS) 都默认集成并启用了 librime-lua 插件。因此,可以通过 lua 编写一个自定义过滤器来实现生僻字过滤的功能。

至于 Linux 下的 Rime 输入法由各发型版打包,是否集成了 librime-lua 插件我不大清楚。

lua 脚本的位置应当放在 Rime 配置文件夹 中。

其中包括一个 rime.lua 脚本,与一个 lua 文件夹。rime.lua 是 Rime 读取 lua 脚本的入口,lua 过滤器的函数必须在 rime.lua 文件中声明。

但是如果把全部代码都写入到一个文件中的话,会让 rime.lua 文件变得很臃肿,因此可以把真正的脚本代码放入 lua/charset.lua 中,然后在 rime.lua 中引入它。

--[[
charset_filter: 生僻字过滤器
--]]

-- 从 txt 文件中读取全部字符
-- return: string
local function read_chars(filename)
  local chars = ""
  for line in io.lines(filename) do
    chars = chars..line
  end
  return chars
end

-- 自定义字符集列表 (字符串列表),候选词只能包括列表中的字符,否则将被过滤掉
local charsets = {
  -- 通过 read_chars 读取 txt 文件中的所有字符并添加到字符集列表中
  -- 将相应的字符写入 txt 文件中,然后通过 read_chars 在这里引入它就可以
  -- 这里为了区分不同的字符集,将它们写入了不同的 txt 文件中,但是你也可以只使用一个 txt 文件
  -- 这里似乎只能使用 txt 文件的绝对路径,怎么使用相对文件路径我还没有搞清楚

  -- lua 中通过 .. 连接两个字符串,而 os.getenv("HOME") 表示我的 HOME 目录 (Windows 下不能这样)
  -- os.getenv("HOME").."/Library/Rime/lua/chars/emoji.txt" 等同于 "/Users/zengxs/Library/Rime/lua/chars/emoji.txt"
  read_chars(os.getenv("HOME").."/Library/Rime/lua/chars/emoji.txt"),  -- emoji 字符集
  read_chars(os.getenv("HOME").."/Library/Rime/lua/chars/symbols.txt"),  -- 符号字符集
  read_chars(os.getenv("HOME").."/Library/Rime/lua/chars/alphanum.txt"),  -- 数字字母字符集
  read_chars(os.getenv("HOME").."/Library/Rime/lua/chars/mandarin.txt"),  -- 通用规范汉字表
}

-- 验证一个字符编码是否在自定义的字符集中
local function is_valid_char(char_code)
  for i=1, #(charsets) do
    for p, code in utf8.codes(charsets[i]) do
      if (code == char_code) then
        return true
      end
    end
  end
  return false
end

-- 检查字符串是否符合要求
local function is_valid_string(text)
  for p, char_point in utf8.codes(text) do
    if (not is_valid_char(char_point)) then
      return false
    end
  end
  return true
end

local function filter(input)
  for cand in input:iter() do
    if (is_valid_string(cand.text)) then
      yield(cand)
    end
  end
end

return filter

然后修改 rime.lua

-- charset_filter 将会是字符集过滤器的名称
charset_filter = require("charset")

然后在 luna_pinyin.custom.yaml 中启用这个过滤器:

patch:
  # engine/filters/+ 表示在现有的「过滤器列表」末尾再添加下面的过滤器
  engine/filters/+:
    - [email protected]_filter

然后重新部署就就可以看到字符集已经生效了。


关于我制作的「通用规范汉字表」的 txt 文件可见:https://gist.github.com/zengxs/3c277a02a3bfa9a556a09a6b497ad061


参考资料

发表新评论