我现在已经在这个主题上工作了几天,我了解到 unicode 在 pdf 中是(同样好)不可能的。使用 2 字节字符的底座描述的方式仅适用于 CID 字体。
看起来,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 += ">";
例如,“你好世界!” 可能会变成 <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 文件炸毁很多,但我可以忍受...