在印度、尼泊尔、斯里兰卡开店:小票上的字为什么老是印错

这份资料里其他几种文字,说到底都能靠"给打印机换一套字库"解决。俄文换个编码就行, 中日韩文字每个字占两格、但一个字就是一个图形,阿拉伯文麻烦一点,也还是一个字母一个字母地变形。

南亚这一大类文字不一样:换字库这条路,在它这里走不通。

天城文(印地语、马拉地语、尼泊尔语用)、孟加拉文、泰米尔文、泰卢固文、 卡纳达文、马拉雅拉姆文、古吉拉特文、果鲁穆奇文、奥里亚文、僧伽罗文, 这些文字有三个共同点,每一个都能让小票印错:

结论先放这里:这类文字必须由软件先排好版、再当成图片发给打印机。 如果供应商跟你说"我们加个编码就支持了",他要么没做过,要么在糊弄你。

本文核对状态

文字编码的部分是照 Unicode 标准数据库(UCD 13.0.0)一个字符一个字符核对过的, 包括每个元音符号和 virama 的属性、几个字母的分解形式, 字符数和字节数是实际数出来的,不是估的。这部分比较硬。

打印机怎么表现、当地小票习惯、税务规定,这些全都没核实。

待核实清单 —— TODO: verify

  1. 你要买的那台打印机,自带字库里到底有没有这类文字。大概率是没有, 但要自己确认,别信参数表上写的"支持多国语言"。见第五节。
  2. 打印机印图片的能力:最大宽度多少点,能不能印满整张纸宽。 一旦接受第五节的结论,这条就变成最关键的一条。
  3. 你实际要用的那台收银机(不是供应商演示用的电脑)上有没有对应字体。 这条是最常见的翻车点。 便宜的安卓收银机经常缺字体。
  4. 当地小票上金额怎么分位、小数点用什么符号、货币符号放哪。
  5. 当地金额是"三位一撇"还是印度式的"二二三"分位。 见第七节。
  6. 小票上是印卢比符号、印 INR、还是印英文缩写。见第六节。
  7. 当地法律对小票用什么语言、什么文字有没有强制要求。
  8. 税务这块,这份资料一个字都没有。 印度、尼泊尔、斯里兰卡、孟加拉国, 这四个国家一个国家篇都没写过,GST 怎么算、发票必须印什么、 有没有电子发票强制要求,全部空白,必须另外找当地会计。见第十节。
  9. 供应商给你的商品资料里有没有本地数字(不是 09),导进系统时会不会出错。

一、为什么"换个字库"解决不了

打印机自带字库的工作方式是:一个字节对应一个图形。 俄文能这么干,因为俄文每个字母都是独立的一个图形,前后有什么字母都不影响它长什么样。

但这类文字里,一个辅音印成什么样,取决于它后面跟着什么。 两个辅音连在一起会变成一个全新的图形,这个新图形在字库里没有自己的编号, 根本没法用"一个字节对一个图形"的方式去调用它。

所以就算你买到一台号称带天城文字库的打印机, 它也只能印单个的字母,印不出连在一起的那些字——而印地语里连字非常常见。

能走通的做法只有一条:软件在电脑/收银机这一端把字排好、渲染成图片,再把图片发给打印机。

这件事要在项目一开始就定下来,不能等验收的时候才发现。 因为打印机那时候已经买了,而且这条路对打印机的要求和"能印文字"完全是两回事—— 要看它印图片的能力。


二、存的顺序和印的顺序不是一个顺序

举个最典型的:天城文有个元音符号,编号 U+093F。 它在数据里存在辅音后面,印出来却在辅音前面

照 Unicode 标准数据库核对,这个字符的属性是:

属性 对你意味着什么
类别 Mc 它自己要占一格宽度,不是飘在字母上方的小符号
组合类 0 做任何"规范化"处理都不会把它挪位置

第二条特别重要:这个位置调换不是记录在数据里的, 是排版引擎在最后印出来那一刻才做的。所以你在数据库里看到的顺序, 和纸上印出来的顺序,永远对不上。

同样行为的还有:

文字 编号
天城文 U+093F
孟加拉文 U+09BF
泰米尔文 U+0BBF
僧伽罗文 U+0DD9

对你的实际影响:光看字符串开头,你没法知道纸上会先印出什么。 任何"取前 20 个字"、"居中"、"右对齐"的处理,处理的都是一个和纸面对不上的顺序。

还有更麻烦的:一个元音拆成两半

孟加拉文的 U+09CB 这个元音,拆开来是 U+09C7U+09BE。 前一半印在辅音左边,后一半印在辅音右边

也就是说,一个元音变成了两个图形,一左一右把字母夹在中间。 纸上没有任何一个位置"就是"这个元音。


三、合体字:三个字符印成一个字

辅音 + U+094D + 辅音,这不是三个字母,是一个字。 印出来是一个融合的新图形,跟原来那两个辅音长得都不一样。

这类"粘合剂"字符每种文字都有一个,属性完全一致(组合类都是 9):

文字 编号 文字 编号
天城文 U+094D 果鲁穆奇文 U+0A4D
孟加拉文 U+09CD 古吉拉特文 U+0ACD
泰米尔文 U+0BCD 奥里亚文 U+0B4D
泰卢固文 U+0C4D 僧伽罗文 U+0DCA
卡纳达文 U+0CCD 马拉雅拉姆文 U+0D4D

实际数出来的结果:

写法 字符数 字节数 纸上印出来 当地人管它叫
天城文一个合体字 3 9 1 个图形 一个字
天城文一个复杂合体字 6 18 1 个图形 一个字
孟加拉文一个音节 2 6 分左右两边 一个字
泰米尔文一个音节 2 6 2 个,元音在前 一个字

同一段字,四种数法四个数字。 数据库按字节算长度、界面按字符算、 小票按格数算、店员按"几个字"算,四边都对不上,而且每一边自己看都是对的。

这会造成什么实际损失

商品名被截断的时候,不是变短,是变成另一个词。

比如你的系统规定商品名最多存 20 个字符,超了就截断。 如果正好截在辅音和"粘合剂"中间,印出来的不是半个词, 而是一个不同的词,或者一个空心圆圈(系统在告诉你"这里断了")。

正确做法是按"用户感知的一个字"(术语叫字素簇)来截, 不能按字符截,更不能按字节截。这一条要在数据库那一层就做对,光在界面上改没用。

还有两个看不见的字符

有两个字符(U+200CU+200D)专门用来控制"这两个辅音要不要粘在一起"。 它们本身印出来是空的、屏幕上也看不见

麻烦在于:供应商发给你的商品资料里可能带着它们, 而很多系统在导入时会"清理不可见字符",一清理,印出来的词就变了。 你在数据里完全看不出区别。


四、从国内带打印机的,看这一节

先说结论:这类文字,从国内带的热敏打印机基本上都印不了文字,只能印图片。

要问供应商的是这几件事:

  1. 这台机器印图片的最大宽度是多少点? 能不能印满 58mm / 80mm 的整个纸宽? 印不满的话,长一点的商品名就会被切掉右边。
  2. 印图片比印文字慢多少? 一张小票全是图片,出票速度会下降, 高峰期一台机器要出几百张的话,这个要提前试。
  3. 收银机(不是演示用的笔记本)上装了对应字体没有? 这是最常翻车的一条。供应商在自己电脑上演示,字体齐全,一切正常; 装到店里那台便宜安卓机上,字体缺一半。

第 3 条的坑在于它的表现形式:字体完全缺失时会印出一排空心方框,这个一眼就能看出来; 但字体只缺一部分时,整行字看着都对,只有其中两三个字是错的。 不懂这门文字的人根本发现不了。


五、卢比符号印不出来

有三个不同的"卢比"符号,不能混用:

编号 说明
U+20B9 印度卢比现在用的那个符号
U+20A8 老式的卢比符号
U+0BF9 泰米尔文里的卢比符号

U+20B9 是比较晚才加进 Unicode 的,老字体和几乎所有热敏打印机字库里都没有。 不过既然第四节已经确定这类文字只能印图片,问题就从"打印机有没有"变成了 "你渲染用的那个字体里有没有"。

TODO: verify —— 当地小票上到底是印符号、印 INR、还是印英文缩写,得问当地人。 另外提醒一句:尼泊尔、斯里兰卡、巴基斯坦、毛里求斯的货币也叫"卢比", 但符号和习惯各不相同,名字一样不等于符号一样


六、金额分位:印度不是三位一撇

印度的数字分位方式和国际通行的不一样,是二二三,不是三位一组:

金额 国际写法 印度写法
十万 100,000 1,00,000
一百万 1,000,000 10,00,000
一千二百多万 12,345,678 1,23,45,678

这个坑很阴:几乎所有系统默认都是三位一撇,印出来的金额看着很正常,但当地人一眼就知道不对。 而且金额本身的数值是对的,只是分位错了,所以对不出账、也不会报错。

TODO: verify —— 要分别确认:当地小票是不是真用这种分位?税额和合计要不要也这么分? 注意分位习惯和用什么文字是两件事,用天城文的地方不一定用印度式分位, 税务局要求的格式也可能和店里的习惯不一样。

另外,商品资料里可能混进本地数字(不是 09 那十个)。 这些字符搜不出来也算不了数,导入的时候要处理掉。


七、泰米尔语能过,不代表印地语能过

这一大类文字内部差别很大,"支持南亚文字"根本不是一个功能。

供应商说"我们支持印度语言"的时候,一定要问清楚具体是哪几种文字, 并且要求用天城文做演示。


八、语言怎么配

大概率你会遇到这个组合:后台你自己和会计用中文或英文,小票必须用当地文字。

这跟俄语区、阿拉伯语区的情况一样——两头不是同一种语言。 要提前想清楚:


九、税务:这份资料完全没有

必须明确说清楚:印度、尼泊尔、斯里兰卡、孟加拉国,这四个国家的国家篇一篇都没写。

上面所有内容讲的都是"字怎么印出来不出错", 至于 GST 税率多少、小票上必须印哪些字段、有没有强制电子发票、 要不要在税务局登记收银设备——这些一个都没查过,一个字都别信这份资料

这部分必须找当地会计或者税务代理确认。这也是为什么本文所有和税有关的地方都留着 TODO: verify


十、验收清单

每一条都必须找一个真正认识这门文字的人来看,不能自己看。

原因在于:别的文字印错了是乱码,一眼就知道错了; 这类文字印错了,看起来还是一段正常的文字,只是意思变了。 不认识这门文字的人,看不出任何异常。

# 测什么 什么算通过
1 印一个带前置元音符号的词 元音符号出现在辅音左边
2 印一个孟加拉文带 U+09CB 的词 元音分成两半,一边一个
3 印一个两辅音合体字 一个融合的图形,不是两个字母中间夹个符号
4 印一个三辅音合体字 一个图形,没有空心圆圈
5 把商品名截断后打印 整个字一起消失,不出现空心圆圈、不变成另一个词
6 同一个词用两种存法各存一条,搜其中一种 两条都能搜出来
7 印一整行满宽的当地文字 印到纸边,右边没被切掉
8 印一个带卢比符号的价格 符号印得出来,不是方框也不是空白
9 印一个六位数和一个八位数金额 分位方式符合第六节确认的当地习惯
10 印一个带隐藏控制字符的商品名 该不粘连的地方确实没粘连
11 导入一份带本地数字的供应商资料 数字被转换或被标记出来,没有悄悄存进去
12 印一行当地文字加英文编号混排 编号从左到右读,没有跑位
13 以上全部在店里那台真机上再做一遍 结果和演示时完全一致

第 13 条最容易被跳过,也最容易出问题。演示用的电脑和店里的收银机不是一台机器。


十一、挑系统时该问清楚的问题

可以直接拿去问供应商:

  1. 你们这套系统印天城文小票,是靠打印机字库还是靠渲染成图片? —— 回答"靠字库"或者"换个编码就行"的,基本可以判断没做过。
  2. 能不能现在就用天城文(不是泰米尔文)打一张样票给我看? —— 要求当场打,别接受截图。
  3. 样票能不能用你们实际会卖给我的那台收银机打? —— 不是用笔记本电脑打。这条最关键。
  4. 商品名超长被截断时,你们按什么单位截? —— 应该回答"按用户看到的一个字",回答"按字符"或"按字节"的有问题。
  5. 导入商品资料时,你们会不会清理不可见字符? —— 会的话,要确认不会把控制粘连的那两个字符也清掉。
  6. 金额能不能按印度式二二三分位?在哪里设置?
  7. 收银机上装了哪些字体?能不能列出来? —— 要具体的字体名,不接受"支持多国语言"这种答复。
  8. 打印机印图片的最大宽度是多少?印一张全图片小票要多久?
  9. 税务这块你们做过印度的实施案例吗? —— 这份资料完全没覆盖税务, 你必须另外找当地会计,别指望系统供应商替你把关。

由秘奥软件(MISAll)团队维护。最后更新:2026-08