算法与数据结构 / regex · Unicode 字符模型与一个自研引擎 / 给汉字注音:读音不在码点里 待审核 21 / 36
字符的真面目 · 读音不在码点里

给汉字注音:读音不在码点里

Han 统一 讲的是一个码点不带字形,长什么样交给字体。本页讲它的孪生事实:一个码点也不带读音。'重'.codePointAt(0) 只得到编号 U+91CD,这个数字里既没有笔画,也没有它读 zhòng 还是 chóng。读音与字形一样记在码点之外:Unihan 的 kMandarinkHanyuPinyin 字段、CLDR 的 Han-Latin 转写表。而且读音比字形更麻烦,同一个码点常有多个读音,选哪个取决于词。

1 · 给一段文本注音

HTML 原生的 <ruby> 负责排版:基字写在 <ruby> 里,注音放进 <rt>,浏览器把注音排在基字上方,不支持时退化成括注。算出每个字读什么则是库的事,本页用 pinyin-pro。两件事分得很干净——排版不依赖任何库,注音结果也不关心怎么渲染。

图 1-1 · 一段中文的逐字注音,多音字标底色并在悬浮提示里给出全部候选与最终选择。可改文本、切换声调写法或点预设。

2 · 读音取决于词

单看一个码点只知道它可能读哪几个音,选不出该用哪个,这与 繁简转换 里单字表给 列出两个候选却停在那儿是同一回事。定音的是上下文: 在「重要」里读 zhòng、在「重复」里读 chóng 在「银行」里读 háng、在「行走」里读 xíng。把 无脑映射到第一个候选,就会把「重复」读成 zhòng fù

pinyin-pro 的做法是内置词典加最大匹配分词,先认出「重复」「重要」这样的词,再为其中的字定音。所以注音与繁简转换属于同一类问题:查词典级、依赖分词,不是逐码点的无歧义查表。

图 2-1 · 八个多音字的全部候选读音,以及它们在几个例词里被选定的音,被选中的候选高亮。

3 · 音节的三段结构

拼音音节有固定结构:声母(开头的辅音)、韵母(余下的元音收尾)与声调。声母可以为空, 的音节 ài 就是零声母;声调为 0 记作轻声,不标调号。

图 3-1 · 一个词逐字拆成声母、韵母与声调三段,并统计其中的零声母音节。可换词或点预设。

4 · 注音串的归一化

声调标在韵母的主元音上,ǐǚ 都是 归一化 那页的老熟人:它们在 NFC 下多是单个预组合码点,NFD 下会拆成基础元音加组合声调记号。于是注音串的长度随归一化形态而变——'nǚ'.length 是 2,'nǚ'.normalize('NFD').length 是 4,因为 ǚU+01DA)会拆成 u 加分音符加抑扬符三个码点。比较或存储拼音串前先归一,否则同一个 会因写法不同而判不等。

图 4-1 · 一个词的拼音串逐码点展开,带调元音单独标注,并给出 NFD 下的长度变化。可改词观察哪些音节含预组合码点。

建议 · 码点之外的三层信息到这一页收齐了:字形交给字体与 lang,读音查 Unihan 与 CLDR 再靠分词定夺,繁简与异体关系查 Unihan 变体字段加词典。一句话,码点定的是「哪个抽象字」,字形、读音与词义对应统统是码点之外的数据。

5 · 参考文献

  1. Unicode. UAX #38: Unicode Han Database (Unihan). kMandarinkHanyuPinyin 等读音字段的定义与数据格式。unicode.org
  2. Unicode. CLDR: common/transforms. Han-Latin.xml 即注音转写表本身,注音的另一份权威数据。github.com
  3. MDN. <ruby>. 注音排版的原生标记:<ruby><rt><rp> 的语义与回退行为。developer.mozilla.org
  4. pinyin-pro. 文档. 本页所用的注音库:词典分词、声调写法与音节拆分接口。pinyin-pro.cn