3
5
2023
0

Linux 上的字体配置与故障排除

常见汉字字体

电脑系统要显示字,首先得有字体。现在 Linux 上常用的、在维护的开源中文字体就一套,同时被 Noto思源两个项目收录。Noto 系列字体是 Google 主导的,名字的含义是「没有豆腐」(no tofu),因为缺字时显示的方框或者方框被叫作「tofu」。思源系列字体是 Adobe 主导的。其中汉字部分被称为「思源黑体」和「思源宋体」,是由这两家公司共同开发的,两个字体系列的汉字部分是一样的。

Noto 字体在 Arch Linux 上位于以下软件包中:

  • noto-fonts: 大部分文字的常见样式,不包含汉字
  • noto-fonts-cjk: 汉字部分
  • noto-fonts-emoji: 彩色的表情符号字体
  • noto-fonts-extra: 提供额外的字重和宽度变种

Noto 系列字族名只支持英文,命名规则是 Noto + Sans 或 Serif + 文字名称。其中汉字部分叫 Noto Sans/Serif CJK SC/TC/HK/JP/KR,最后一个词是地区变种。

思源系列则有:

  • adobe-source-sans-fonts: 无衬线字体,不含汉字。字族名叫 Source Sans 3 和 Source Sans Pro,以及带字重的变体,加上 Source Sans 3 VF
  • adobe-source-serif-fonts: 衬线字体,不含汉字。字族名叫 Source Code Pro,以及带字重的变体
  • adobe-source-code-pro-fonts: 等宽字体,不含汉字。字族名叫 Source Code Pro,以及带字重的变体,加上 Source Code Variable。
  • adobe-source-han-{sans,serif,mono}-{cn,hk,jp,kr,tw}-fonts: 五个地区的汉字之黑体、宋体和等宽版本
  • adobe-source-han-{sans,serif,mono}-otc-fonts: 所有地区合体了的汉字之黑体、宋体和等宽版本

其中等宽版本的中文字体位于 [archlinuxcn] 仓库中。

思源汉字字体的字族名有两种,「独立包装」的版本(非 OTC 版本),是「Source Han Sans/Serif」或本地化名称、空格、地区代码(CN/HK/TW/JP/KR)。比如「思源黑体 CN」、「源ノ角ゴシック JP」等。也有带字重的别名。

而全部打包的 OTC 版本,字族名是本地化名称或者英文的「Source Han Sans/Serif」空格再加上「HC/TC/HC/K」变种代码。如果没有变种代码,则是日文变种。为了区分,香港繁体的版本附带「香港」字样,比如黑体叫「思源黑體 香港」。这些字体也有不同字重的别名。另外有个半宽的版本,是在字族名的变种代码前加「HW」字样,仅有少数几个字符是半宽的。

OTC 版本有趣的地方在于,对于大多数软件来说,不管你叫它的哪个地区的名字,它都会以设定的语种来显示。比如网页声明语种为日文(<html lang=ja>),那么不管字体指定为「源ノ角ゴシック」还是「思源黑体」或者「본고딕」,它都会「门上插刀、直字拐弯、天顶加盖、船顶漏雨」。所以用这个字体的话,不妨一律写「Source Han Sans」,然后加好语种标记。我知道的唯一例外是 mpv 的 ass 字幕文件,里边指定本地化名称的话,会使用那个语种的变体显示。

早些年还没有 Noto 和思源的时候,Linux 系统上通常使用文泉驿正黑或者文泉驿微米黑。后者是基于 Android 系统上的 Droid Sans Fallback 字体,体积较小。再之前是文鼎系列字体,也就是名字「AR PL」开头、包名叫 ttf-arphic-{uming,ukai} 的那些。

字体的属性

字体有很多属性,常用的有字族(family)、倾斜(slant)、字重(weight)。后两者合一起叫样式(style)。

字族就是它的名字啦。常见的指代字体的方式除了字族之外还有 Postscript 名,它不含空格、使用短横线将样式附加在名称之后,比如「DejaVuSans-BoldOblique」。后者是 CSS @font-face 规则中使用 local唯一指定样式的方法(除非该字体把样式也写到了字族名里)。

倾斜就是斜不斜,英文叫「Roman」「Italic」或者「Oblique」,Italic 是专门的斜体写法(更接近手写样式), Oblique 是把常规写法倾斜一下完事。

字重就更简单了,就是笔划的粗细。常见的有 Regular、Normal、Medium、Bold、Semibold、Black、Thin、Light、Extralight 等。

详细信息可以 man 5 fonts-conf 查询。

通用字族名

很多时候,程序并不在乎用户具体使用的是哪款字体,像很多网站的 CSS 那样把各个平台的常见字体全部列出来太傻了,又容易出问题。所以,人们发明了「通用字族名」,也就是 sans-serif (sans)、serif 和 monospace (mono) 这些。中文分别叫无衬线字体、衬线字体和等宽字体。但是中文字体不讲衬线不衬线的,而是叫「黑体」和「宋体」(有些地区叫「明体」)。黑体常用于屏幕显示的正文,而宋体常用于印刷文本的正文。

另外,中文没有斜体。英文使用斜体的场合,中文通常是使用仿宋或者楷体。中文本也没有粗体。传统上,强调的时候,中文使用着重号,也就是在字的下方或者右方加点,像这样子(如果你看到的着重号在文字上方,那是因为你用的 Chrome/Chromium 浏览器不听页面指示,执意将它作为日文处理了)。

最近有一个新加的通用字族名叫作「emoji」。Pango 渲染表情符号的文本时,会自动使用 emoji 字体。但是 Qt 尚不支持,导致有时会出问题,而将 emoji 字体排到常规字体之前的做法,又会导致数字和空格显示为全角。火狐自带了一个 SVG 格式的 emoji 字体,会自动使用。很多软件(比如 Telegram)也会使用图片来取代 emoji 字符。

CSS 4 又加了一套 ui- 开头的字族名但是除了 Safari 没浏览器支持。fontconfig 倒是可以通过配置来支持上,但是由于火狐的一个 bug 导致 ui-sans-serif 无效。

fontconfig 配置

大部分 Linux 桌面软件都或多或少地使用 fontconfig 来获取字体配置信息。其中 Pango(GTK 使用的文字渲染库)的支持是最好的。很多简陋的图形界面库则只用来读取默认字体,可能完全不支持字体回落,造成部分文字明明有字体却显示为「豆腐」。

了解了通用字族名,我们就可以为它们指定我们喜欢的字体啦。在 ~/.config/fontconfig/fonts.conf 里为每一个通用字族名像这样写即可:

  <match target="pattern">
    <test qual="any" name="family">
      <string>sans-serif</string>
    </test>
    <edit name="family" mode="prepend" binding="strong">
      <string>DejaVu Sans</string>
      <string>文泉驿正黑</string>
      <string>Twemoji</string>
      <string>Font Awesome 6 Free</string>
      <string>Font Awesome 6 Brands</string>
      <string>Source Han Sans</string>
    </edit>
  </match>

因为我并没有完全采用思源字体来显示汉字,所以我还是为不同语言和地区变种分别匹配了不同的字体。我完整的配置文件见:https://github.com/lilydjwg/dotconfig/tree/master/fontconfig。其中,web-ui-fonts.conf 文件用于提供 CSS 4 新增的字族名,而 source-han-for-noto-cjk.conf 则使用思源系列字体来代替 Noto CJK 系列字体。

查看浏览器使用的字体

排查字体问题时,一个常见的要知道的事实是,软件究竟在用什么字体来显示这些文本?想知道这个通常很难,但是对浏览器来说却很简单。所以字体匹配问题首先看浏览器能不能复现。

火狐浏览器,对着有疑问的字点右键,选择「检查」(也可以按 Q 键),然后看弹出的开发者工具右边的「字体」选项卡即可。鼠标悬停到下方灰色的字体名上时还能将使用该字体的字高亮显示。

在火狐中查看网页所使用的字体

Google Chrome 浏览器及其变种类似,对着有疑问的字点右键,选择「检查」(也可以按 N 键),然后看弹出的开发者工具右边的「计算样式」选项卡,拖动到最下面,可以看到使用的字体名以及有多少个字形。

在 Google Chrome 中查看网页所使用的字体

至于这个字体是怎么选上的,可以切换到「规则」(火狐)或者「样式」(Google Chrome)选项卡来看 CSS 规则。搜索「font-family」看看具体被应用上的规则是哪一条。通常这里会写上一大排字体名。火狐会将正在使用的那个加上下划线,但是有时候不准确(比如该 HTML 元素使用了多种字体)。更好的除错方法是,从头到尾一个个删字体,删到哪一个时网页上的字体变动了,就说明在使用的是哪一个。我通过这种方式找出了好些我学生时代不懂事从 Windows 下复制过来的字体导致的问题。

Google Chrome 默认的字体比较奇怪,是「Times New Roman」、「Arial」和「Monospace」。见《Google Chrome 中的字体设置》一文。

Qt

https://z.sh/qtfontbugs。其中最著名的 bug 是 QTBUG-80434 (https://z.sh/434)。

小技巧

使用 gucharmap 软件可以检查所有字符使用指定的字体时的渲染效果,以及它回落到什么字体上了。找到要查看的字符,然后对着它按住右键即可。

使用 fc-match -s NAME:charset=HHHH 可以查看针对指定字符的字体优先顺序,包含这个字符的字体会优先。如果不加 -s 就是看指定的模式会匹配上的字体了。其中 HHHH 是该字符的 Unicode 码点之十六进制值。如 fc-match :charset=7684 查看默认字体下「的」字会用什么字体,而 fc-match serif:charset=7684:lang=ja 查看在语种为日文的时候,使用 serif 字族名会使用哪个字体来显示「的」字。使用 fc-list :charset=HHHH 则是查看包含该字符的所有字体。

参考资料

10
21
2016
3

在 Python 里设置 stdout 的编码

有时候进程的运行环境里,locale 会被设置成只支持 ASCII 字符集的(比如 LANG=C)。这时候 Python 就会把标准输出和标准错误的编码给设置成 ascii,造成输出中文时报错。

一种解决办法是设置支持 UTF-8 的 locale,但是那需要在 Python 进程启动前设置。启动之后,初始化过了,再设置 locale 也不会重新初始化那些对象。

另一种办法是往 sys.stdout.buffer 这种地方直接写 bytes。理论上完全没问题,但是写起程序来好累……

我就去找了一下怎么优雅地弄一个新的 sys.stdout 出来。Python 3 的 I/O 不再使用 C 标准库的 I/O 函数,而是直接使用 OS 提供的接口。封装位于 io 这个模块里边,有带缓冲的,不带缓冲的,二进制的,文本的。

研究了一下文档可知,sys.stdout 是个 io.TextIOWrapper,有个 buffer 属性,里边是个 io.BufferedWriter。我们用它造一个新的 io.TextIOWrapper,指定编码为 UTF-8:

import sys
import io

def setup_io():
  sys.stdout = sys.__stdout__ = io.TextIOWrapper(
    sys.stdout.detach(), encoding='utf-8', line_buffering=True)
  sys.stderr = sys.__stderr__ = io.TextIOWrapper(
    sys.stderr.detach(), encoding='utf-8', line_buffering=True)

这里除了可以设置编码之外,也可以设置错误处理和缓冲。所以这个技巧也可以用来容忍编码错误、改变标准输出的缓冲(不需要在启动的时候加 -u 了)。

其实这样子还是不够彻底。Python 在很多地方都有用到默认编码。比如 subprocess,指定 universal_newlines=True 时 Python 会自动给标准输入、输出、错误编解码,但是呢,在 Python 3.6 之前,这里的编码是不能手动指定的。还有参数的编码,也是不能指定的(不过可以传 bytes 过去)。

所以,还是想办法去设置合适的 locale 更靠谱……

Category: python | Tags: Python 中文支持 linux
5
17
2015
23

Linux 下在 Minecraft 里输入中文

Linux 下各种奇怪的地方总是会遇到输入启用不了的情况,比如 Sublime Text 就需要打补丁版。Teewords 以前能好好地使用输入法的,不知从什么时候起,启用输入法输入时,直接从键盘输入的编码和输入法提交的字符串都会被 teewords 接收并显示(teewords 版本号 0.6.3)。最近换了新本子,跑起 Minecraft 终于不那么卡了,所以也研究了一下怎么在 mc 里输入中文。

喵窝 wiki 里给出了一个脚本,是通过外部程序输入中文,然后粘贴到 mc 里来达到输入中文的效果的。然而粘贴功能在我的 mc 里是无效的。不过照着这思路,改进一下发现也能用。

首先,需要 xdotools。其次,需要一个输入文本的程序。我使用的是 zentiy,当然还有 kdialog、gdialog 之类的也可以用。最后,需要设置快捷键。

我的脚本如下:

#!/bin/bash -e

chars=$(zenity --title 中文输入 --text 中文输入 --width 500 --entry 2>/dev/null)
sleep 0.1
xdotool key --delay 150 Escape t
sleep 0.2
xdotool type --delay 150 "$chars"
xdotool key Return

原理很简单,在这个脚本被调用时,弹出一个对话框让用户输入文字。对话框关闭后,焦点应该回到 mc。发送 Escape 键「回到游戏」,然后发送「t」开启聊天。然后把文字发送过去并按回车。

很神奇,原来可以直接向它发送中文字符。不过那些延迟是需要的,不然会接收不完整。所以使用效果就是,对话框关闭之后,可以看到程序在往 mc 里一个个地输入文本并发送~

至于绑定快捷键,作为 Awesome 用户,可以做到只在 mc 的窗口绑定。定制性比较差的窗口管理器/桌面环境可能只能全局绑定了,会占用掉一个全局快捷键。

相关代码如下:

    elseif c.class and c.class:match('^Minecraft ') then
        local keys = c:keys()
        local mykey = awful.key({'Control'}, 't', function(c)
            awful.util.spawn('zhinput')
        end)
        keys = awful.util.table.join(keys, mykey)
        c:keys(keys)
    elseif c.name == '中文输入' then
        awful.util.spawn_with_shell('sleep 0.05 && fcitx-remote -T', false)

顺便在弹出的对话框里把输入法切换到了中文模式~(完整的配置文件在这里


2019年01月02日更新:Minecraft 已经可以直接输入中文了(虽然有些类似于 teeworlds / DDNet 的 bug)。

Category: Linux | Tags: 中文支持 minecraft
9
16
2013
4

记一次 Wine 乱码

今天发现 QQWry 升级程序的一些按钮和对话框中文显示乱码,但是在全新建立的 Wine prefix 中正常。经与新 prefix 的比对,将以下注册表信息写入system.reg后即恢复正常:

[System\\CurrentControlSet\\Control\\FontAssoc\\Associated Charset]
"ANSI(00)"="YES"
"OEM(FF)"="YES"
"SYMBOL(02)"="NO"
Category: 中文支持 | Tags: 乱码 wine 中文支持
8
5
2013
10

rst_tables 改进版

rst_tables 是一个用来创建和格式化 rst(reStructuredText)格式文档中的表格用的。此文档里的表格得画成表格的样子,囧死了……比如(网页上显示的可能没对齐,在 Vim 里应该很齐的www):

+----------+----------+-----------------------------------------+
| 格式名称 | 使用频率 | 使用场景                                |
+==========+==========+=========================================+
| markdown | 非常高   | 简单的文字,如博客、简单文档            |
+----------+----------+-----------------------------------------+
| rst      | 较低     | 较复杂的文档,如包含表格或者描述性列表。|
|          |          | 以及 Python 库的文档。                  |
+----------+----------+-----------------------------------------+

所以,作为编辑器之神的 Vim,当然会有更方便的创建这种非人道的表格的办法啦。(其实我是看到 Vimwiki 的表格挺不错的 n(≧▽≦)n

略作搜索,找到了 rst_tables。它是这样子写的(墙外视频演示):

格式名称  使用频率  使用场景
markdown  非常高  简单的文字,如博客、简单文档
rst  较低  较复杂的文档,如包含表格或者描述性列表。以及 Python 库的文档。

每行的单元格间空两格,然后光标放在光标上,按\\c(其实是<leader><leader>ccreate),就创建好啦。如果后期又修改了,按\\fformat)就可以重新格式化啦。

rst 的表格里可以写多行文字,就如前边所示那样。修改表格第一行那些减号的数量后再按\\f,可以调整栏宽。

好啦,rst_tables 本身的介绍至此结束。下面讲讲我作出的改进:

  1. 去除对 vim_bridges Python 库的依赖。根本没大量使用的东西,也没省下几行代码,何必用呢。
  2. 正确对齐和排版中文。官方版考虑了中文字符的宽度,但是用 Python 的 textwrap 来排版,造成各种混乱。我给改成用 Vim 原生排版功能排了。
  3. 使用 Python 3 接口,免得非 UTF-8 'encoding' 时出问题。同时使用了 Vim 7.4 新添加的 Python 接口。
  4. 如果没有 Python 支持,不要载入。
  5. 键映射局部于缓冲区。
  6. 放到 plugin 目录下,因为那些 Python 函数定义不需要载入多次。

安装很简单,把这个文件(使用「Raw」链接来下载)扔到 ~/.vim/plugin 下即可。

Category: Vim | Tags: vim python 中文支持
7
30
2013
20

对比不同字体中的同一字符

有人在 openSUSE 中文论坛询问他的输入法打出的「妩媚」的「妩」字为什么显示成「女」+「元」。怀疑是字体的问题,于是空闲时用好友写的 python-fontconfig 配合 Pillow (PIL 的一个 fork)写了个脚本,使用系统上所有包含这个「妩」字的字体来显示这个字,看看到底是哪些字体有问题。

(更新后的)脚本如下:

Google Chrome / Chromium 用户请注意:如果复制得到的代码中含有不间断空格(0xa0),请手动替换下。

#!/usr/bin/env python3
# vim:fileencoding=utf-8

from PIL import Image, ImageDraw, ImageFont
import fontconfig

ch = '妩'
def get_fonts():
  ret = []
  for f in fontconfig.query():
    f = fontconfig.FcFont(f)
    if f.has_char(ch):
      ret.append((f.file, f.bestname))
  return ret

w, h = 800, 20000
image = Image.new('RGB', (w, h), 'white')
draw = ImageDraw.Draw(image)
pos = 0
w = 0
strs = ch
for fontfile, fontname in get_fonts():
  font = ImageFont.truetype(fontfile, 24)
  s = '%s: %s' % (fontname, strs)
  font_width, font_height = font.getsize(s)
  w = max((font_width, w))
  draw.text((10, pos), s, font=font, fill='black')
  pos += font_height
  h = pos

image = image.crop((0, 0, w+10, h))
image.save('fonts.png')

寻找字体,然后渲染到当前目录下的fonts.png文件中。寻找字体的过程挺花时间的,要耐心等待。最后结果如下:

我这里,文泉驿微米黑、方正魏碑、某个 Droid Sans Fallback 字体中「妩」字的字形不对。(我这里有三个字体文件都叫「Droid Sans Fallback」……)>

10
7
2012
11

Fcitx Lua 插件:国际音标输入

GTK 右键的输入法菜单中有一项「IPA」,用于输入国际音标的。不过为了输入几个国际音标去够鼠标点菜单太麻烦了。既然是输入,交给我最爱的 fcitx 输入法去处理就好了嘛。

GTK 的国际音标输入很简单,每一两个字符对应一个音标字符。不过,因为通常是连续输入好几个国际音标,因此简单地使用 fcitx 的「快速输入」模块的话,每输入一个得打一次前缀,太痛苦了。于是我用 fcitx 的 Lua 模块来做。

要注意的是,fcitx 的 Lua 支持默认没有开启,编译时需要在 cmake 参数中加上-DENABLE_LUA=On。Arch 用户可以从 archlinuxcn 源安装 fcitx-lilydjwg-git。其它发行版可能有单独的fcitx-lua包,也可能需要自行编译。

安装方法很简单,把ipa.lua放到~/.config/fcitx/lua目录下即可。然后按Ctrl-5(默认)重新加载 fcitx 配置即可。

使用方法是,使用预定义的快速输入快捷键(默认是;)进入「快速输入」模式后,输入命令前缀yb,然后按 GTK 那个 IPA 输入法的方式输入即可,按空格提交输入。不知道对应关系的可查看脚本源码。

[ˌðæts ˈɔːl ˈθænks].

Category: Linux | Tags: fcitx Lua 中文支持
10
7
2012
0

拼音声调数字转字符

Suǒwèide 「pīnyīn shēngdiào shùzì zhuǎn zìfú」 jiùshì bǎ 「pīnyīn+shùzìdiàbiǎo de shēngdiào」 zhuǎnchén Unicode zìfú biǎoshì.

所谓的「拼音声调数字转字符」就是把「拼音+数字表示的声调」转成 Unicode 字符表示,为了是做成 fcitx 的拼音输入插件,以方便输入上段的内容。

算法是参考别人的,把所有带声调的音节后缀穷举出来再转换,简单暴力好用。我改写的 Python 3 版在 github/winterpy 上,支持大写和「ü」。

为了在 fcitx 中使用,我又改写了一 Lua 版本,代码如下:

#!/usr/bin/env lua

-- http://www.robertyu.com/wikiperdido/Pinyin%20Parser%20for%20MoinMoin

-- definitions
-- For the pinyin tone rules (which vowel?), see
-- http://www.pinyin.info/rules/where.html

local strsub = string.gsub
local _strupper = string.upper

-- map (final) constanant+tone to tone+constanant
mapConstTone2ToneConst = {
  n1 = '1n',
  n2 = '2n',
  n3 = '3n',
  n4 = '4n',
  ng1 = '1ng',
  ng2 = '2ng',
  ng3 = '3ng',
  ng4 = '4ng',
  r1 = '1r',
  r2 = '2r',
  r3 = '3r',
  r4 = '4r',
}

-- map vowel+vowel+tone to vowel+tone+vowel
mapVowelVowelTone2VowelToneVowel = {
  ai1 = 'a1i',
  ai2 = 'a2i',
  ai3 = 'a3i',
  ai4 = 'a4i',
  ao1 = 'a1o',
  ao2 = 'a2o',
  ao3 = 'a3o',
  ao4 = 'a4o',
  ei1 = 'e1i',
  ei2 = 'e2i',
  ei3 = 'e3i',
  ei4 = 'e4i',
  ou1 = 'o1u',
  ou2 = 'o2u',
  ou3 = 'o3u',
  ou4 = 'o4u',
}

-- map vowel-number combination to unicode
mapVowelTone2Unicode = {
  a1 = 'ā',
  a2 = 'á',
  a3 = 'ǎ',
  a4 = 'à',
  e1 = 'ē',
  e2 = 'é',
  e3 = 'ě',
  e4 = 'è',
  i1 = 'ī',
  i2 = 'í',
  i3 = 'ǐ',
  i4 = 'ì',
  o1 = 'ō',
  o2 = 'ó',
  o3 = 'ǒ',
  o4 = 'ò',
  u1 = 'ū',
  u2 = 'ú',
  u3 = 'ǔ',
  u4 = 'ù',
  v1 = 'ǜ',
  v2 = 'ǘ',
  v3 = 'ǚ',
  v4 = 'ǜ',
}

function strupper(c)
  local specials = {
    ['ā'] = 'Ā',
    ['á'] = 'Á',
    ['ǎ'] = 'Ǎ',
    ['à'] = 'À',
    ['ē'] = 'Ē',
    ['é'] = 'É',
    ['ě'] = 'Ě',
    ['è'] = 'È',
    ['ī'] = 'Ī',
    ['í'] = 'Í',
    ['ǐ'] = 'Ǐ',
    ['ì'] = 'Ì',
    ['ō'] = 'Ō',
    ['ó'] = 'Ó',
    ['ǒ'] = 'Ǒ',
    ['ò'] = 'Ò',
    ['ū'] = 'Ū',
    ['ú'] = 'Ú',
    ['ǔ'] = 'Ǔ',
    ['ù'] = 'Ù',
    ['ǜ'] = 'Ǜ',
    ['ǘ'] = 'Ǘ',
    ['ǚ'] = 'Ǚ',
    ['ǜ'] = 'Ǜ',
  }
  if specials[c] then
    return specials[c]
  else
    return _strupper(c)
  end
end

function ConvertPinyinToneNumbers(lineIn)
  local lineOut = lineIn

  -- first transform
  for x, y in pairs(mapConstTone2ToneConst) do
    lineOut = strsub(strsub(lineOut, x, y), strupper(x), strupper(y))
  end

  -- second transform
  for x, y in pairs(mapVowelVowelTone2VowelToneVowel) do
    lineOut = strsub(strsub(lineOut, x, y), strupper(x), strupper(y))
  end

  -- third transform
  for x, y in pairs(mapVowelTone2Unicode) do
    lineOut = strsub(strsub(lineOut, x, y), strupper(x), strupper(y))
  end

  return strsub(strsub(lineOut, 'v', 'ü'), 'V', 'Ü')
end

local function main()
  local lineOut
  for lineIn in io.stdin:lines() do
    lineOut = ConvertPinyinToneNumbers(lineIn)
    print(lineOut)
  end
end

main()

很可惜的是,fcitx 的 Lua 模块目前不支持屏蔽数字键选字,所以暂无法在 fcitx 中使用。

Category: 编程 | Tags: Lua Python 中文支持 拼音
9
18
2012
12

截短 UTF-8 字符串

用于显示时,经常会遇到显示的文本太长需要截短的情况。如果是如 ASCII 这样的定长编码,截短到指定长度自然不成问题。可如果源字符串是 UTF-8 编码的呢?ANSI C 里只管字节不管编码,所以如果想只用 ANSI C 提供的功能的话,就只能自己写了。因为需求仅仅是截短字符串而已,也不要求多么精确,所以没有去做编解码,只是丢弃按字节截短后的字符串最后的无效编码而已。而且目标语种是 Lua,也不方便搞位操作。

维基百科可知,UTF-8 多字节字符第一字节的最高两位为11,而其它字节的最高两位均为10。所以就把后面那些10xxxxxx连同最开始的11xxxxxx去掉好了。这样会多截掉一个多字节字符,但无所谓了。

function truncateUTF8String(s, n)
  local r = string.sub(s, 1, n)
  local last = string.byte(r, n)
  if not last then return r end
  while last >= 128 and last <= 192 do
    n = n - 1
    r = string.sub(r, 1, n)
    last = string.byte(r, n)
  end
  if last >= 128 then
    r = string.sub(r, 1, n-1)
  end
  return r
end

2012年9月27日更新:感谢Fermat618提供的思路,更新了更简洁、准确的代码如下:

function truncateUTF8String(s, n)
  local dropping = string.byte(s, n+1)
  if not dropping then return s end
  if dropping >= 128 and dropping < 192 then
    return truncateUTF8String(s, n-1)
  end
  return string.sub(s, 1, n)
end
Category: 编程 | Tags: 乱码 Lua 中文支持

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com