Skip to content

字库与词库

要实现汉字的自动拆分,首要的工作就是把汉字表示成容易被计算机理解并处理的数据格式。

在本系统中,汉字或不成字的汉字构件统一用「原始汉字数据」这个数据类型表示:

interface 原始汉字数据 {
unicode: number;
// 字集数据
tygf: 0 | 1 | 2 | 3; // 1, 2, 3 表示通用规范汉字集一至三级字,0 表示不属于
gb2312: 0 | 1 | 2; // 1, 2 表示国标一级字和二级字,0 表示不属于
// 字形数据
glyphs: 字形数据[];
// 其他辅助数据
name: string | null; // PUA 字符的别名
gf0014_id: number | null; // 兼容 GF0014-2009 汉字部件标准
gf3001_id: number | null; // 兼容 GF3001-1999 汉字部件标准
ambiguous: boolean; // 维护标记,用于管理员标记字的分部有没有歧义,对数据的使用没有影响
}
type 字形数据 = 基本部件数据 | 衍生部件数据 | 拼接部件数据 | 复合体数据 | 全等数据;

其中,字形数据是一个列表,表示了这个字的一个或多个可能的字形表示。每个表示可能是基本部件数据 | 衍生部件数据 | 拼接部件数据 | 复合体数据 | 全等数据之一。

标签是一种系统的处理分部歧义的方式。汉字的分部方式一般来自于字源或者字形,其中字源即是按照一个字在造字时的理据来分部(比如:形声字分为形旁和声旁;会意字分为各个有独立意义的部件),而字形则是按部件之间的自然间隙来划分。如果一个字根据字源和字形都能得出相同的一种分部方式,那么这个字就没有歧义。反之,若有分部的歧义,一般是由于以下两个原因:

基本部件相当于传统意义上的独体字,凡是没有明显间隙的字形都可以算作一个部件。基本部件的表示方式是直接用 SVG 曲线在一张横坐标 0 至 100、纵坐标 0 至 100 的画布上绘制出汉字的各个笔画:

interface 矢量笔画数据​ {
feature: string;
start: [number, number];
curveList: 绘制[];
}
interface 基本部件数据 {
type: "basic_component";
tags?: string[]; // 给这个部件描述打上的标签
strokes: 矢量笔画数据​[];
}

在每个笔画中,首先声明笔画的种类,如横、竖、撇、点等,其具体的分类方式遵照 GF 2001-2001 分为 31 类。其次声明笔画的起始点位置坐标。最后是绘制笔画的 SVG 命令,由于折笔的存在,绘制命令可能有多个。

一个绘制命令由种类描述符(水平 H、竖直 V、三次曲线 C 或 Z、圆弧 A)和一系列参数构成,比如「沿水平方向绘制 10 个单位」的命令就是

const draw: 绘制 = { command: "h", parameterList: [10] };

哪种笔画应该包含哪些绘制命令,详见源代码

很多部件其实包含的笔画都长得差不多,而且常常有「单笔画衍生」的关系,例如「戊」加一笔变成「戌」、「戍」、「成」。由于这些衍生部件中的笔画已经在基本部件中出现过了,所以我们没有必要把它们再写一遍,只需要描述是取自哪个部件的哪些笔画即可。这种方法主要是受鹤形的衍生小字启发。

interface 引用笔画数据 {
feature: "reference";
index: number;
}
interface 衍生部件数据 {
type: "derived_component";
tags?: string[]; // 给这个部件描述打上的标签
source: string;
strokes: (矢量笔画数据| 引用笔画数据)[];
}

另外,在制作具体的形码方案时,衍生部件可以很方便地描述一些非成字的字根。

但是显然,汉字的数量非常多,我们不可能对所有汉字都进行如此细致的刻画。为此我们引出下一个概念:

复合体相当于传统意义上的合体字,凡是在视觉上有一定间隙的字形都可以划分成两或三个部分,如左右结构、上下结构、包围结构等。一个部分可以是一个部件,也可以是其他的复合体。只要构成复合体的各个部件有 SVG 笔画描述,那么我们就获得了关于这个复合体的描述;复合体还可以参与构造其他复合体,这样我们就可以表示任意复杂的汉字结构。

在本系统中,一个字可以有多种分部方式,每种分部方式包括四种信息:结构描述字符、各部分的名称、分部标签(可选)以及笔顺描述序列(可选)。下面依次进行详细介绍。

interface 复合体数据 {
type: "compound",
tags?: string[], // 给这个复合体描述打上的标签
operator: "" | "" | "" | "" | "" | ...,
operandList: [string, string] | [string, string, string],
order?: { index: number, strokes: number }[]
}

即 Unicode 表意文字描述字符(U+2FF0 至 U+2FFF,参见维基百科这 16 个中的任一个(但现在只用到了 12 个)。

出于一致性考虑,各个部分的顺序并不是按照笔顺来的,而是只考虑几何关系:上下(上中下)结构从上到下,左右(左中右)结构从左到右,包围结构从外到内,重叠(⿻)结构从外到内。笔顺请在下面的字段中指定。

如果整个复合体的笔顺并不是依次写完每个部分,而是有所交叉,那么需要指定一个笔顺序列,以便计算字根序。例如,「国」字的笔顺序列是第一部「囗」写 2 笔,然后写第二部「玉」,然后再写「囗」的第三笔

拼接部件的数据结构和复合体基本相同,不过在系统内是作为部件看待的。

interface 拼接部件数据 {
type: "spliced_component",
... // 其余和复合体相同
}

全等即完全等同,例如康熙部首和 CJK 基本集内的字。

interface 全等​数据 {
type: "identity",
source: string
}