中文自定义字符字形的处理

      在进行应用系统维护的过程中,因需要不同行业间交互数据,在系统和提供商纷杂的情况下,难免会出现信息处理方式不对称的尴尬。最近遇到一个比较具体的自定义冷僻字的字形问题,现处理的差不多的情况下,也该总结一下做个备忘,这里就流水账的方式记录一下解决和测试的方法。

      首先需要面对的是五花八门的中文字符编码,这里就引出几个国标文件,不一定都需要理解,大概看一下有个概念就可以了。

GB 2312-1980 信息交换用汉字编码字符集 基本集
GB 13000-2010 信息技术 通用多八位编码字符集(UCS)
GB 18030-2005 信息技术 中文编码字符集
GB13000.1字符集汉字部首归部规范
GBT 11383-1989 信息处理 信息交换用八位代码结构和编码规则

      接下来探究的是字符的内码,在不同的编码方案中,相同字符的内码是不一样的,又或者相同的字符在同一编码方案中还会存在重复编码(确实是有的)。这里为了方便演示,就从Windows自带的宋体字形文件中,新增一个自定义的字形最终用于生成PDF文档,使用到的工具在过程中就逐一引出。

      参考GB 18030-2010文档,双字节用户区都是自定义的字形,分1~3区,这一点在Windows自带的造字程序中也体现出来了。在开始菜单中搜索eudcedit.exe就可以启动造字程序。造字的结果可生成eudc文件保存在C:\Windows\Fonts目录下,也可以由注册表指定其它的位置。如下就是搜狗字库自定义字形的定义注册表,936表示的是GBK的code page。

HKEY_CURRENT_USER\EUDC\936
    SystemDefaultEUDCFont=C:\Users\entryman\AppData\LocalLow\SogouPY\EUDC\SGPYEUDC_1.TTE

      造字程序中EUDC是end-user-defined characters的缩写,也就是终端用户定义字符,直接上图。

      其中范围下拉框就可以选择用户区的1~3个分区,对应到上面提到的国标定义,图片中的汉字就是搜狗自定义的字库。字形看上去可能大家都认识,但是Unicode码的对应关系完全是看自己发挥,这也是在信息系统之间传递和显示出现差异的罪魁祸首。上图左侧的双字节编码就是Unicode的内码,系统间交互大都使用Unicode编码,虽然各自系统表示的却是不同的汉字。这里就得提提下工业标准中常用的UTF8编码方案,有一个概念需要先弄清楚,在说UCS2时指的是字符编码,表示这个字符存储和传输占用两字节的空间,而UTF8是一种对Unicode的编码,用于序列号这种表示,可以使用1~6字节来表示一个字符,首字节几个1开头,就表示共使用几个字节组合表示,这和GB 13000中描述的辅助平面的概念有些类似。下图就是UTF8编码方案表示字符的示意,x表示有效用来表示字符内容的,其余的1和0为了表示一共采用几个字节表示一个字符。可以看出UTF8最大能表示的字符码值为2的31次方,也就是十进制的2147483648,十六进制的0x80000000。

Unicode符号范围     |        UTF-8编码方式
 (十六进制)         |         (二进制)
----------------------+----------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

      接下来开始实操,首先从Windows系统自带的字体中取出宋体字体文件,位置在C:\Windows\Fonts\simsun.ttc,为了操作上的安全,先把这个文件拷贝到一个临时目录。

copy C:\Windows\Fonts\simsun.ttc D:\Workshop\Sources\Fonts\

      TTC的文件扩展名表示这个文件包含了多个TrueType字体文件,也就是TTF,接下来用工具可以看到这些信息,TTC是TureType Collection的缩写。这里引出第一个工具,Adobe Font Development Kit for OpenType (AFDKO),这家伙免费的,源代码也放在GitHub上,Adobe官网也是有介绍的,使用Python实现的功能,当前的版本依赖Python 3.6,一切从简,安装它。

pip install afdko

      安装好以后开始查看simsun.ttc文件的内容,可以执行afdko命令看更多的参数开关信息,如下所示。

(base) D:\Workshop\Sources\Fonts>otc2otf -r simsun.ttc
Input font: simsun.ttc

Font 0: SimSun.ttf
    tag     checksum    length    offset
    ----  ----------  --------  --------
    EBDT  0x72A25569   5528924       780
    EBLC  0x18CDBFEA    683648   5529704
    GDEF  0x7080707E        30   6213352
    GPOS  0x73A683B2        66   6213384
    GSUB  0xDC28F7E6       822   6213452
    MERG  0x00160001        12   6214276
    OS/2  0x50D18CD0        96   6214288
    cmap  0xBC0D63BF      1984   6214480
    cvt   0x04BA01CD       186   6216464
    fpgm  0xC564B4F6      3566   6216652
    gasp  0x00530031        20   6220220
    glyf  0x82AAE3C2  11707144   6220240
    head  0xF3329F66        54  17927384
    hhea  0x02015711        36  17927496
    hmtx  0x719345FD    101740  17927532
    loca  0x3A4A5F22    115176  18029272
    maxp  0x735A04D0        32  18144448
    meta  0x8C0585B4       102  18144480
    name  0x23F2727D      2028  18144584
    post  0xFFED000C        32  18148648
    prep  0x51510FE7       516  18148712
    vhea  0x01E100DD        36  18149228
    vmtx  0xE99ADF07     57592  18149264

Font 1: NSimSun.ttf
    tag     checksum    length    offset
    ----  ----------  --------  --------
    EBDT  - shared -
    EBLC  - shared -
    GDEF  - shared -
    GPOS  - shared -
    GSUB  - shared -
    MERG  - shared -
    OS/2  0x53518CD9        96   6214384
    cmap  - shared -
    cvt   - shared -
    fpgm  - shared -
    gasp  - shared -
    glyf  - shared -
    head  0xF3329F67        54  17927440
    hhea  - shared -
    hmtx  - shared -
    loca  - shared -
    maxp  - shared -
    meta  - shared -
    name  0xC83DA902      2033  18146612
    post  0xFFED000D        32  18148680
    prep  - shared -
    vhea  - shared -
    vmtx  - shared -

      在这里可以看出,simsun.ttc文件中包含SimSun.ttf和NSimSun.ttf两个文件,而且两者都有共享数据,这里我们把这两个文件都摘出来,可以先看看otc2otf命令支持的开关信息,比较简陋。

(base) D:\Workshop\Sources\Fonts>otc2otf -h
usage: otc2otf [-h] [--version] [-v] [-r] TTC FONT

Extract all OpenType fonts from the parent OpenType Collection font.

positional arguments:
  TTC FONT       path to TTC font

optional arguments:
  -h, --help     show this help message and exit
  --version      show program's version number and exit
  -v, --verbose  verbose mode
                 Use -vv for debug mode
  -r, --report   report the TTC's fonts and tables (no files are written)

(base) D:\Workshop\Sources\Fonts>otc2otf -v simsun.ttc
INFO:afdko.otc2otf:Saved D:\Workshop\Sources\Fonts\SimSun.ttf
INFO:afdko.otc2otf:Saved D:\Workshop\Sources\Fonts\NSimSun.ttf

      摘取到TTF文件后下一个工具就要登场,使用FontCreator打开这两个文件,查看里面的字形,并在PUA区域添加一个自定义字形,这里为了掩饰直接从其它区域拷贝一个字形,重新赋予它Unicode内码,当在应用程序中出现这个Unicode内码时,展现组件就会去寻找这个字形了。

      软件操作就不赘述了,界面还是比较友好的,编辑结束后将两个TTF文件各自导出,覆盖原先摘取的文件,接着用otf2otc将这两个文件合并。

(base) D:\Workshop\Sources\Fonts>otf2otc -h

otf2otc  -t <table tag=src font index> -o <output ttc file name> <input font file 0> ... <input font file n>

-t <table tag=source font index>  Optional. When this option is present, the matching table from the specified font file is
used for all the font files.

example:
  otf2otc -t 'CFF '=2 -o LogoCutStd.ttc LogoCutStd-Light.otf LogoCutStd-Medium.otf LogoCutStd-Bold.otf LogoCutStd-Ultra.otf
# The 'ttc' file will contain only one CFF table, taken from  LogoCutStd-Bold.otf.

The script mbe invoked with either the FDK command:
  otf2otc
or directly with the command:
  python <path to FDK directory>/python/afdko/otf2otc.py

Build an OpenType Collection font file from two or more OpenType font
files. The fonts are ordered in the output 'ttc' file in the same order
that the file names are listed in the command line. If a table is
identical for more than one font file, it is shared.

(base) D:\Workshop\Sources\Fonts>otf2otc -o simsun.ttc SimSun.ttf NSimSun.ttf
Input fonts: ['SimSun.ttf', 'NSimSun.ttf']
Output font: simsun.ttc
Shared tables: ['EBDT', 'EBLC', 'GDEF', 'GPOS', 'GSUB', 'MERG', 'cmap', 'cvt ', 'fpgm', 'gasp', 'glyf', 'hhea', 'hmtx', 'loca', 'maxp', 'meta', 'prep', 'vhea', 'vmtx']
Un-shared tables: ['OS/2', 'head', 'name', 'post']
Done

      可以看出,合并的过程中自动共享了数据。在重新生成TTC文件后,使用Apache™ FOP (Formatting Objects Processor)将我们的字体文件内嵌到PDF,来验证修改的结果。首先使用FOP工具从TTC生成字形的XML,为了简便,这里用脚本一气呵成了。

#encoding:utf-8

jars = [
'D:/Applications/Java/jdk1.8.0_192/lib/tools.jar',
'D:/Applications/fop-2.5/fop/lib/batik-all-1.13.jar',
'D:/Applications/fop-2.5/fop/lib/commons-io-1.3.1.jar',
'D:/Applications/fop-2.5/fop/lib/commons-logging-1.0.4.jar',
'D:/Applications/fop-2.5/fop/lib/fontbox-2.0.16.jar',
'D:/Applications/fop-2.5/fop/lib/serializer-2.7.2.jar',
'D:/Applications/fop-2.5/fop/lib/xalan-2.7.2.jar',
'D:/Applications/fop-2.5/fop/lib/xercesImpl-2.12.0.jar',
'D:/Applications/fop-2.5/fop/lib/xml-apis-1.4.01.jar',
'D:/Applications/fop-2.5/fop/lib/xml-apis-ext-1.3.04.jar',
'D:/Applications/fop-2.5/fop/lib/xmlgraphics-commons-2.4.jar',
'D:/Applications/fop-2.5/fop/build/fop.jar'
]

ttfs = [
'SimSun',
'NSimSun'
]

ENV['CLASSPATH'] = jars.join(';') 
ttfs.each do |ttf|
	IO.popen("java org.apache.fop.fonts.apps.TTFReader -ttcname #{ttf} simsun.ttc #{ttf}.xml") do |io|
		puts io.read
	end
end

IO.popen("java org.apache.fop.cli.Main -c fop.cfg -fo fop.fo -pdf test.pdf") do |io|
	puts io.read
end

      脚本执行结果显示如下。

---------- Ruby-x64 ----------
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.apps.TTFReader main
信息: TTF Reader for Apache FOP 2.5

九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.apps.TTFReader main
信息: Parsing font...
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.apps.TTFReader loadTTF
信息: Reading simsun.ttc...
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: This is a TrueType collection file with 2 fonts
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: Containing the following fonts: 
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: SimSun <-- selected
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: NSimSun
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.apps.TTFReader loadTTF
信息: Font Family: [宋体, SimSun]
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.apps.TTFReader constructFontXML
信息: Creating xml font file...
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.apps.TTFReader main
信息: Creating CID encoded metrics...
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.apps.AbstractFontReader writeFontXML
信息: Writing xml font file SimSun.xml...
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.apps.TTFReader main
信息: This font contains no embedding license restrictions.
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.apps.TTFReader main
信息: 
九月 06, 2020 1:57:27 上午 org.apache.fop.fonts.apps.TTFReader main
信息: XML font metrics file successfully created.
九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.apps.TTFReader main
信息: TTF Reader for Apache FOP 2.5

九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.apps.TTFReader main
信息: Parsing font...
九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.apps.TTFReader loadTTF
信息: Reading simsun.ttc...
九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: This is a TrueType collection file with 2 fonts
九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: Containing the following fonts: 
九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: SimSun
九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: NSimSun <-- selected
九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.apps.TTFReader loadTTF
信息: Font Family: [NSimSun, 新宋体]
九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.apps.TTFReader constructFontXML
信息: Creating xml font file...
九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.apps.TTFReader main
信息: Creating CID encoded metrics...
九月 06, 2020 1:57:28 上午 org.apache.fop.fonts.apps.AbstractFontReader writeFontXML
信息: Writing xml font file NSimSun.xml...
九月 06, 2020 1:57:29 上午 org.apache.fop.fonts.apps.TTFReader main
信息: This font contains no embedding license restrictions.
九月 06, 2020 1:57:29 上午 org.apache.fop.fonts.apps.TTFReader main
信息: 
九月 06, 2020 1:57:29 上午 org.apache.fop.fonts.apps.TTFReader main
信息: XML font metrics file successfully created.
九月 06, 2020 1:57:33 上午 org.apache.fop.events.LoggingEventListener processEvent
信息: Rendered page #1.
九月 06, 2020 1:57:33 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: This is a TrueType collection file with 2 fonts
九月 06, 2020 1:57:33 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: Containing the following fonts: 
九月 06, 2020 1:57:33 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: SimSun
九月 06, 2020 1:57:33 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: NSimSun <-- selected
九月 06, 2020 1:57:33 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: This is a TrueType collection file with 2 fonts
九月 06, 2020 1:57:33 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: Containing the following fonts: 
九月 06, 2020 1:57:33 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: SimSun <-- selected
九月 06, 2020 1:57:33 上午 org.apache.fop.fonts.truetype.OpenFont checkTTC
信息: NSimSun

      可见将字体内嵌到PDF,并最终正确生成了文档,其中FOP的配置文件如下。

<?xml version="1.0" encoding="UTF-8"?>
<fop version="1.0">
	<base>.</base>
	<renderers>
		<renderer mime="application/pdf">
			<filterList>
				<value>flate</value>
			</filterList>
			<fonts>
				<font metrics-url="SimSun.xml" kerning="yes" embed-url="simsun.ttc">
					<font-triplet name="SimSun" style="normal" weight="normal"/>
					<font-triplet name="SimSun" style="normal" weight="bold"/>
					<font-triplet name="SimSun" style="italic" weight="normal"/>
					<font-triplet name="SimSun" style="italic" weight="bold"/>
				</font>
				<font metrics-url="NSimSun.xml" kerning="yes" embed-url="simsun.ttc">
					<font-triplet name="NSimSun" style="normal" weight="normal"/>
					<font-triplet name="NSimSun" style="normal" weight="bold"/>
					<font-triplet name="NSimSun" style="italic" weight="normal"/>
					<font-triplet name="NSimSun" style="italic" weight="bold"/>
				</font>
			</fonts>
		</renderer>
	</renderers>
</fop>

      待处理的文档如下。

<?xml version="1.0" encoding="utf-8"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
	<fo:layout-master-set>
		<fo:simple-page-master master-name="simple" page-height="29.7cm" page-width="21cm" margin-top="1cm" margin-bottom="2cm" margin-left="2.5cm" margin-right="2.5cm">
			<fo:region-body margin-top="3cm"/>
			<fo:region-before extent="3cm"/>
			<fo:region-after extent="1.5cm"/>
		</fo:simple-page-master>
	</fo:layout-master-set>
	<fo:page-sequence master-reference="simple">
		<fo:flow flow-name="xsl-region-body">
			<fo:block font-size="18pt" font-family="SimSun" line-height="24pt" text-align="center" padding-top="3pt"> 
        这是宋体汉字-&#xe863;
      </fo:block>
			<fo:block font-size="18pt" font-family="NSimSun" line-height="24pt" text-align="center" padding-top="3pt"> 
        这是新宋体汉字-&#xe863;
      </fo:block>
		</fo:flow>
	</fo:page-sequence>
</fo:root>

      这里使用实体字符直接把字形中的Unicode码塞进去测试,避免了因当前系统的输入法、环境字符编码、自造字形注册等差异造成的误会和困扰,XML实体字符的表示方法为Ampersand加Sharp再加进制数最后分号结束,输出的PDF文档如下体所示。

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值