5
2023
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)选项卡来看 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
则是查看包含该字符的所有字体。
参考资料
21
2016
在 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 更靠谱……
17
2015
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)。
16
2013
记一次 Wine 乱码
今天发现 QQWry 升级程序的一些按钮和对话框中文显示乱码,但是在全新建立的 Wine prefix 中正常。经与新 prefix 的比对,将以下注册表信息写入system.reg
后即恢复正常:
[System\\CurrentControlSet\\Control\\FontAssoc\\Associated Charset] "ANSI(00)"="YES" "OEM(FF)"="YES" "SYMBOL(02)"="NO"
5
2013
rst_tables 改进版
rst_tables 是一个用来创建和格式化 rst(reStructuredText)格式文档中的表格用的。此文档里的表格得画成表格的样子,囧死了……比如(网页上显示的可能没对齐,在 Vim 里应该很齐的www):
+----------+----------+-----------------------------------------+ | 格式名称 | 使用频率 | 使用场景 | +==========+==========+=========================================+ | markdown | 非常高 | 简单的文字,如博客、简单文档 | +----------+----------+-----------------------------------------+ | rst | 较低 | 较复杂的文档,如包含表格或者描述性列表。| | | | 以及 Python 库的文档。 | +----------+----------+-----------------------------------------+
所以,作为编辑器之神的 Vim,当然会有更方便的创建这种非人道的表格的办法啦。(其实我是看到 Vimwiki 的表格挺不错的 n(≧▽≦)n
略作搜索,找到了 rst_tables。它是这样子写的(墙外视频演示):
格式名称 使用频率 使用场景 markdown 非常高 简单的文字,如博客、简单文档 rst 较低 较复杂的文档,如包含表格或者描述性列表。以及 Python 库的文档。
每行的单元格间空两格,然后光标放在光标上,按\\c(其实是<leader><leader>c,create),就创建好啦。如果后期又修改了,按\\f(format)就可以重新格式化啦。
rst 的表格里可以写多行文字,就如前边所示那样。修改表格第一行那些减号的数量后再按\\f,可以调整栏宽。
好啦,rst_tables 本身的介绍至此结束。下面讲讲我作出的改进:
- 去除对 vim_bridges Python 库的依赖。根本没大量使用的东西,也没省下几行代码,何必用呢。
- 正确对齐和排版中文。官方版考虑了中文字符的宽度,但是用 Python 的 textwrap 来排版,造成各种混乱。我给改成用 Vim 原生排版功能排了。
- 使用 Python 3 接口,免得非 UTF-8 'encoding' 时出问题。同时使用了 Vim 7.4 新添加的 Python 接口。
- 如果没有 Python 支持,不要载入。
- 键映射局部于缓冲区。
-
放到
plugin
目录下,因为那些 Python 函数定义不需要载入多次。
30
2013
对比不同字体中的同一字符
有人在 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」……)>
7
2012
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].
7
2012
拼音声调数字转字符
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 中使用。
18
2012
截短 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