Использование библиотеки Java PDFBox для написания русского PDF файла

Я использую библиотеку Java под названием PDFBox, пытаясь записать текст в PDF. Он отлично подходит для английского текста, но когда я пытался писать русский текст внутри PDF, письма выглядели настолько странно. Кажется, проблема в используемом шрифте, но я не настолько уверен в этом, поэтому надеюсь, что кто-нибудь сможет мне помочь в этом. Вот важные строки кода:

PDTrueTypeFont font = PDTrueTypeFont.loadTTF( pdfFile, new File( "fonts/VREMACCI.TTF" ) );  // Windows Russian font imported to write the Russian text.
font.setEncoding( new WinAnsiEncoding() );  // Define the Encoding used in writing.
// Some code here to open the PDF & define a new page.
contentStream.drawString( "отделом компьютерной" ); // Write the Russian text.

Исходный код WinAnsiEncoding: Нажмите здесь

--------------------- Редактировать 18 ноября 2009

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

Я не уверен, как его использовать, но вот что я пробовал до сих пор:

COSDictionary cosDic = new COSDictionary();
cosDic.setString( COSName.getPDFName("Ercyrillic"), "0420 " ); // Russian letter.
font.setEncoding( new DictionaryEncoding( cosDic ) );

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

Исходный код DictionaryEncoding: Нажмите здесь

Ответ 1

Длинная история заключается в том, что для вывода unicode в формате PDF из шрифта TrueType вывод должен содержать тонну подробной и, казалось бы, излишней информации. В чем дело - внутри шрифта TrueType глифы хранятся как идентификаторы глифов. Эти идентификаторы глифов связаны с конкретным символом юникода (и IIRC, один символ Unicode внутри может ссылаться на несколько кодовых точек - например, eacute, ссылаясь на e и острый акцент - моя память туманна). PDF действительно не поддерживает юникод, кроме как сказать, что существует сопоставление значений UTF16BE в строке с идентификаторами глифов в шрифте TrueType, а также отображение значений UTF16BE в Unicode - даже если оно идентично.

  • Словарь шрифтов подтипа Type0 с
    • массив DescendantFonts с описанием, описанным ниже
    • запись ToUnicode, которая отображает значения UTF16BE в unicode
    • Кодировка установлена ​​в Identity-H

Результат одного из моих модульных тестов в моих собственных инструментах выглядит следующим образом:

13 0 obj
<< 
   /BaseFont /DejaVuSansCondensed 
   /DescendantFonts [ 4 0 R  ]   
   /ToUnicode 14 0 R 
   /Type /Font 
   /Subtype /Type0 
   /Encoding /Identity-H 
>> endobj

14 0 obj
<< /Length 346 >> stream
/CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo <<
/Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def /CMapName /Adobe-Identity-UCS
def /CMapType 2 def 1 begincodespacerange <0000> <FFFF> endcodespacerange 1
beginbfrange <0000> <FFFF> <0000> endbfrange endcmap CMapName currentdict /CMap
defineresource pop end end

endstream% обратите внимание, что форматирование неверно для потока

  • словарь шрифтов подтипа CIDFontTYpe2 с
    • a CIDSsytemInfo
    • a FontDescriptor
    • DW и W
    • CIDToGIDMap, который сопоставляет идентификатор персонажа с идентификатором глифа

Здесь один из одного теста - это объект в массиве DescendantFonts:

4 0 obj
<< 
   /Subtype /CIDFontType2 
   /Type /Font 
   /BaseFont /DejaVuSansCondensed 
   /CIDSystemInfo 8 0 R 
   /FontDescriptor 9 0 R 
   /DW 1000 
   /W 10 0 R 
   /CIDToGIDMap 11 0 R 
>>

8 0 obj
<< 
   /Registry (Adobe)
   /Ordering (UCS)
   /Supplement 0 
>>
endobj

Почему я говорю вам это? Что это связано с PDFBox? Только это: выход Unicode в PDF - это, откровенно говоря, королевская боль в прикладе. Acrobat был разработан до того, как появился Unicode, и с самого начала было больно кодировать CJK без Unicode (я знаю - тогда я работал над Acrobat). Более поздняя поддержка Unicode была добавлена, но на самом деле было похоже, что она была задумана. Можно было бы надеяться, что вы просто скажете/Кодирование/Юникод и получите строки, которые начинаются с символов шипа и y-dieresis и от вас. Нет такой удачи. Если вы не ставите каждую деталь (и действительно, Acrobat, внедряя программу PostScript для перевода в Unicode? WTH?), Вы получаете пустую страницу в Acrobat. Клянусь, я этого не делаю.

На этом этапе я пишу инструменты для создания PDF файлов для отдельной компании (.NET сейчас, так что это вам не поможет), и я поставил перед собой цель дизайна, чтобы скрыть всю эту глупость. Весь текст является unicode - если вы используете только те коды символов, которые являются одним и тем же WinAnsi, это то, что вы получаете под капотом. Используйте что-нибудь еще, вы получите все это. Я был бы удивлен, если PDFBox сделает это для вас - это серьезная проблема.

Ответ 2

Возможно, русский язык должен быть написан, он должен выглядеть как WinAnsiEncoding один, я полагаю.
Теперь я понятия не имею, что туда положить!

Или, если это не то, что вы уже делаете, возможно, вы должны закодировать исходный файл в UTF-8 и использовать кодировку по умолчанию.
Я видел некоторые сообщения, связанные с проблемами с извлечением русского текста из существующих файлов PDF (с использованием PDFBox, конечно), но я не знаю, связан ли выход.
Вы также можете записать список рассылки PDFBox.

Ответ 3

Проверка того, является ли это проблемой кодирования, довольно легко сделать (просто переключитесь на кодировку UTF16).

Я предполагаю, что вы попытались использовать редактор или что-то с шрифтом VREMACCI и подтвердили, что он показывает, как вы его ожидаете?

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

EDIT - длинный ответ на комментарии:

ok - извините за вопрос, связанный с кодировкой... Ваша основная проблема (которую вы, вероятно, уже знаете) заключается в том, что кодирование байтов, записываемых в поток контента, отличается от кодировки, используемой для просмотра вверх глифы. Теперь я попытаюсь на самом деле быть полезным:

Я взглянул на класс кодирования словаря в PdfBox, и он выглядит совершенно неинтуитивно... Под "словарем" речь идет о словаре PDF. Так что вам в основном нужно будет создать объект словаря Pdf (я думаю, что PdfBox называет это типом COSObject), а затем добавлять записи к нему.

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

Затем вы укажете запись для cmap для кодировки. Этот cmap будет кодировкой вашего шрифта.

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

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

EDIT - 11/17/09

OK - здесь дамп словаря из файла russian.pdf(суб-словари перечислены в отступе и в порядке, указанном в содержащем словаре):

(/CropBox=[0, 0, 595, 842], /Parent=Dictionary of type: /Pages, /Type=/Page, /Contents=[209 0 R, 210 0 R, 211 0 R, 214 0 R, 215 0 R, 216 0 R, 222 0 R, 223 0 R], /Resources=Dictionary, /MediaBox=[0, 0, 595, 842], /StructParents=0, /Rotate=0)
    Subdictionary /Parent = (/Type=/Pages, /Count=6, /Kids=[195 0 R, 1 0 R, 3 0 R, 5 0 R, 7 0 R, 9 0 R])
    Subdictionary /Resources = (/ExtGState=Dictionary, /ProcSet=[/PDF, /Text], /ColorSpace=Dictionary, /Font=Dictionary, /Properties=Dictionary)
        Subdictionary /ExtGState = (/GS0=Dictionary of type: /ExtGState)
            Subdictionary /GS0 = (/OPM=1, /op=false, /Type=/ExtGState, /SA=false, /OP=false, /SM=0.02)
        Subdictionary /ColorSpace = (/CS0=[/ICCBased, 228 0 R])
        Subdictionary /Font = (/C2_1=Dictionary of type: /Font, /C2_2=Dictionary of type: /Font, /C2_3=Dictionary of type: /Font, /C2_4=Dictionary of type: /Font, /TT2=Dictionary of type: /Font, /TT1=Dictionary of type: /Font, /TT0=Dictionary of type: /Font, /C2_0=Dictionary of type: /Font, /TT3=Dictionary of type: /Font)
            Subdictionary /C2_1 = (/DescendantFonts=[243 0 R], /BaseFont=/LDMIEC+TimesNewRomanPS-BoldMT, /Type=/Font, /Subtype=/Type0, /Encoding=/Identity-H, /ToUnicode=Stream)
            Subdictionary /C2_2 = (/DescendantFonts=[233 0 R], /BaseFont=/LDMIBO+TimesNewRomanPSMT, /Type=/Font, /Subtype=/Type0, /Encoding=/Identity-H, /ToUnicode=Stream)
            Subdictionary /C2_3 = (/DescendantFonts=[224 0 R], /BaseFont=/LDMIHD+TimesNewRomanPS-ItalicMT, /Type=/Font, /Subtype=/Type0, /Encoding=/Identity-H, /ToUnicode=Stream)
            Subdictionary /C2_4 = (/DescendantFonts=[229 0 R], /BaseFont=/LDMIDA+Tahoma, /Type=/Font, /Subtype=/Type0, /Encoding=/Identity-H, /ToUnicode=Stream)
            Subdictionary /TT2 = (/LastChar=58, /BaseFont=/LDMIFC+TimesNewRomanPS-BoldMT, /Type=/Font, /Subtype=/TrueType, /Encoding=/WinAnsiEncoding, /Widths=[250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 333], /FontDescriptor=Dictionary of type: /FontDescriptor, /FirstChar=32)
                Subdictionary /FontDescriptor = (/Type=/FontDescriptor, /StemV=136, /Descent=-216, /FontWeight=700, /FontBBox=[-558, -307, 2000, 1026], /CapHeight=656, /FontFile2=Stream, /FontStretch=/Normal, /Flags=34, /XHeight=0, /FontFamily=Times New Roman, /FontName=/LDMIFC+TimesNewRomanPS-BoldMT, /Ascent=891, /ItalicAngle=0)
            Subdictionary /TT1 = (/LastChar=187, /BaseFont=/LDMICP+TimesNewRomanPSMT, /Type=/Font, /Subtype=/TrueType, /Encoding=/WinAnsiEncoding, /Widths=[250, 0, 0, 0, 0, 833, 778, 0, 333, 333, 0, 0, 250, 333, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 0, 564, 0, 444, 0, 722, 667, 667, 722, 611, 556, 0, 722, 333, 389, 0, 611, 889, 722, 722, 556, 0, 667, 556, 611, 0, 722, 944, 0, 722, 0, 333, 0, 333, 0, 500, 0, 444, 500, 444, 500, 444, 333, 500, 500, 278, 0, 500, 278, 778, 500, 500, 500, 0, 333, 389, 278, 500, 500, 722, 0, 500, 444, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 500, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 500, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 500], /FontDescriptor=Dictionary of type: /FontDescriptor, /FirstChar=32)
                Subdictionary /FontDescriptor = (/Type=/FontDescriptor, /StemV=82, /Descent=-216, /FontWeight=400, /FontBBox=[-568, -307, 2000, 1007], /CapHeight=656, /FontFile2=Stream, /FontStretch=/Normal, /Flags=34, /XHeight=0, /FontFamily=Times New Roman, /FontName=/LDMICP+TimesNewRomanPSMT, /Ascent=891, /ItalicAngle=0)
            Subdictionary /TT0 = (/LastChar=55, /BaseFont=/LDMIBN+TimesNewRomanPS-BoldItalicMT, /Type=/Font, /Subtype=/TrueType, /Encoding=/WinAnsiEncoding, /Widths=[250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 0, 500, 500, 500, 0, 0, 0, 0, 500], /FontDescriptor=Dictionary of type: /FontDescriptor, /FirstChar=32)
                Subdictionary /FontDescriptor = (/Type=/FontDescriptor, /StemV=116.867004, /Descent=-216, /FontWeight=700, /FontBBox=[-547, -307, 1206, 1032], /CapHeight=656, /FontFile2=Stream, /FontStretch=/Normal, /Flags=98, /XHeight=468, /FontFamily=Times New Roman, /FontName=/LDMIBN+TimesNewRomanPS-BoldItalicMT, /Ascent=891, /ItalicAngle=-15)
            Subdictionary /C2_0 = (/DescendantFonts=[238 0 R], /BaseFont=/LDMHPN+TimesNewRomanPS-BoldItalicMT, /Type=/Font, /Subtype=/Type0, /Encoding=/Identity-H, /ToUnicode=Stream)
            Subdictionary /TT3 = (/LastChar=169, /BaseFont=/LDMIEB+Tahoma, /Type=/Font, /Subtype=/TrueType, /Encoding=/WinAnsiEncoding, /Widths=[313, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 546, 0, 546, 0, 0, 546, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 929], /FontDescriptor=Dictionary of type: /FontDescriptor, /FirstChar=32)
                Subdictionary /FontDescriptor = (/Type=/FontDescriptor, /StemV=92, /Descent=-206, /FontWeight=400, /FontBBox=[-600, -208, 1338, 1034], /CapHeight=734, /FontFile2=Stream, /FontStretch=/Normal, /Flags=32, /XHeight=546, /FontFamily=Tahoma, /FontName=/LDMIEB+Tahoma, /Ascent=1000, /ItalicAngle=0)
        Subdictionary /Properties = (/MC0=Dictionary of type: /OCMD)
            Subdictionary /MC0 = (/Type=/OCMD, /OCGs=Dictionary of type: /OCG)
                Subdictionary /OCGs = (/Usage=Dictionary, /Type=/OCG, /Name=HeaderFooter)
                    Subdictionary /Usage = (/CreatorInfo=Dictionary, /PageElement=Dictionary)
                        Subdictionary /CreatorInfo = (/Creator=Acrobat PDFMaker 6.0 äëÿ Word)
                        Subdictionary /PageElement = (/SubType=/HF)

здесь много движущихся частей. вам может понадобиться собрать тестовый документ, который имеет только 3 или 4 символа в рассматриваемом шрифте... Существует много шрифтов типа 1, которые используются здесь (в дополнение к шрифтам TT), поэтому трудно сказать что связано с вашей конкретной проблемой.

(Вы уверены, что не хотите, по крайней мере, попробовать это с iText?;-) Я не говорю, что это сработает, просто это может стоить того выстрела).

Для справки, вышеупомянутый дамп словаря был получен с использованием класса com.lowagie.text.pdf.parser.PdfContentReaderTool

Ответ 4

Просто попробуйте следующее:

Фраза lefttitle= новая фраза ( "САНКТ-ПЕТЕРБУРГ", FontFactory.getFont( "Tahoma", "Cp1251", true, 25));

Это будет работать, по крайней мере, с последним (5.0.1) iText