字体编码取决于字体类型。通常有一个被定义为当前字体的字体资源,在该字体字典中有对基础字体的引用以及描述编码的方法(通过 /Encoding
键)。如果该键不存在,则编码将是“标准”的,但您可以使用其他简单的编码,如 /MacRoman
和 /WinAnsi
作为编码值,或者指定一个标准编码和编码差异来显示差异。
如果你只处理8位字符,那么一切都很简单。对于许多早期的应用程序,它们会创建一些不同的字体,其中一个具有罗马编码,另一个将罗马字符映射到不可用字符。为了做到这一点,您的编码差异将包括对连字和其他通常未编码的符号的引用。这对于Type 1字体非常有效,但在TrueType字体的规范的部分中明确禁止使用:
无符号字体应该将其Encoding条目的值指定为MacRomanEncoding或WinAnsiEncoding,并且没有Differences数组
当您想要使用Unicode时,情况就大不相同了。在这种情况下,您将使用CID字体(基于字符ID的字体)。在这种情况下,字体引用的过程用于从您字符串中的字符编码映射到字体中的字符ID(反之亦然)。我强烈建议您阅读并充分理解PDF规范中第9.7节关于组合字体的内容,该节描述了您需要进行的所有操作,以便将UTF16BE编码到字符串中以使其在PDF中正确呈现。这是相当复杂的,因为有很多细节如果忽略将导致Acrobat中呈现的空白页面。
作为一名专业编写生成和消费PDF的代码的软件工程师,让我声明,当我被指派在我的代码中放入处理非规范兼容PDF的特殊情况时,我的内心都会死去一部分。请不要甚至想着发布任何未通过Preflight的文档。这与“Acrobat呈现它所以一定没问题”不同。让我举个例子 - 我见过许多在野外的文件,其中包含缺失FontDescriptor字典的关键元素,包括 /Ascent
,/Descent
,/CapHeight
等等。这些在Acrobat中呈现,但违反了规范,因为每个元素都是必需的。我知道Acrobat如何处理它 - 它附带一个巨大的字体度量数据库,如果找不到文件中的值,则会查找该值(甚至可能忽略文件中的度量)。我没有那个奢侈,因此我必须采取一些(潜在昂贵/无效)的临时措施。
你可能想考虑使用一个库来帮助你完成这项工作 - 也许是iText,因为它有一个足够好的教育许可证计划,我知道你是学生。还有一些基于C的库。也许你可以想出一种方法让GhostScript为你服务。
如果您不愿意或者无法遵循我的建议,关于坚持规范或使用明显遵守规范的库,请至少帮我填写文档信息字典中引用的尾部 / Creator 和/ Producer 字符串(参见14.3.3和7.5.5节)。这样,当我需要解析/消耗/操作您的文档时,我将直接怀疑你的家世。
让我们从上到下开始处理页面对象 - 我正在使用来自我的库的输出,并剥离掉我认为您不需要的内容:
1 0 obj <<
/Type /Page
/Parent 18 0 R
/Resources <<
/Font <<
/U0 13 0 R
>>
/ProcSet [ /PDF /Text ]
>>
/MediaBox [ 0 0 612 792 ]
/Contents 19 0 R
/Dur -1
>>
endobj
U0是一个用于Unicode文本的字体参考。
内容流旨在打印以下文本:Greek: Γειά σου κόσμος
。
BT /U0 24 Tf 72 670 Td
(\000G\000r\000e\000e\000k\000:\000 \003\223\003\265\003\271\003\254\000 \003\303\003\277\003\305\000 \003\272\003\314\003\303\003\274\003\277\003\302)
Tj ET
参考的字体字典如下:
13 0 obj <<
/BaseFont /DejaVuSansCondensed
/DescendantFonts [ 4 0 R ]
/ToUnicode 14 0 R
/Type /Font
/Subtype /Type0
/Encoding /Identity-H
>>
endobj
该流具有包含以下PostScript代码的/ToUnicode
入口点:
/CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> <FFFF> endcodespacerange 1 beginbfrange <0000> <FFFF> <0000> endbfrange endcmap CMapName currentdict /CMap defineresource pop end end
这是由CID字体规范定义的。
而DescendantFonts数组指向此对象:
4 0 obj <<
/Subtype /CIDFontType2
/Type /Font
/BaseFont /DejaVuSansCondensed
/CIDSystemInfo 7 0 R
/FontDescriptor 8 0 R
/DW 1000
/W 9 0 R
/CIDToGIDMap 10 0 R
>>
CIDToGIDMap是一个压缩流,包含实际映射。CIDSystemInfo是<</Registry (Adobe) /Ordering (USC) /Supplement 0>>
(因为我在所有unicode字体中共享它)。FontDescriptor很简单,W数组从字体度量衍生而来。
通过这些细节,你明白为什么我不会轻易说“走开,别再污染我的环境了”了吗?
我真的开始质疑这个任务的性质。编写简单的PDF文件是一回事,但编写能够处理任意OpenType/TrueType字体中的完整Unicode的代码需要你理解CID规范和TrueType规范(提示:我有一个完整的TrueType解析器,可以提取字体中任何字形的所有度量信息,以便我输出/W数组)。
然而,如果你只需要输出到Type 1字体,那么你的生活就变得容易得多,因为你将获取整个UTF8流并将其作为Unicode读入,对于每个独特字符,你将使用此表格建立一个从Unicode字符到字形名称和内部字符编号的映射。内部字符编号本质上是所输入字符的唯一索引(mod)。例如,如果你在页面上有不到257个独特字符,那么你将只有一个字体,该字体的编码映射到以所输入顺序映射字符。如果你使用“abcba”作为输入,则PDF中的输出字符串将是(\000\001\002\001\000)
,并将映射到一个具有差异数组的编码字典,该数组为[0/a/b/c]
。如果你有n个唯一字符,其中n>256,则你将拥有(n/256)+1个字体,并且每个字体都具有编码。
如果你的老师/教授想在短时间内得到除Type 1字体之外的任何东西,那么他对学生的期望不切实际和/或对输出质量的期望很低。你应该问自己是否需要处理CID字体,如果需要,那么你的教授至少是个虐待狂。对我这样的经验丰富的专业人士而言,提取宽度的TrueType解析器大约需要4天时间。我有两个优势:(1)使用托管语言(C#)减少了C语言中会出现的问题,并且还可以使用反射来自动解析;(2)当没有干扰时,我编写的代码比典型的学生快10-20倍,因此我的32小时将转化为320个学生小时,或者更少(然而,我的代码有不同的约束条件——它必须优雅地处理任何垃圾字体),如果你被允许偷一些像stb这样的东西,那么就称之为200个或更少。这仅仅是获取字体描述符中的一个特定元素。