36

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

(something)

还可以选择使用八进制代码转义字符:

(\527)

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


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

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


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


编辑 3: iText 对我来说工作得很好。我想教训是,当面对看似毫无意义的困难时,寻找比你更了解它的人。

4

7 回答 7

40

在第 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 开头的字符串,这不太可能是一个词或短语的有意义的开头)。

于 2008-10-02T15:39:12.637 回答
15

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

于 2008-09-27T14:28:03.880 回答
10

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 或从中复制文本。

于 2016-04-24T07:03:33.443 回答
5

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

于 2016-08-17T22:57:00.170 回答
4

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

查看 PDFBox 或 iText。

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

于 2008-10-02T15:31:06.150 回答
4

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

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

所以直接在pdf中使用unicode

  1. 您必须将普通字体转换为 CID-Fonts,这可能非常困难 - 您必须从原始字体(?)生成图形例程,提取字符度量等。
  2. 你不能像普通字体一样使用 CID-Fonts - 你不能像加载和缩放普通字体一样加载或缩放它们
  3. 此外,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 += ">";

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

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

为了“你好世界!” 例如,这个 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 文件炸毁很多,但我可以忍受...

于 2015-08-05T11:23:22.197 回答
-2

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

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

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

于 2008-09-24T16:57:31.840 回答