Hunter的大杂烩 技术学习笔记

October 11, 2013

PDF CMAP知识

Filed under: 技术话题 — hunter @ 12:20 pm

from:http://bbs.csdn.net/topics/340109816

PDF为了识别所有的字符,给每一个文字都赋予一个唯一的编码,叫CID。

然后又提供了不同的字体编码与CID的Map文件和CID和Unicode的Map文件。参照Resources\cmap\00_readme.pdf文件,就可以知道这些文件是什么。

一般的PDF文件中文字识别的方法应该是从PDF文件解析出来字体名称和文字编码后,从对应的CMap文件中找到该文字编码对应的CID。然后再根据CID从CID和Unicode的Map文件找到对应的Unicode。

PDF从页描述命令中,可以知道文字打印的位置与高度(字号),但它的宽度则必须取决于字型。除此之外,字型同时也决定了字码,因此需要说明一下PDF文件里的字型信息。字型信息是放在额外资源里(Pages或Page对象的Resources属性值),它本身是一个词典对象。以下为其相关的属性:(1) Type:后接名称对象,必须是Font(2) Subtype:后接的名称对象表示其字型型态,可能是Type0、Type1、MMType1、Type3、TrueType(3) ToUnicode:如果有的话,后接的串流对象表示用来转换成Unicode字码的CMap(后述)

对于一个非Type0的字型,输入字码一律是一个byte,取得Unicode字码的方式如下:
(1) 如果有ToUnicode CMap的话,便直接到该CMap取得对应的Unicode字码
(2) 如果找不到对应的Unicode字码,便依输入字码到Encoding信息里取得该字码对应的字符名称
(3) 依照取得的字符名称转成对应的Unicode字码(不一定能转换,此时表示显示出来的字没有对应的Unicode字码,或是writer并未附相关的Unicode字码转换信息)

  由于要从PDF文件中取出正确的文字出来,必须基于PDF Writer所附的转码表,但早期的PDF文件大都未附(因为那时候只重视原文重现,还未有原文再利用的观念),在这种情况下取出的文字,一定是乱码,而且完全无解。这点大家必须牢记在心。

至于取得宽度信息的方式如下:
(1) 依输入字码到Width信息中取得对应的宽度信息
(2) 如果没有对应的宽度信息,则取内定宽度Type0字型要取得Unicode字码与宽度信息比较麻烦些。由于Type0字型的输入字码允许多byte,无论1 byte、2 byte、4 byte都可以,也可以相互混用(只要不冲突即可,例如繁体中文BIG5码在20h~7Eh便是1 byte,8140h~FEFEh便是2 byte),因此要决定输入字码的byte数,便必须利用Encoding属性里的CMap信息。Encoding CMap会将输入的字码转成Font Selector和CID,也就是对应到DescendantFonts字型数组里的那个字型与那个字。DescendantFonts数组里的字型,存有字型宽度与字集的相关信息,而且该字型的型态必须是CIDFontType0或CIDFontType2,下面便是这两种字型型态的属性:
(1) CIDSystemInfo:后接的词典信息用来说明本字型的字集
(2) DW:如果有的话,后接的实数表示内定的宽度(即未定义在W属性里字码应使用的宽度)
(3) W:如果有的话,

后接的数组用来定义各字码的宽度CIDSystemInfo的属性如下:
(1) Registry:后接的字符串表示字集类别,例如Adobe
(2) Ordering:后接的字符串表示字集名称,Adobe类别目前有Japan1、Korea1、GB1、CNS1等4种

由于CIDSystemInfo里记录了该字型使用的字集,而每种字集的CID都是固定的,因此只要知道这个字集,即可将Encoding CMap转出来的CID再转成Unicode字码(如果有ToUnicode CMap,当然要先由ToUnicode CMap决定)。以目前而言,我们需要制作4份Adobe标准的字集CID转Unicode字码表,所幸Acrobat Reader有附这部份的CMap定义档,名称后面加-UCS2的就是了,例如Adobe-CNS1-UCS2,因此我们可以将之视为ToUnicode CMap定义档,直接加载用来转码即可(但有版权问题,因此最好还是自行做成转码表)。如果没有ToUnicode CMap,又无法辨识CIDSystemInfo里的字集,那就无法转换出正确的Unicode字码了。

至于W的数组内容格式有下列两种:
起始CID 宽度数组
起始CID 结束CID 宽度
第一种格式表示由该CID开始的宽度,都定义在宽度数组中,数目由宽度数组决定。
第二种格式表示在该范围的CID都是使用后面所指示的宽度。

综合一下,Type0字型取得Unicode字码的方式如下:
(1) 如果有ToUnicode CMap的话,便直接到该CMap取得对应的Unicode字码
(2) 如果找不到对应的Unicode字码,便利用Encoding CMap将输入字码转成Font Selector和CID
(3) 依Font Selector到DescendantFonts取出对应的CID字型,由其CIDSystemInfo决定字集名称,再依该字集名称的CID转Unicode字码表转换成Unicode字码

取得宽度信息的方式如下:
(1) 利用Encoding CMap将输入字码转成Font Selector和CID
(2) 依Font Selector到DescendantFonts取出对应的CID字型,再到该字型依CID取得宽度信息,如果没有宽度信息,则使用内定宽度信息

如此全部字型的Unicode字码与宽度信息取法都已明朗化了,剩下的便只有Encoding CMap和ToUnicode CMap这两个而已。以下便开始说明这两个CMap。Encoding CMap可以是一个名称对象,或是一个串流对象,如果是一个名称对象,便表示为内定的Encoding CMap。

PDF文件定义了许多常用内定的Encoding CMap,其目的当然便是要减少PDF文件的大小。至于有那些内定的Encoding CMap,可以直接到Acrobat Reader程序目录里找CMap目录,里面便有一堆(且随着版本更新而增加)。因此我们的做法最好也是附上这些文件,然后在遇到标准的Encoding CMap名称时,再去找对应相同名称的CMap定义档,读取其CMap定义。其中有2个比较特别的定义:Identity-H和Identity-V,这两个都是直接将2 byte输入字码视为CID处理(high byte在前)。虽然CMap目录里也有附这两个标准CMap定义档,但由于很常见,故为了速度考虑,可以直接另行处理。
如果Encoding CMap不是一个内定的Encoding CMap名称,则必须以串流对象来定义它,下面便是串流对象CMap的重要属性:
(1) Type:后接名称对象,必须是CMap
(2) UseCMap:如果有的话,后接的CMap(可能是名称对象或串流对象),系为本CMap数据的基准,其后接的串流数据,便是用来修改此一基准,成为本CMap的实际内容
(3) WMode:如果有的话,后接的数字若是1表示为直排字型,内定为0(会影响排字走向)

虽然在串流数据里定义的CMap内容,必须符合“Adobe CMap and CIDFont File Specification”里定义的语法,不过由于Encoding CMap和ToUnicode CMap的格式不太一样,我们还是分别予以说明。Encoding CMap的格式如下(只取相关部份,其余均可略过,标准Encoding CMap定义档的内容也是相同形式):

… (略)
begincmap基准名称 
usecmapcmap文件头信息(不重要,可略过)
范围定义(不重要,可略过)
转码定义
*endcmap
… (略)

若有usecmap表示做为基准的内定Encoding CMap名称,后面的定义是用以修改此基准定义的数据。
转码定义的格式如下:
字型索引 usefont
定义项数 begincidrange
范围转码定义*
endcidrange

字型索引便是Font Selector,用来表示其后转出的CID是对应到那个CID字型(由0编起)。如果省略的话,则沿用前面的定义,如果都没有,则内定为0。范围转码定义的形式如下:
开始字码 结束字码 CID
表示输入的起迄字码范围,刚好对应到后面CID开始的数字。注意开始字码/结束字码的格式为字符串对象,而CID是数字对象。字符串的长度同时也表示了输入字码的长度,
例如:<20>表示输入字码只需一个byte<0020>表示输入字码要2个byte
至于ToUnicode CMap,它本身必须是一个串流数据(因为没有内定的ToUnicode CMap,故串流对象里的UseCMap属性也不能是名称对象),其格式如下:
… (略)
begincmapcmap
文件头信息(不重要,可略过)
范围定义(不重要,可略过)
转码定义*
endcmap
… (略)

转码定义的格式如下:
定义项数 beginbfrange
范围转码定义*
endbfrange
或是
定义项数 beginbfchar
字符转码定义*
endbfchar

范围转码定义有下列两种形式:
开始字码 结束字码 对应的开始Unicode字符串
开始字码 结束字码 [各字码应转换的Unicode字符串]

第一种形式表示符合该字码范围的文字,刚好线性地对应到Unicode字符串开始的一个连续区。
第二种形式表示符合该字码区的文字,必须到后面的字符串数组中取得对应的Unicode字符串。至于字符转码定义,则是:
字码 该字码应转换的Unicode字符串
这边必须注意的是,转换后的Unicode字符串可能有多个Unicode字符(无论任何转码定义形式),
例如:<005F8192> <00660069>表示当输入字码符合00h,5Fh,81h,92h时(4 byte),便将它转成0066h、0069h这两个Unicode字码(也就是fi)。

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Powered by WordPress