Юникод в формате PDF
Моя программа генерирует относительно простые PDF-документы по запросу, но у меня возникают проблемы с символами юникода, такими как кандзи или нечетные математические символы. Чтобы записать обычную строку в формате PDF, поместите ее в скобки:
(something)
Существует также возможность избежать символа с восьмеричными кодами:
(\527)
но это только до 512 символов. Как вы кодируете или избегаете более высоких символов? Я видел ссылки на потоки байтов и строки с шестнадцатеричным кодированием, но ни одна из прочитанных мной ссылок, похоже, не готова рассказать мне, как это сделать на самом деле.
Изменить: В качестве альтернативы укажите мне хорошую библиотеку Java PDF, которая будет выполнять эту работу для меня. Тот, который я сейчас использую, представляет собой версию gnujpdf (в которой я исправил несколько ошибок, поскольку исходный автор, похоже, прошел AWOL), что позволяет вам программировать интерфейс AWT Graphics, и в идеале любая замена должна делать то же самое.
Альтернативы кажутся либо HTML → PDF, либо программной моделью, основанной на параграфах и блоках, которые очень похожи на HTML. iText является примером последнего. Это означало бы переписывание моего существующего кода, и я не уверен, что они дадут мне такую же гибкость при планировании.
Изменить 2: Я раньше этого не понимал, но библиотека iText имеет интерфейс Graphics2D и, похоже, отлично работает с юникодом, так что я буду использовать. Хотя это не ответ на заданный вопрос, он решает проблему для меня.
Редактировать 3: iText работает хорошо для меня. Думаю, урок, когда он сталкивается с чем-то, что кажется бессмысленным, искать кого-то, кто знает об этом больше, чем вы.
Ответы
Ответ 1
Простой ответ: простого ответа нет. Если вы посмотрите на спецификацию PDF, вы увидите целую главу - и длинную в этом - посвященную механизмам отображения текста. Я реализовал всю поддержку PDF для своей компании, и обработка текста была, безусловно, самой сложной частью упражнений. Решение, которое вы обнаружили, - используйте стороннюю библиотеку для выполнения вашей работы - действительно лучший выбор, если у вас нет особых специальных требований для ваших PDF файлов.
Ответ 2
В справочнике PDF в главе 3 это то, что они говорят о Unicode:
Текстовые строки кодируются в либо в формате PDFDocEncoding, либо в кодировке Unicode. PDFDocEncoding - это надмножество кодировки ISO Latin 1 и описано в Приложении D. Unicode описан в Unicode Standard консорциумом Unicode (см. библиографию). Для текстовых строк, закодированных в Юникоде, первые два байта должны быть 254, за которыми следует 255. Эти два байта представляют маркер порядка байтов Unicode, U + FEFF, указывающий что строка кодируется в схеме кодирования UTF-16BE (big-endian), указанной в стандарте Unicode. (Этот механизм исключает начало строки, используя PDFDocEncoding с двумя символами шип ydieresis, что вряд ли быть значимым началом слова или фразы).
Ответ 3
Алгоманский ответ неправильный во многом. Вы можете делать PDF-документы с помощью unicode в нем ", и это не ракетостроение, хотя для этого нужна определенная работа.
Да, он прав, чтобы использовать более 255 символов в одном шрифте, вам нужно создать сложный шрифт (CIDFont) pdf-объект.
Затем вы просто упомянете фактический шрифт TrueType, который вы хотите использовать в качестве записи DescendatFont для CIDFont.
Хитрость заключается в том, что после этого вам нужно использовать индексы глифов шрифта вместо кодов символов. Чтобы получить эту карту индексов, вам нужно проанализировать раздел cmap
шрифта - получить содержимое шрифта с помощью функции GetFontData
и взять на себя спецификацию TTF.
И это! Я только что сделал это, и теперь у меня есть unicode pdf!
Пример кода для разбора cmap
находится здесь: https://support.microsoft.com/en-us/kb/241020
И да, не забывайте/запись в ToUnicode, указав @user2373071, или пользователь не сможет искать ваш PDF или скопировать текст с него.
Ответ 4
См. Приложение D (стр. 995) спецификации PDF. Существует ограниченное количество шрифтов и наборов символов, предварительно определенных в потребительском приложении PDF. Чтобы отобразить другие символы, вам необходимо встроить шрифт, содержащий их. Также желательно вставлять только подмножество шрифта, включая только обязательные символы, чтобы уменьшить размер файла. Я также работаю над отображением символов Unicode в PDF, и это серьезная проблема.
Проверьте PDFBox или iText.
http://www.adobe.com/devnet/pdf/pdf_reference.html
Ответ 5
Я уже несколько дней работал над этой темой, и то, что я узнал, заключается в том, что unicode (насколько это хорошо) невозможно в формате pdf. Использование двухбайтовых символов, как описано в плинту, работает только с CID-шрифтами.
По-видимому, CID-Fonts - это встроенная pdf-конструкция, и в этом смысле они не являются действительно шрифтами - они, похоже, больше похожи на графические подпрограммы, которые могут быть вызваны путем их адресации (с 16-разрядными адресами).
Итак, чтобы использовать unicode в pdf напрямую
- вам нужно будет преобразовать обычные шрифты в CID-Fonts, что, вероятно, очень сложно - вам придется создавать графические подпрограммы из исходного шрифта (?), извлекать метрики символов и т.д.
- Вы не можете использовать CID-шрифты, такие как обычные шрифты - вы не можете загружать или масштабировать их так, как вы загружаете и масштабируете обычные шрифты
- 2-байтные символы даже не охватывают полное пространство Юникода.
IMHO, эти точки делают абсолютно невозможным использовать unicode напрямую.
Теперь я использую символы косвенно следующим образом:
Для каждого шрифта я генерирую кодовую страницу (и таблицу поиска для быстрого поиска) - в С++ это будет что-то вроде
std::map<std::string, std::vector<wchar_t> > Codepage;
std::map<std::string, std::map<wchar_t, int> > LookupTable;
тогда, когда я хочу поместить некоторую строку unicode на страницу, я повторяю ее символы, просматриваю их в таблице lookup и, если они новы, я добавляю их на кодовую страницу следующим образом:
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";
Обратите внимание, что я использую глобальный регистр шрифтов - я использую те же имена шрифтов /F 1,/F2,... во всем документе PDF. Тот же объект-регистр шрифтов упоминается в записи /Ресурсы всех страниц. Если вы сделаете это по-другому (например, вы используете один регистр шрифта на странице) - вам, возможно, придется адаптировать код к вашей ситуации...
Итак, как вы находите имена глифов (/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 файл, но я, во-первых, могу с этим жить..
Ответ 6
Как указывал дредкин, вам нужно использовать индексы глифов вместо значения символа Юникода в потоке содержимого страницы. Этого достаточно для отображения текста в формате Unicode в формате PDF, но текст Юникода не будет доступен для поиска. Чтобы сделать текст доступным для поиска или скопировать/вставить его, вам также потребуется включить поток /ToUnicode. Этот поток должен перевести каждый глиф в документе на фактический символ Юникода.
Ответ 7
Я не эксперт в формате PDF, и (как сказал Ферруччио) спецификации PDF в Adobe должны рассказать вам все, но в моей голове появилась мысль:
Вы уверены, что используете шрифт, который поддерживает все нужные вам персонажи?
В нашем приложении мы создаем PDF с HTML-страниц (с третьей стороной), и у нас была эта проблема с кириллическими символами...