给汉字注音:拼音不在码点里
Han 统一那页讲的是:一个码点不带字形,长什么样交给字体。
本页讲它的孪生事实:一个码点也不带读音。'重'.codePointAt(0) 只得到一个编号 U+91CD——
这个数字里既没有「重」的笔画,也没有它读 zhòng 还是 chóng。
读音和字形一样,记在码点之外的数据表里:Unihan 的 kMandarin / kHanyuPinyin 字段、
CLDR 的 Han-Latin 转写表。
而且读音比字形更麻烦:同一个码点常有多个读音(多音字),选哪个取决于词——和
繁简转换那节的「一对多」一样,得靠词典 + 分词,而非字符级映射。
本页用 pinyin-pro 把注音跑起来,并按标准 <ruby> 标记法渲染。
给一段文本注音
输入中文,下面用 <ruby> 给每个汉字加注音(声调可切三种写法)。
非汉字(字母、数字、标点)原样保留。有多个候选读音的多音字会标底色——它们的读音是
pinyin-pro 按词替你选定的,把鼠标悬上去能看到候选与最终选择:
ruby)<ruby>重<rt>chóng</rt><rt>…——基字写在 <ruby> 里,注音放进
<rt>(ruby text),浏览器自动把注音排在基字上方、不支持时退化成括注。这是 HTML 原生能力,
不依赖任何库;pinyin-pro 只负责算出每个字读什么,排版交给 <ruby>。
读音取决于词:多音字的「一对多」
单看一个码点,你只知道它可能读哪几个音(候选),却选不出该用哪个——和
繁简那张手写单字表给 发 列出 發 / 髮 却停在这一样。
真正定音的是上下文(词)。下表每行一个多音字:左列是它在 Unihan 里登记的全部候选读音,
右列是几个例词,看同一个码点在不同词里被选成不同的音:
| 字 | 候选读音 单字 · 全部 |
在词里(按词选定) |
|---|
zhòng,就会把「重复」读成 zhòng fù。
所以注音和繁简转换是同一类问题——查词典级、依赖分词,不是逐码点的无歧义查表。
pinyin-pro 内置词典 + 最大匹配分词,先认出「重复 / 重要」这样的词,再为其中的字定音。
一个音节拆三段:声母 / 韵母 / 声调
拼音音节有固定结构:声母(initial,开头辅音)+ 韵母(final,余下的元音收尾)+ 声调(tone)。
pinyin-pro 能把每个字拆开。输入一个词看逐字拆解:
声调符号是组合记号:注音串的归一化
声调标在韵母的主元音上(nǐ 的 ǐ、nǚ 的 ǚ)。这些带调元音是
归一化那页的老熟人:它们在 NFC 下多是单个预组合码点,
在 NFD 下会拆成「基础元音 + 组合声调记号」。于是注音串的 .length 会随归一化形态而变。
下面对输入词的拼音串逐码点拆开看:
'nǚ' 看着两个字符,.length 也确实是 2(n + 预组合的 ǚ = U+01DA);
但 'nǚ'.normalize('NFD') 会把 ǚ 拆成 u + 组合分音符 + 组合抑扬符,长度变 4。
比较 / 存储拼音串前先 normalize,否则「同一个 nǚ」可能因写法不同而 !==——这正是归一化页讲的坑。
其一 字形 —— 交给字体 +
lang(Han 统一);
其二 读音 —— 查 Unihan
kMandarin / CLDR 转写表,多音字再靠分词(本页);
其三 繁简 / 异体关系 —— 查 Unihan 变体字段 + 词典(繁简那节)。
一句话:码点定的是「哪个抽象字」,字形、读音、词义对应统统是码点之外的数据。