使用Java PDFBox库编写俄语PDF

12

我正在使用一个名为PDFBox的Java库,尝试将文本写入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的源代码请点击这里:点击这里

--------------------- 2009年11月18日编辑

经过一些调查,我现在确定这是一个编码问题,可以通过使用有用的PDFBox类DictionaryEncoding来定义自己的编码来解决。

我不确定如何使用它,但这是我到目前为止尝试过的:

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

似乎我的字典填充方法不正确,因此这无法正常工作。当我使用它来编写PDF页面时,页面会呈现为空白。

DictionaryEncoding源代码在这里:点击这里


1
很明显吧?你正在将字体设置为新的WinAnsiEncoding()。Win+Ansi无法显示俄语。 - jitter
我尝试了所有可用的编码,但都没有起作用。可用的编码在此处进行了子类化: http://127.0.0.1:51381/help/nftopic/jar:file:/C:/Programs/Java/Libraries/PDFBox%20v0.8/javadoc%20v0.8.zip!/org/apache/pdfbox/encoding/Encoding.html所以问题不在字体上,而是在编码上吗? - Brad
1
12.0.0.1:51381?本地主机?那只有你能用。 - jitter
抱歉... 可用的编码在此处进行了子类化: http://www.java2s.com/Open-Source/Java-Document/PDF/PDFBox-0.7.3/org/pdfbox/encoding/Encoding.java.java-doc.htm - Brad
6个回答

6
长话短说,为了从TrueType字体中进行unicode输出,输出必须包含大量详细且看似多余的信息。归根结底,在TrueType字体内,字形存储为字形ID。这些字形ID与特定的Unicode字符相关联(如果我没记错,Unicode字形在内部可能涉及几个代码点,例如é可能涉及e和一个重音符号 - 我的记忆有点模糊)。PDF除了存在从字符串中的UTF16BE值到TrueType字体中的字形ID的映射以及从UTF16BE值到Unicode的映射(即使是相同的),实际上并没有Unicode支持。

  • 具有以下条目的Subtype Type0字典的Font字典
    • DescendantFonts数组中的一个条目,其描述如下
    • 将UTF16BE值映射到Unicode的ToUnicode条目
    • 设置为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 % 请注意流的格式有误

  • 一个Subtype为CIDFontType2的字体字典,其中包含:
    • CIDSsytemInfo
    • FontDescriptor
    • DW和W参数
    • CIDToGIDMap,将字符ID映射到字形ID

以下是来自同一测试的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有什么关系?只是因为:在PDF中使用Unicode输出实际上是一件非常麻烦的事情。Acrobat在Unicode出现之前就已经开发了,没有Unicode的CJK编码非常困难(我知道-我当时在Acrobat上工作)。后来添加了Unicode支持,但感觉像是附加上去的。人们希望只需说/Encoding /Unicode并且以thorn和y-dieresis字符开头的字符串即可。但不幸的是,如果您不放入每一个详细信息(真的,Acrobat,嵌入PostScript程序来转换为Unicode?WTH?),则在Acrobat中会得到空白页面。我发誓,我没有编造这个。

此时,我为另一家公司编写PDF生成工具(现在是.NET,所以对您没有帮助),而我将其设计目标设置为隐藏所有这些废话。所有文本都是Unicode-如果您只使用与WinAnsi相同的字符代码,那就是您在底层得到的东西。使用其他任何内容,您都会得到其他所有东西。我会惊讶PDFBox是否为您完成了这项工作-这是一项严重的麻烦。


1

尝试使用这个结构:

PDFont font = PDType0Font.load( pdfFile, new File( "fonts/VREMACCI.TTF" ) );  // Windows Russian font imported to write the Russian text.
// Some code here to open the PDF & define a new page.
contentStream.beginText();
contentStream.setFont(font, 12);
contentStream.showText( "отделом компьютерной" ); // Write the Russian text.
contentStream.endText();

当我提出这个问题的时候,PDFBox只支持写拉丁语系的语言,因此不支持写俄语。现在,创建PDFBox的人已经解决了这个问题,它现在支持俄语和其他语言,感谢他们,也感谢您分享的解决方案 :) - Brad

1
这是一个非常简单的解决方案。
1)您必须找到与您想要显示的字符兼容的字体。
2)下载字体的.ttf文件并保存在本地。
3)从应用程序中加载字体。
例如,如果您要使用希腊字符,则需要执行以下操作:
content = new PDPageContentStream(document, page);
pdfFont = PDType0Font.load( document, new File( "arialuni.ttf" ) )
content.setFont(pdfFont, fontSize);

0

测试是否存在编码问题应该很容易(只需切换到UTF16编码)。

我假设您已经尝试使用带有VREMACCI字体的编辑器或其他工具,并确认它显示的方式符合您的预期?

您可能想尝试在iText中执行相同的操作,以了解问题是否与PdfBox库本身相关... 如果您的主要目标是生成PDF文件,则iText可能是更好的解决方案。

编辑-对评论的长回答:

好的-对于编码问题的来回讨论感到抱歉... 您的核心问题(您可能已经知道)是写入内容流的字节的编码与用于查找字形的编码不同。现在我将尝试实际提供帮助:

我查看了PdfBox中的字典编码类,它看起来非常不直观... 所谓的“字典”是一个PDF字典。因此,您基本上需要创建一个Pdf字典对象(我认为PdfBox将其称为COSObject类型),然后向其中添加条目。

在PDF中,字体的编码被定义为一个字典(请参见上述规范的第266页)。该字典包含一个基本编码名称,以及一个可选的差异数组。从技术上讲,差异数组不应与TrueType字体一起使用(尽管我在某些情况下看到过它被使用-但不要使用它)。

然后,您将为编码指定cmap条目。这个cmap将是您的字体的编码。

我的建议是使用现有的PDF文件,然后获取字体的字典结构转储,以便您可以看到它的样子。

这绝对不是给心脏脆弱的人。如果您需要字典转储,请向我发送带有示例PDF的超链接,我将通过我在iText开发中使用的一些算法运行它(我是iText文本提取子系统的维护者)。

编辑-11/17/09

好的-这是来自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个字符...除了TT字体之外,这里使用了很多type-1字体,因此很难确定您特定问题的涉及内容。

(您确定不想至少尝试一下iText吗?;-) 我不是说它一定会起作用,只是说这可能值得一试)。

供参考,上述词典转储是使用com.lowagie.text.pdf.parser.PdfContentReaderTool类获得的。


如果您使用PDFBox解析所创建的内容,能否恢复文本?如果可以,那么这可能不是编码的限制,也许这只是PDFBox将字节元组映射到字形的问题? - Kevin Day
你说的“恢复”是什么意思?我可以写一些其他的外语,比如法语、德语等,但是俄语似乎是个问题。我相信这是一个编码问题。而类DictionaryEncoding是为了允许扩展其他不支持的编码而创建的,但我仍然无法弄清如何使用它。 - Brad
我尝试了一下,它返回了我输入的相同文本,所以A=A返回true。另一方面,我不明白编码和字形转换之间的区别...我以为它们是一样的东西。当我与图书馆管理员交谈时,他说:“问题在于要添加的字符串与字体的编码之间的映射。据我所知,用作TrueType字体默认编码的WinAnsiEncoding不包含俄文字母。因此,最终你必须找到另一种映射方式。你应该能够使用DictionaryEncoding定义自己的映射。” - Brad
好的。反向工程字体字典可能需要三个月的时间,不过你至少可以尝试一下iText,看看它是否能更好地工作。我知道在比赛进行到一半时改变策略很难,但有时你必须咬紧牙关(我自己曾经在发现PdfBox不支持xref流时做出了这个改变)。 - Kevin Day
这是一个非常艰难的决定,但我认为我必须这样做。我会尝试的,希望我不会后悔。 - Brad
显示剩余7条评论

0

或许需要编写俄罗斯编码类,它应该看起来像 WinAnsiEncoding 类一样,我想。
现在,我不知道应该放什么!

或者,如果这不是你已经做的事情,也许你应该将源文件编码为 UTF-8 并使用默认编码。
我看到了一些相关的消息,说从现有的 PDF 文件中提取俄语文本存在问题(当然是使用 PDFBox),但我不知道输出是否相关。
您还可以写信给 PDFBox 邮件列表。


提取俄语文本使用PDFBox很好,问题在于将俄语文本写入PDF。 - Brad
为了编写俄语编码,有一个名为DictionaryEncoding类的工具可以让我定义自己的编码...但是对我来说它似乎像一个迷宫: http://kickjava.com/src/org/pdfbox/encoding/DictionaryEncoding.java.htm - Brad

-1

试试这个:

Phrase leftTitle = new Phrase("САНКТ-ПЕТЕРБУРГ", FontFactory.getFont("Tahoma", "Cp1251", true, 25));

这至少可以在最新的(5.0.1)iText中使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接