Юникод в PDF

моя программа генерирует относительно простые PDF-документы по запросу, но у меня проблемы с символами unicode, такими как кандзи или нечетные математические символы. Чтобы написать нормальную строку в PDF, вы помещаете ее в скобки:

(something)

существует также возможность избежать символа с восьмеричными кодами:

(7)

но это только до 512 символов. Как вы кодируете или избегаете высших символов? Я видел ссылки на потоки байтов и строки с шестнадцатеричной кодировкой, но ни одна из них ссылки, которые я прочитал, похоже, готовы рассказать мне, как это сделать.


Edit: кроме того, укажите мне хорошую библиотеку Java PDF, которая сделает эту работу за меня. В настоящее время я использую версию gnujpdf (в которой я исправил несколько ошибок, так как исходный автор, похоже, ушел в самоволку), которая позволяет программировать против графического интерфейса AWT, и в идеале любая замена должна делать то же самое.

альтернатив быть либо HTML - > PDF, либо программной моделью, основанной на абзацах и полях, которые очень похожи на HTML. iText является примером последнего. Это означало бы переписать мой существующий код, и я не уверен, что они дадут мне такую же гибкость в изложении.


Edit 2: Я не понимал раньше, но библиотека iText имеет API Graphics2D и, похоже, отлично справляется с unicode, так что это то, что я буду использовать. Хотя это не ответ на вопрос, как если меня спросят, это решит проблему.


Edit 3: iText отлично работает для меня. Думаю, урок в том, что когда сталкиваешься с чем-то, что кажется бессмысленно трудным, ищи кого-то, кто знает об этом больше тебя.

7 ответов


простой ответ заключается в том, что простого ответа нет. Если вы посмотрите на спецификацию PDF, вы увидите целую главу - и длинную - посвященную механизмам отображения текста. Я реализовал всю поддержку PDF для своей компании, и обработка текста была самой сложной частью упражнения. Решение, которое вы обнаружили-использовать стороннюю библиотеку для выполнения работы за вас-действительно лучший выбор, если у вас нет очень конкретных, специальных требований для вашего файл PDF.


в ссылке PDF в главе 3, это то, что они говорят о Unicode:

текстовые строки кодируются в либо PDFDocEncoding или кодировка символов Юникода. PDFDocEncoding является надмножество кодировки ISO Latin 1 и документировано в приложении D. Unicode описывается в стандарте Unicode консорциумом Unicode (см. Библиографию). Для текстовых строк, закодированных в Unicode, первые два байта должны быть 254 255. Эти два байта представляют маркер порядка байтов Unicode, U+FEFF, указывающий что строка кодируется в указанной схеме кодирования UTF-16BE (big-endian в стандарте Unicode. (Этот механизм исключает начало строки с помощью PDFDocEncoding с двумя символами thorn ydieresis, который вряд ли быть значимым началом слова или фразы).


Algoman составляет неправильно во многих вещах. Вы can сделайте 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 или скопировать текст из него.


см. приложение D (стр. 995) спецификации PDF. Существует ограниченное количество шрифтов и наборов символов, предварительно определенных в приложении-потребителе PDF. Для отображения других символов необходимо внедрить шрифт, который их содержит. Также предпочтительно вставлять только подмножество шрифта, включая только необходимые символы, чтобы уменьшить размер файла. Я также работаю над отображением символов Unicode в PDF, и это серьезная проблема.

Проверьте PDFBox или iText.

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


Как dredkin указал, вы должны использовать индексы глифа вместо значения символов Unicode в потоке содержимого страницы. Этого достаточно для отображения текста Юникода в формате PDF, но текст Юникода не будет доступен для поиска. Чтобы сделать текст доступным для поиска или скопировать/вставить его, вам также необходимо включить поток /ToUnicode. Этот поток должен перевести каждый символ в документ символ Юникода.


Я работал несколько дней на эту тему, и я узнал, что unicode (так же хорошо, как) невозможно в pdf. Используя 2-байтовые символы, описанный плинтус работает только с CID-шрифтами.

по-видимому, CID-шрифты являются pdf-внутренней конструкцией, и они на самом деле не являются шрифтами в этом смысле - они больше похожи на графические подпрограммы, которые можно вызвать, обратившись к ним (с 16-битными адресами).

Итак, чтобы использовать unicode в pdf напрямую

  1. вам придется конвертировать обычные шрифты в CID-шрифты, что, вероятно, очень сложно - вам придется генерировать графические процедуры из исходного шрифта (?), экстракт метрики символов и т. д.
  2. вы не можете использовать CID-шрифты, такие как обычные шрифты - вы не можете загружать или масштабировать их так, как вы загружаете и масштабируете обычные шрифты
  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 += ">";

например, " H€llo World!"может стать и теперь вы можете просто поместить эту строку в pdf и распечатать ее, используя оператор Tj, как обычно...

но теперь у вас есть проблема: pdf не знает, что вы имеете в виду "Н" с 01. Чтобы решить эту проблему, вы также должны включить кодовую страницу в файл pdf. Это делается путем добавления /кодирование к объекту шрифта и установка его различия

для " H€llo мира!"например, этот шрифт-объект будет работать:

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 ссылается в /ресурсов запись всех страниц. Если вы делаете это по-другому (например, вы используете один шрифт-регистр на странице) - Вам может потребоваться адаптировать код к вашей ситуации...

Итак, как вы находите имена глифов (/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";
        ...
    }
}

A главная проблема я оставил открытым, что это работает только до тех пор, пока вы используете не более 254 различных символов из того же шрифта. Чтобы использовать более 254 различных символов, необходимо создать несколько кодовых страниц для одного шрифта.

внутри pdf, различные кодовые страницы представленные разными шрифтами, поэтому для переключения между кодовыми страницами вам придется переключать шрифты, которые теоретически могли бы немного взорвать ваш pdf, но я, например, могу жить с этим...


Я не эксперт PDF, и (как сказал Ферруччо) спецификации PDF в Adobe должны рассказать вам все, но в моей голове возникла мысль:

вы уверены, что вы используете шрифт, который поддерживает все символы, которые вам нужны?

в нашем приложении мы создаем PDF из HTML-страниц (с сторонней библиотекой), и у нас была эта проблема с кириллическими символами...