【问题标题】:Why character ID 160 is not recognised as Unicode in PDFMiner?为什么字符 ID 160 在 PDFMiner 中不被识别为 Unicode?
【发布时间】:2015-12-05 17:38:00
【问题描述】:

我正在使用 PDFMiner 将 .pdf 文件转换为 .xml 文件。

对于 .pdf 文件中的每个单词,PDFMiner 会检查它是否是 Unicode(以及许多其他内容)。如果是,则返回字符,如果不是,则引发异常并返回字符串“(cid:%d)”,其中 %d 是字符 id,我认为是 Unicode Decimal。

这在这个问题的编辑部分得到了很好的解释: What is this (cid:51) in the output of pdf2txt?。为了方便,我在这里报告代码:

def render_char(self, matrix, font, fontsize, scaling, rise, cid):
    try:
        text = font.to_unichr(cid)
        assert isinstance(text, unicode), text
    except PDFUnicodeNotDefined:
        text = self.handle_undefined_char(font, cid)


def handle_undefined_char(self, font, cid):
    if self.debug:
        print >>sys.stderr, 'undefined: %r, %r' % (font, cid)
    return '(cid:%d)' % cid

对于用西里尔文编写的 .pdf 文件,我通常会收到此异常。但是,有一个文件使用简单的英语,并且我在其中得到了非中断空格(具有 cid=160)的异常。我不明白为什么这个字符不被识别为 Unicode,而同一文件中的所有其他字符都是。

如果在同一环境中,我在控制台中运行isinstance(u'160', unicode),我得到True,而(显然)等效命令在PDFMiner 中运行时返回False

如果我调试,我看到字体被正确识别,即我得到:

cid = 160
font =  <PDFType1Font: basefont='Helvetica'>

PDFMiner 接受编解码器作为参数。我选择了 utf-8,它有 160 作为 Unicode Decimal 用于不间断空格 (http://dev.networkerror.org/utf8/)。

如果有帮助,这里是 to_unichr 的代码:

def to_unichr(self, cid):
    if self.unicode_map:
        try:
            return self.unicode_map.get_unichr(cid)
        except KeyError:
            pass
    try:
        return self.cid2unicode[cid]
    except KeyError:
        raise PDFUnicodeNotDefined(None, cid)

有没有办法设置/更改代码识别的字符映射?

你认为我应该改变什么,或者你认为我应该在哪里调查,以便 cid=160 不会引发异常?

【问题讨论】:

  • 请分享有问题的PDF进行分析。
  • 这里是test file。运行 pdf2txt.py -o cidIssue_160_4times.xml cidIssue_160_4times.pdf 时,我得到了 4 次字符串“(cid:160)”。它们可以在 .xml 文件的第 4264、4266、4269、4272 行找到。

标签: python pdf utf-8 python-unicode pdfminer


【解决方案1】:

示例文档中有问题的字体是简单字体并使用 WinAnsiEncoding。此编码在 PDF 规范 ISO 32000-1 中定义为附件 D.2 拉丁字符集和编码中表格中的四种特殊编码之一。此表WIN 列中包含 240 条目(= 十进制 160。表格条目以八进制数给出!)。

该表被提取为 latin_enc.py 中的 ENCODING 数组,并从该数组中生成这四种编码的映射在 encodingdb.py 中,然后是使用,例如对于具有这种编码的字体,请参见 pdffont.py 中的 PDFSimpleFont

因此,PdfMiner 无法将代码 160 识别为在 WinAnsiEncoding 中具有任何关联字符。这会导致您的问题。


只看似乎正确的表格,但如果阅读表格下方的注释,就会发现:

  1. SPACE 字符在 MacRomanEncoding 中也应编码为 312,在 WinAnsiEncoding。该重复代码应表示不间断的空格;它应该是印刷的 与 (U+003A) 空间相同。

PdfMiner 开发似乎忽略了这一点。

可以通过为 space 添加第二个条目来解决此疏忽

('nbspace', None, 202, 160, None)

ENCODING 数组(使用十进制数);如果您愿意,可以改用space

(我之所以说可能,是因为我不熟悉 Python 编程,因此无法检查,尤其是检查不需要的副作用。)

【讨论】:

  • [很好的答案,谢谢!] encodingdb.py 采用 latin_enc.py 中的字符名称和该名称的 unciode 值glyphlist.py。所以我相信,如果将 nbspace 行添加到 latin_enc.py,那么还应该在 glyphlist.py 中添加以下行:'nbspace': u'\xa0'。 “空格”已经存在于编码表中(四种编码的 cid = 32)。所以我认为最好将它们视为两个单独的条目。
  • 实际上 glyphlist.py 已经包含键 'nbspace',其值为:'u\u00A0'。
  • 其实我只是在glyphlist.py中找到nbspace之后才提议添加('nbspace', None, 202, 160, None)这一行,在此之前我想提议添加('space', ...)
【解决方案2】:

对不同文件中的类似字符适用的一种解决方案是使用ftfy.fix_text()。我被这个修复 mojibake 的包吸引到了 pdf 的 unicode 中,基本上是你典型的不同编码之间的弯引号。 Pdfminer 将它们捕获为“(cid:146)”等,但我想进一步清理它们。到目前为止,此类适用于该文件;它包括使其打印某些内容的最低要求,但工作模块中可能会有更多的 pdfminer 元素。如果有人使用pdf2txt.py,也许可以将副本放在安全的地方,将pdfminer.high_level.extract_text_to_fp(fp, **locals()) 行重定向到该模块的安全副本,将此类添加到该模块的末尾,并将其交换为它继承的基类。我刚刚完成了 HTMLConverter,但其他的可能可以类似地处理。

from pdfminer.converter import HTMLConverter
from io                 import BytesIO
class HTMLConvertOre(HTMLConverter):
    import ftfy, six
    from pdfminer.layout    import LTChar
    from pdfminer.pdffont   import PDFUnicodeNotDefined
    def __init__(self, rsrcmgr, outfp, codec='utf-8', pageno=1, laparams=None,
                 scale=1, fontscale=1.0, layoutmode='normal', showpageno=True,
                 pagemargin=50, imagewriter=None, debug=0,
                 rect_colors={'curve': 'black', 'page': 'gray'},
                 text_colors={'char': 'black'}):
        """Initialize pdfminer.converter HTMLConverter."""
        HTMLConverter.__init__(**locals())
    def render_char(self, matrix, font, fontsize, scaling, rise, cid, ncs,
                    graphicstate):
        """Mod invoking ftfy.fix_text() to possibly rescue bad cids."""
        try:
            text = font.to_unichr(cid)
            assert isinstance(text, six.text_type), str(type(text))
        except PDFUnicodeNotDefined:
            try:
                text = ftfy.fix_text(chr(cid), uncurl_quotes=False)
                assert isinstance(text, six.text_type), str(type(text))
                cid=ord(text)
            except PDFUnicodeNotDefined:
                text = self.handle_undefined_char(font, cid)
        textwidth = font.char_width(cid)
        textdisp = font.char_disp(cid)
        item = LTChar(matrix, font, fontsize, scaling, rise, text, textwidth,
                      textdisp, ncs, graphicstate)
        self.cur_item.add(item)
        return item.adv
if __name__ == '__main__':
    rsrcmgr = PDFResourceManager()
    outfp = BytesIO()
    device = HTMLConvertOre(rsrcmgr, outfp)
    print(device)

【讨论】:

    【解决方案3】:

    对于遇到上述错误的人,下面的代码可能会对您有所帮助。

    import minecart
    from PIL import Image
    import io
    
    pdffile = open('sample.pdf', 'rb')
    doc = minecart.Document(pdffile)
    
    for page in doc.iter_pages():
        im = page.images[0]#taking only one image per page
        byteArray = im.obj.get_data()
        image = Image.open(io.BytesIO(byteArray))
        image.show()
    

    希望对你有帮助!

    请参考https://github.com/felipeochoa/minecart/issues/16

    【讨论】:

    • 这和字符有什么关系?
    • 我做到了。 github 问题和您的代码是关于提取图像的。这个问题是关于 pdfminer 如何处理特定字符的。我看不到连接。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-23
    • 2021-07-01
    • 2021-07-30
    • 1970-01-01
    • 2022-11-26
    • 1970-01-01
    相关资源
    最近更新 更多