PDF 中的 Unicode

Posted

技术标签:

【中文标题】PDF 中的 Unicode【英文标题】:Unicode in PDF 【发布时间】:2010-09-12 19:00:34 【问题描述】:

我的程序根据请求生成相对简单的 PDF 文档,但我遇到了 unicode 字符问题,例如汉字或奇怪的数学符号。要在 PDF 中编写普通字符串,请将其放在括号中:

(something)

还有用八进制码转义字符的选项:

(\527)

但这最多只能包含 512 个字符。你如何编码或转义更高的字符?我看过对字节流和十六进制编码字符串的引用,但我读过的所有引用似乎都不愿意告诉我如何实际去做。


编辑: 或者,给我指出一个可以为我完成这项工作的优秀 Java PDF 库。我目前使用的是 gnujpdf 的一个版本(我已经修复了几个错误,因为原作者似乎已经离开了),它允许您针对 AWT 图形界面进行编程,理想情况下任何替换都应该这样做一样的。

替代方案似乎是 html -> PDF,或基于段落和框的编程模型,感觉非常像 HTML。 iText 是后者的一个例子。这意味着重写我现有的代码,我不相信他们会给我同样的布局灵活性。


编辑 2: 我之前没有意识到,但是 iText 库有一个 Graphics2D API,并且似乎可以完美地处理 unicode,所以这就是我将要使用的。虽然这不是问题的答案,但它为我解决了问题。


编辑 3: iText 非常适合我。我想教训是,当面对看似毫无意义的困难时,寻找比你更了解它的人。

【问题讨论】:

除了用()包裹字符串外,还可以使用<>。在 gt/lt 中,您使用十六进制数字而不是字母。效率要低得多,但您不必担心逃脱。 <FEFF00480065006C006C006F00200077006F0072006C00640021>:“你好,世界!”作为 Unicode-16 字符串。 Plinth 的帖子也很重要……你必须使用 FE FF。 FFFE 不好。因为某些原因。 :// @MarkStorer,它必须是 FEFF,因为它必须是 UTF-16BE。 这些对我有用(正确给出áéíóú)\341\351\355\363\372 但这些不\527\777(它们显示为Wÿ) - 有没有办法知道哪个那些将与例如 > 一起使用 @murkle 八进制值被剥离为 8 位 @murkle paulbourke.net/dataformats/postscript 和链接的 PDF 为 PostScript 解释了这一点,PDF 也是一样的。 czyborra.com/draft/printing.html 讲述了一个非常悲伤的故事☹ 【参考方案1】:

Algoman 的答案在很多方面都是错误的。您可以制作包含 Unicode 的 PDF 文档,这不是火箭科学,尽管它需要一些工作。 是的,他是对的,要在一种字体中使用超过 255 个字符,您必须创建一个复合字体 (CIDFont) pdf 对象。 然后,您只需提及要用作 CIDFont 的 DescendatFont 条目的实际 TrueType 字体。 诀窍是在那之后你必须使用字体的字形索引而不是字符代码。要获取此索引映射,您必须解析字体的 cmap 部分 - 使用 GetFontData 函数获取字体的内容并掌握 TTF 规范。 就是这样!我刚刚做了,现在我有了一个 Unicode PDF!

解析cmap部分的示例代码在这里:https://web.archive.org/web/20150329005245/http://support.microsoft.com/en-us/kb/241020

是的,不要忘记 @user2373071 指出的 /ToUnicode 条目,否则用户将无法搜索您的 PDF 或从中复制文本。

【讨论】:

【参考方案2】:

正如dredkin 所指出的,您必须在页面内容流中使用字形索引而不是Unicode 字符值。这足以在 PDF 中显示 Unicode 文本,但无法搜索 Unicode 文本。要使文本可搜索或对其进行复制/粘贴,您还需要包含 /ToUnicode 流。此流应将文档中的每个字形转换为实际的 Unicode 字符。

【讨论】:

【参考方案3】:

我现在已经在这个主题上工作了几天,我了解到 unicode 在 pdf 中是(尽可能)不可能的。使用 2 字节字符的 plinth 描述的方式仅适用于 CID-Fonts。

看起来,CID-Fonts 是一个 pdf 内部结构,在这个意义上它们并不是真正的字体 - 它们似乎更像是图形子例程,可以通过寻址来调用它们(使用 16 位地址)。

所以直接在pdf中使用unicode

    您必须将普通字体转换为 CID-Fonts,这可能非常困难 - 您必须从原始字体生成图形例程 (?)、提取字符度量等。 您不能像普通字体一样使用 CID-Fonts - 您不能像加载和缩放普通字体那样加载或缩放它们 另外,2 字节字符甚至无法覆盖整个 Unicode 空间

恕我直言,这些点使得直接使用 unicode 绝对不可行。



我现在正在做的是通过以下方式间接使用字符: 对于每种字体,我都会生成一个代码页(以及一个用于快速查找的查找表)——在 c++ 中,这类似于

std::map<std::string, std::vector<wchar_t> > Codepage;
std::map<std::string, std::map<wchar_t, int> > LookupTable;

然后,每当我想在页面上放置一些 unicode 字符串时,我都会迭代它的字符,在查找表中查找它们 - 如果它们是新的,我将它们添加到代码页中,如下所示:

for(std::wstring::const_iterator i = str.begin(); i != str.end(); i++)
                
    if(LookupTable[fontname].find(*i) == LookupTable[fontname].end())
    
        LookupTable[fontname][*i] = Codepage[fontname].size();
        Codepage[fontname].push_back(*i);
    

然后,我生成一个新字符串,其中原始字符串中的字符被替换为它们在代码页中的位置,如下所示:

static std::string hex = "0123456789ABCDEF";
std::string result = "<";
for(std::wstring::const_iterator i = str.begin(); i != str.end(); i++)
                
    int id = LookupTable[fontname][*i] + 1;
    result += hex[(id & 0x00F0) >> 4];
    result += hex[(id & 0x000F)];

result += ">";

例如,“你好,世界!”可能会变成 现在您可以像往常一样使用 Tj 运算符将该字符串放入 pdf 并打印出来...

但是你现在有一个问题:pdf 不知道你的意思是 01 的“H”。要解决这个问题,你还必须在 pdf 文件中包含代码页。这是通过向 Font 对象添加 /Encoding 并设置其 Differences

来完成的

为了“你好世界!”例如,这个 Font-Object 可以工作:

5 0 obj 
<<
    /F1
    <<
        /Type /Font
        /Subtype /Type1
        /BaseFont /Times-Roman
        /Encoding
        <<
          /Type /Encoding
          /Differences [ 1 /H /Euro /l /o /space /W /r /d /exclam ]
        >>
    >> 
>>
endobj 

我用这段代码生成它:

ObjectOffsets.push_back(stream->tellp()); // xrefs entry
(*stream) << ObjectCounter++ << " 0 obj \n<<\n";
int fontid = 1;
for(std::list<std::string>::iterator i = Fonts.begin(); i != Fonts.end(); i++)

    (*stream) << "  /F" << fontid++ << " << /Type /Font /Subtype /Type1 /BaseFont /" << *i;

    (*stream) << " /Encoding << /Type /Encoding /Differences [ 1 \n";
    for(std::vector<wchar_t>::iterator j = Codepage[*i].begin(); j != Codepage[*i].end(); j++)
        (*stream) << "    /" << GlyphName(*j) << "\n";
    (*stream) << "  ] >>";

    (*stream) << " >> \n";

(*stream) << ">>\n";
(*stream) << "endobj \n\n";

请注意,我使用了一个全局字体寄存器 - 我在整个 pdf 文档中使用相同的字体名称 /F1、/F2、...。所有页面的 /Resources 条目中都引用了相同的 font-register 对象。如果您以不同的方式执行此操作(例如,您每页使用一个字体寄存器) - 您可能需要根据您的情况调整代码...

那么如何找到字形的名称(/Euro 代表“€”,/exclam 代表“!”等)?在上面的代码中,这是通过简单地调用“GlyphName(*j)”来完成的。我已经用

的列表中的 BASH 脚本生成了这个方法

http://www.jdawiseman.com/papers/trivia/character-entities.html

它看起来像这样

const std::string GlyphName(wchar_t UnicodeCodepoint)

    switch(UnicodeCodepoint)
    
        case 0x00A0: return "nonbreakingspace";
        case 0x00A1: return "exclamdown";
        case 0x00A2: return "cent";
        ...
    

一个主要问题我没有解决这个问题只有在您使用相同字体的最多 254 个不同字符时才有效。要使用超过 254 个不同的字符,您必须为同一种字体创建多个代码页。

在 pdf 中,不同的代码页由不同的字体表示,因此要在代码页之间切换,您必须切换字体,理论上这可能会使您的 pdf 变得相当糟糕,但我可以忍受这一点.. .

【讨论】:

顺便说一下 - 我提到的字形列表包含超过 3600 个条目。生成的代码文件为 175 KiB,编译后的目标文件为 600 KiB(调试版本为 1.1 MiB) 一旦你开始使用标准14字体以外的其他字体,CID字体就会变得非常自然。 1.您必须将普通字体转换为 CID-Fonts,这可能非常困难 - 这对于 OpenType(带有 CFF 或 TrueType 轮廓)字体相当简单。这些可以使用Identity-H 编码作为CIDFontType0 (CFF) 或CIDFontType2 (TrueType) 包含在内。这就是我在rinohtype 中所做的。 2.你不能像普通字体一样使用 CID-Fonts - 你不能像加载和缩放普通字体一样加载或缩放它们 - 我认为这是不正确的。据我所知,显示文本的唯一区别是您需要使用 16 位字符代码。【参考方案4】:

在第 3 章的 PDF 参考中,这是他们对 Unicode 的评价:

文本字符串被编码为 PDFDocEncoding 或 Unicode 字符编码。 PDFDocEncoding 是一个 ISO Latin 1 编码的超集,并记录在附录 D 中。Unicode 由 Unicode 联盟在 Unicode 标准中描述(参见参考书目)。 对于以 Unicode 编码的文本字符串,前两个字节必须是 254,后跟 255.这两个字节代表Unicode字节序标记,U+FEFF,表示 字符串以指定的 UTF-16BE (big-endian) 编码方案进行编码 在 Unicode 标准中。 (这种机制排除了使用 PDFDocEncoding 带有两个字符 thorn ydieresis,这不太可能 是一个词或短语的有意义的开头)。

【讨论】:

我知道这听起来好得令人难以置信。 “文本字符串”用于文档元数据(注释、书签名称),用于渲染文本! @BrechtMachiels 至少在 PDF 1.7 参考中,Text 对象 (BT) 文本显示运算符 (Tj) 明确表示“显示文本字符串”。这意味着它们可以按照描述进行 UTF-16BE 编码。 @jdmichal 这不会自动工作。如果字体支持,字符串的编码只能是 UTF-16BE(实际上,它必须是具有 ToUnicode 值和其他几个元素的 CID 字体)。 有一个细节我似乎无法理解:UTF-16BE 可以与(text string) 语法一起使用吗?此语法暗示,单字节 ) 字符是终止标记。那么 UTF-16BE 代码单元呢,其中高字节恰好具有值 29h?所有这些都需要逃脱吗?或者 UTF-16BE 是否要求使用十六进制字符串 (&lt;hex string&gt;)? 如果字符串是 UTF-16BE,那么字符串必须是偶数字节。高字节没有冲突。【参考方案5】:

请参阅 PDF 规范的附录 D(第 995 页)。 PDF 消费者应用程序中预定义的字体和字符集数量有限。要显示其他字符,您需要嵌入包含它们的字体。为了减小文件大小,最好只嵌入字体的一个子集,包括只需要的字符。我也在努力在 PDF 中显示 Unicode 字符,这很麻烦。

查看 PDFBox 或 iText。

http://www.adobe.com/devnet/pdf/pdf_reference.html

【讨论】:

【参考方案6】:

简单的答案是没有简单的答案。如果你看一下 PDF 规范,你会看到一整章——而且其中有一章很长——专门讨论文本显示的机制。我为我的公司实现了所有的 PDF 支持,而处理文本是迄今为止练习中最复杂的部分。您发现的解决方案(使用 3rd 方库为您完成工作)确实是最佳选择,除非您对 PDF 文件有非常具体的特殊用途要求。

【讨论】:

【参考方案7】:

我不是 PDF 专家,而且(正如 Ferruccio 所说)Adobe 的 PDF 规范应该告诉你一切,但我的脑海中突然冒出一个想法:

您确定您使用的字体支持您需要的所有字符吗?

在我们的应用程序中,我们从 HTML 页面(使用第三方库)创建 PDF,但我们遇到了西里尔字符的问题...

【讨论】:

我们坚持使用每台计算机上的基本字体,不嵌入任何字体。 “Adobe 的 PDF 规范应该告诉你一切”。不幸的是,根据我的经验,他们应该不会。 @Renan:“Adobe 的 PDF 规范应该告诉你一切”。不幸的是,根据我的经验,你不容易找到它们,而且它们通常是不必要的复杂。 @Renan 确实!有些 1.7 规范甚至是错误的!呸,我说!呸!

以上是关于PDF 中的 Unicode的主要内容,如果未能解决你的问题,请参考以下文章

Qt学习笔记6.Qt中的字符字符串

python 第三天

如何编辑PDF文件,怎么修改PDF中的文字

如何提取pdf中的数据将pdf转换成excel

尝试使用 java 签署 pdf 文档。为啥 PDF 文件中的签名无效?

使用 PDFBox 编辑 pdf 中的内容会从 pdf 中删除最后一行