Я работал над этой темой несколько дней, и я узнал, что Unicode (как хорошо) невозможен в PDF. Используя 2-байтовые символы, описанный плинтус работает только с CID-шрифтами.
по-видимому, CID-шрифты - это внутренняя структура pdf, и они в действительности не являются шрифтами в этом смысле - они больше похожи на графические подпрограммы, которые можно вызывать, обращаясь к ним (с 16-битными адресами).
Таким образом, чтобы использовать Unicode в PDF напрямую
- вам придется конвертировать обычные шрифты в CID-шрифты, что, вероятно, чрезвычайно сложно - вам придется генерировать графические процедуры из исходного шрифта (?), Извлекать метрики символов и т. Д.
- вы не можете использовать CID-шрифты как обычные шрифты - вы не можете загружать или масштабировать их так же, как вы загружаете и масштабируете обычные шрифты
- также, 2-байтовые символы даже не покрывают все пространство Юникода
ИМХО, эти пункты делают абсолютно невозможным использование Юникода напрямую .
Вместо этого я сейчас использую символы косвенно следующим образом:
Для каждого шрифта я генерирую кодовую страницу (и таблицу поиска для быстрого поиска) - в c ++ это будет что-то вроде
std::map<std::string, std::vector<wchar_t> > Codepage;
std::map<std::string, std::map<wchar_t, int> > LookupTable;
затем, всякий раз, когда я хочу поместить некоторую строку в кодировке Юникод на страницу, я перебираю ее символы, ищу их в таблице соответствия и - если они новые, я добавляю их в кодовую страницу следующим образом:
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 += ">";
например, "H € llo World!" может стать <01020303040506040703080905>
и теперь вы можете просто поместить эту строку в pdf и распечатать ее, как обычно используя оператор Tj ...
но теперь у вас есть проблема: pdf не знает, что вы имеете в виду «H» для 01. Чтобы решить эту проблему, вы также должны включить кодовую страницу в файл pdf. Это делается путем добавления / Encoding к объекту Font и установки его Differences
Для "H € llo World!" Например, этот 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";
Обратите внимание, что я использую глобальный регистр шрифтов - я использую одни и те же имена шрифтов / F1, / F2, ... во всем документе PDF. Ссылка на тот же объект font-register указана в / Resources Entry всех страниц. Если вы делаете это по-другому (например, вы используете один шрифт-регистр на страницу) - вам, возможно, придется адаптировать код к вашей ситуации ...
Так как же найти названия глифов (/ Euro для "€", / exclam для "!" И т. Д.)? В приведенном выше коде это делается простым вызовом «GlyphName (* j)». Я сгенерировал этот метод с помощью BASH-Script из списка, найденного на
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";
...
}
}
A главная проблема Я оставил открытым то, что этот работает только до тех пор, пока вы используете не более 254 различных символов из одного и того же шрифта. Чтобы использовать более 254 различных символов, вам потребуется создать несколько кодовых страниц для одного и того же шрифта.
Внутри pdf разные кодовые страницы представлены разными шрифтами, поэтому для переключения между кодовыми страницами вам придется переключать шрифты, которые теоретически могут немного взорвать ваш pdf, но я, например, могу с этим смириться .. .