PyMuPDF 1.24.4 中文文档(二)(2)https://developer.aliyun.com/article/1559643
文本
如何提取所有文档文本
此脚本将接受文档文件名并生成其所有文本的文本文件。
文档可以是任何 支持的类型。
脚本作为一个命令行工具运行,期望以文档文件名作为参数提供。它在脚本目录中生成一个名为“filename.txt”的文本文件。页面的文本由一个表单馈送字符分隔:
import sys, pathlib, pymupdf fname = sys.argv[1] # get document filename with pymupdf.open(fname) as doc: # open document text = chr(12).join([page.get_text() for page in doc]) # write as a binary file to support non-ASCII characters pathlib.Path(fname + ".txt").write_bytes(text.encode())
输出将是文档中编码的纯文本。没有任何美化的努力。特别是对于 PDF,这可能意味着输出不按照通常的阅读顺序,出现意外的换行等。
你有很多选项来纠正这一点 - 参见附录 2:关于嵌入文件的考虑章节。其中包括:
- 以 HTML 格式提取文本并将其存储为 HTML 文档,以便在任何浏览器中查看。
- 通过 Page.get_text(“blocks”) 提取文本作为文本块列表。列表中的每个条目包含其文本的位置信息,可用于建立方便的阅读顺序。
- 通过 Page.get_text(“words”) 提取单词列表。其条目是带有位置信息的单词。用它来确定给定矩形中包含的文本 - 请参见下一节。
请参见以下两个部分的示例和进一步解释。 ## 如何将文本提取为 Markdown
这对于 RAG/LLM 环境特别有用 - 请参见输出为 Markdown。 ## 如何从页面中提取键-值对
如果页面的布局在某种意义上是 “可预测的”,那么有一种简单的方法可以快速轻松地找到给定关键字集的值 - 而不使用正则表达式。请参见此示例脚本。
在此上下文中,“可预测的”意味着:
- 每个关键字后跟其值 - 没有其他文本出现在它们之间。
- 值的边界框底部 不在 关键字的底部上方。
- 没有 其他限制:页面布局可以固定也可以不固定,文本也可能已存储为一个字符串。键和值之间可能具有任何距离。
例如,以下五个键-值对将被正确识别:
key1 value1 key2 value2 key3 value3 blah, blah, blah key4 value4 some other text key5 value5 ... ```## 如何从矩形中提取文本 现在(v1.18.0)有超过一种方法可以实现这一点。因此,我们在 PyMuPDF-Utilities 仓库中创建了一个专门处理此主题的 [文件夹](https://github.com/pymupdf/PyMuPDF-Utilities/tree/master/textbox-extraction)。 * * * ## 如何提取自然阅读顺序中的文本 PDF 文本提取的一个常见问题是,文本可能不会按照任何特定的阅读顺序出现。 这是 PDF 创建者(软件或人类)的责任。例如,页面标题可能是在文档生成之后的单独步骤中插入的。在这种情况下,页眉文本将出现在页面文本提取的末尾(尽管它将由 PDF 查看器软件正确显示)。例如,以下代码段将向现有的 PDF 添加一些页眉和页脚行: ```py doc = pymupdf.open("some.pdf") header = "Header" # text in header footer = "Page %i of %i" # text in footer for page in doc: page.insert_text((50, 50), header) # insert header page.insert_text( # insert footer 50 points above page bottom (50, page.rect.height - 50), footer % (page.number + 1, doc.page_count), )
以这种方式修改的页面提取的文本序列如下所示:
- 原始文本
- 标题行
- 页脚行
PyMuPDF 有几种方法来重新建立一些阅读顺序,甚至重新生成接近原始的布局:
- 使用
Page.get_text()
的sort
参数。它将输出从左上到右下排序的输出(对 XHTML、HTML 和 XML 输出不起作用)。 - 在 CLI 中使用
pymupdf
模块:python -m pymupdf gettext ...
,它会产生一个文本文件,其中的文本已经重新排列成保留布局的模式。有许多选项可用于控制输出。
您也可以使用上述提到的脚本进行修改。
- 如何从文档中提取表格内容
如果你在文档中看到一个表格,通常不会看到像嵌入式 Excel 或其他可识别对象一样的东西。它通常只是普通的标准文本,格式化为看起来像表格数据。
因此,从这样一个页面区域提取表格数据意味着你必须找到一种方法来识别表格区域(即其边界框),然后**(1)图形地指示表格和列边界,然后(2)**根据此信息提取文本。
这可能是一个非常复杂的任务,具体取决于细节,如线条的存在或不存在,矩形或其他支持矢量图形的存在。
方法Page.find_tables()
会为您完成所有这些工作,并具有高精度的表检测。它的巨大优势是没有外部库依赖,也不需要使用人工智能或机器学习技术。它还提供了与著名的 Python 数据分析包pandas的集成接口。
请查看示例Jupyter notebooks,其中涵盖了标准情况,比如一页上有多个表格或跨多个页面连接表格片段。
- 如何标记提取的文本
有一个标准搜索功能,可以在页面上搜索任意文本:Page.search_for()
。它返回一个围绕找到的出现的 Rect 对象列表。这些矩形可以用来自动插入可见标记找到的文本的注释。
这种方法有利有弊。优点是:
- 搜索字符串可以包含空格,并且可以跨行换行
- 大写或小写字符都被视为相等
- 在行末的单词连字符会被检测和解决
- 返回也可能是 Quad 对象的列表,以精确定位不与任何轴平行的文本-当页面旋转不为零时,也建议使用 Quad 输出。
但您也有其他选择:
import sys import pymupdf def mark_word(page, text): """Underline each word that contains 'text'. """ found = 0 wlist = page.get_text("words", delimiters=None) # make the word list for w in wlist: # scan through all words on page if text in w[4]: # w[4] is the word's string found += 1 # count r = pymupdf.Rect(w[:4]) # make rect from word bbox page.add_underline_annot(r) # underline return found fname = sys.argv[1] # filename text = sys.argv[2] # search string doc = pymupdf.open(fname) print("underlining words containing '%s' in document '%s'" % (word, doc.name)) new_doc = False # indicator if anything found at all for page in doc: # scan through the pages found = mark_word(page, text) # mark the page's words if found: # if anything found ... new_doc = True print("found '%s' %i times on page %i" % (text, found, page.number + 1)) if new_doc: doc.save("marked-" + doc.name)
此脚本使用 Page.get_text("words")
来查找通过 cli 参数传递的字符串。该方法使用空格作为分隔符将页面文本分割为“单词”。进一步说明:
- 如果找到,则包含字符串的完整单词将被标记(下划线)-不仅仅是搜索字符串。
- 搜索字符串可能不包含单词分隔符。默认情况下,单词分隔符是空格和不间断空格
chr(0xA0)
。如果您使用额外的分隔字符(例如page.get_text("words", delimiters="./,")
)则搜索字符串中也不应包含其中任何字符。 - 如图所示,大小写是保留的。但是这可以通过使用字符串方法lower()(甚至正则表达式)在函数mark_word中进行更改。
- 没有上限:所有出现都将被检测到。
- 您可以使用任何东西来标记单词:‘下划线’、‘高亮’、‘删除线’或‘方框’注释等。
- 这是本手册页面的示例片段,其中“MuPDF”已用作搜索字符串。请注意,所有包含“MuPDF”的字符串都已完全下划线标记(不仅仅是搜索字符串)。
- 如何标记搜索到的文本
此脚本搜索文本并标记它:
# -*- coding: utf-8 -*- import pymupdf # the document to annotate doc = pymupdf.open("tilted-text.pdf") # the text to be marked needle = "¡La práctica hace el campeón!" # work with first page only page = doc[0] # get list of text locations # we use "quads", not rectangles because text may be tilted! rl = page.search_for(needle, quads=True) # mark all found quads with one annotation page.add_squiggly_annot(rl) # save to a new PDF doc.save("a-squiggly.pdf")
结果如下:
- 如何标记非水平文本
上一节已经展示了一个示例,用于标记非水平文本,该文本是通过文本搜索检测到的。
但使用Page.get_text()
的“dict”/“rawdict”选项进行文本提取也可能返回具有非零角度到 x 轴的文本。这由线字典的"dir"
键的值指示:它是该角度的元组 (cosine, sine)
。如果 line["dir"] != (1, 0)
,则所有跨度的文本都将被(相同的)非零角度旋转。
但是,该方法返回的“bboxes”仅为矩形-不是四边形。因此,为了正确标记跨度文本,必须从包含在线和跨度字典中的数据中恢复其四边形。使用以下实用程序函数执行此操作(v1.18.9 中的新功能):
span_quad = pymupdf.recover_quad(line["dir"], span) annot = page.add_highlight_annot(span_quad) # this will mark the complete span text
如果您想要一次性标记完整行或其子集的跨度,请使用以下片段(适用于 v1.18.10 或更高版本):
line_quad = pymupdf.recover_line_quad(line, spans=line["spans"][1:-1]) page.add_highlight_annot(line_quad)
上面的spans
参数可以指定line["spans"]
的任何子列表。在上面的示例中,从第二个到倒数第二个 span 被标记了。如果省略,将取整个行。
- 如何分析字体特性
要分析 PDF 中文本的特性,请使用这个基本脚本作为起点:
import sys import pymupdf def flags_decomposer(flags): """Make font flags human readable.""" l = [] if flags & 2 ** 0: l.append("superscript") if flags & 2 ** 1: l.append("italic") if flags & 2 ** 2: l.append("serifed") else: l.append("sans") if flags & 2 ** 3: l.append("monospaced") else: l.append("proportional") if flags & 2 ** 4: l.append("bold") return ", ".join(l) doc = pymupdf.open(sys.argv[1]) page = doc[0] # read page text as a dictionary, suppressing extra spaces in CJK fonts blocks = page.get_text("dict", flags=11)["blocks"] for b in blocks: # iterate through the text blocks for l in b["lines"]: # iterate through the text lines for s in l["spans"]: # iterate through the text spans print("") font_properties = "Font: '%s' (%s), size %g, color #%06x" % ( s["font"], # font name flags_decomposer(s["flags"]), # readable font flags s["size"], # font size s["color"], # font color ) print("Text: '%s'" % s["text"]) # simple print of text print(font_properties)
这是 PDF 页面和脚本输出:
- 如何插入文本
PyMuPDF 提供了在新的或现有 PDF 页面上插入文本的方法,具有以下特性:
- 选择字体,包括内置字体和作为文件可用的字体
- 选择文本特性,如粗体、斜体、字体大小、字体颜色等。
- 将文本以多种方式定位:
- 作为从特定点开始的简单线路定向输出,
- 或将文本适应于作为矩形提供的框中,在这种情况下,还可以选择文本对齐方式,
- 选择文本是否应放在前景(覆盖现有内容)中,
- 所有文本可以任意“变形”,即其外观可以通过矩阵改变,以实现缩放、倾斜或镜像等效果,
- 独立于变形以及此外,文本可以被整数倍数的 90 度旋转。
所有上述内容由三种基本的页面,分别是形状方法提供:
Page.insert_font()
– 为页面安装字体以供以后参考。结果反映在Document.get_page_fonts()
的输出中。该字体可以是:
- 作为文件提供,
- 通过字体(然后使用
Font.buffer
)- 已经在这个或另一个PDF 中某处存在,或者
- 是一个内置字体。
Page.insert_text()
– 写入一些文本行。在内部,这使用了Shape.insert_text()
。Page.insert_textbox()
– 将文本适应于给定矩形。在这里,您可以选择文本对齐特性(左对齐、右对齐、居中、两端对齐),并且您可以控制文本是否实际适合。在内部,这使用了Shape.insert_textbox()
。
注意
两种文本插入方法会根据需要自动安装字体。
如何写文本行
在页面上输出一些文本行:
import pymupdf doc = pymupdf.open(...) # new or existing PDF page = doc.new_page() # new or existing page via doc[n] p = pymupdf.Point(50, 72) # start point of 1st line text = "Some text,\nspread across\nseveral lines." # the same result is achievable by # text = ["Some text", "spread across", "several lines."] rc = page.insert_text(p, # bottom-left of 1st char text, # the text (honors '\n') fontname = "helv", # the default font fontsize = 11, # the default font size rotate = 0, # also available: 90, 180, 270 ) print("%i lines printed on page %i." % (rc, page.number)) doc.save("text.pdf")
使用这种方法,仅控制行数以确保不超过页面高度。多余的行将不会被写入,并返回实际行数。计算使用从fontsize
和 36 点(0.5 英寸)底部边距计算的行高。
忽略线宽度。线的多余部分将简单地不可见。
然而,对于内置字体,有方法可以预先计算行宽度 - 请参阅get_text_length()
。
这是另一个例子。它使用四种不同的旋转选项插入了 4 个文本字符串,并因此解释了必须选择的文本插入点,以实现所需的结果:
import pymupdf doc = pymupdf.open() page = doc.new_page() # the text strings, each having 3 lines text1 = "rotate=0\nLine 2\nLine 3" text2 = "rotate=90\nLine 2\nLine 3" text3 = "rotate=-90\nLine 2\nLine 3" text4 = "rotate=180\nLine 2\nLine 3" red = (1, 0, 0) # the color for the red dots # the insertion points, each with a 25 pix distance from the corners p1 = pymupdf.Point(25, 25) p2 = pymupdf.Point(page.rect.width - 25, 25) p3 = pymupdf.Point(25, page.rect.height - 25) p4 = pymupdf.Point(page.rect.width - 25, page.rect.height - 25) # create a Shape to draw on shape = page.new_shape() # draw the insertion points as red, filled dots shape.draw_circle(p1,1) shape.draw_circle(p2,1) shape.draw_circle(p3,1) shape.draw_circle(p4,1) shape.finish(width=0.3, color=red, fill=red) # insert the text strings shape.insert_text(p1, text1) shape.insert_text(p3, text2, rotate=90) shape.insert_text(p2, text3, rotate=-90) shape.insert_text(p4, text4, rotate=180) # store our work to the page shape.commit() doc.save(...)
这是结果:
- 如何填充文本框
此脚本用文本填充了 4 个不同的矩形,每次选择一个不同的旋转值:
import pymupdf doc = pymupdf.open() # new or existing PDF page = doc.new_page() # new page, or choose doc[n] # write in this overall area rect = pymupdf.Rect(100, 100, 300, 150) # partition the area in 4 equal sub-rectangles CELLS = pymupdf.make_table(rect, cols=4, rows=1) t1 = "text with rotate = 0." # these texts we will written t2 = "text with rotate = 90." t3 = "text with rotate = 180." t4 = "text with rotate = 270." text = [t1, t2, t3, t4] red = pymupdf.pdfcolor["red"] # some colors gold = pymupdf.pdfcolor["gold"] blue = pymupdf.pdfcolor["blue"] """ We use a Shape object (something like a canvas) to output the text and the rectangles surrounding it for demonstration. """ shape = page.new_shape() # create Shape for i in range(len(CELLS[0])): shape.draw_rect(CELLS[0][i]) # draw rectangle shape.insert_textbox( CELLS[0][i], text[i], fontname="hebo", color=blue, rotate=90 * i ) shape.finish(width=0.3, color=red, fill=gold) shape.commit() # write all stuff to the page doc.ez_save(__file__.replace(".py", ".pdf"))
以上使用了一些默认值:字体大小 11 和文本对齐“left”。结果将会如下所示:
- 如何用 HTML 文本填充方框
方法Page.insert_htmlbox()
提供了一种更加强大的方式来在矩形中插入文本。
与简单的纯文本不同,此方法接受 HTML 源码,该源码不仅可以包含 HTML 标签,还可以包含样式指令,以影响诸如字体、字重(加粗)和样式(斜体)、颜色等。
也可以混合多种字体和语言,输出 HTML 表格,并插入图片和 URI 链接。
为了更灵活地进行样式设置,还可以提供额外的 CSS 源码。
该方法基于 Story 类。因此,复杂的脚本系统,如天城文、尼泊尔文、泰米尔文等,得以支持并由于使用了 HarfBuzz 库(提供了所谓的**“文本整形”**功能),而被正确书写。
从 Google NOTO 字体库自动获取所需的字体以输出字符 - 作为备用(当可选择提供的用户字体不包含某些字形时)。
作为对此处提供的功能的小瞥见,我们将输出以下 HTML 富文本:
import pymupdf rect = pymupdf.Rect(100, 100, 400, 300) text = """Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation <b>ullamco <i>laboris</i></b> nisi ut aliquid ex ea commodi consequat. Quis aute iure <span style="color: #f00;">reprehenderit</span> in <span style="color: #0f0;font-weight:bold;">voluptate</span> velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui <a href="https://www.artifex.com">officia</a> deserunt mollit anim id est laborum.""" doc = pymupdf.Document() page = doc.new_page() page.insert_htmlbox(rect, text, css="* {font-family: sans-serif;font-size:14px;}") doc.ez_save(__file__.replace(".py", ".pdf"))
请注意“css”参数是如何被用来全局选择默认的“无衬线”字体和字体大小 14 的。
结果将会如下所示:
如何输出 HTML 表格和图片
这是另一个例子,演示了使用此方法输出表格。这次,我们在 HTML 源代码中包含了所有的样式。还请注意,如何在表格单元格中包含图片:
import pymupdf import os filedir = os.path.dirname(__file__) text = """ <style> body { font-family: sans-serif; } td, th { border: 1px solid blue; border-right: none; border-bottom: none; padding: 5px; text-align: center; } table { border-right: 1px solid blue; border-bottom: 1px solid blue; border-spacing: 0; } </style> <body> <p><b>Some Colors</b></p> <table> <tr> <th>Lime</th> <th>Lemon</th> <th>Image</th> <th>Mauve</th> </tr> <tr> <td>Green</td> <td>Yellow</td> <td><img src="img-cake.png" width=50></td> <td>Between<br>Gray and Purple</td> </tr> </table> </body> """ doc = pymupdf.Document() page = doc.new_page() rect = page.rect + (36, 36, -36, -36) # we must specify an Archive because of the image page.insert_htmlbox(rect, text, archive=pymupdf.Archive(".")) doc.ez_save(__file__.replace(".py", ".pdf"))
结果将会如下所示:
如何输出世界各国的语言
我们的第三个示例将演示自动多语言支持。它包括用于复杂脚本系统(如天城文和从右到左的语言)的自动文本整形:
import pymupdf greetings = ( "Hello, World!", # english "Hallo, Welt!", # german "سلام دنیا!", # persian "வணக்கம், உலகம்!", # tamil "สวัสดีชาวโลก!", # thai "Привіт Світ!", # ucranian "שלום עולם!", # hebrew "ওহে বিশ্ব!", # bengali "你好世界!", # chinese "こんにちは世界!", # japanese "안녕하세요, 월드!", # korean "नमस्कार, विश्व !", # sanskrit "हैलो वर्ल्ड!", # hindi ) doc = pymupdf.open() page = doc.new_page() rect = (50, 50, 200, 500) # join greetings into one text string text = " ... ".join([t for t in greetings]) # the output of the above is simple: page.insert_htmlbox(rect, text) doc.save(__file__.replace(".py", ".pdf"))
这是输出结果:
如何指定自己的字体
使用@font-face
语句以 CSS 语法定义您的字体文件。您需要为希望支持的每种字体粗细和字体样式(例如粗体或斜体)单独使用一个@font-face
。以下示例使用了著名的 MS Comic Sans 字体及其四个变体:常规、粗体、斜体和粗斜体。
由于这四个字体文件位于系统文件夹C:/Windows/Fonts
中,该方法需要一个存档定义,指向该文件夹:
""" How to use your own fonts with method Page.insert_htmlbox(). """ import pymupdf # Example text text = """Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation <b>ullamco <i>laboris</i></b> nisi ut aliquid ex ea commodi consequat. Quis aute iure <span style="color: red;">reprehenderit</span> in <span style="color: green;font-weight:bold;">voluptate</span> velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui <a href="https://www.artifex.com">officia</a> deserunt mollit anim id est laborum.""" """ We need an Archive object to show where font files are located. We intend to use the font family "MS Comic Sans". """ arch = pymupdf.Archive("C:/Windows/Fonts") # These statements define which font file to use for regular, bold, # italic and bold-italic text. # We assign an arbitary common font-family for all 4 font files. # The Story algorithm will select the right file as required. # We request to use "comic" throughout the text. css = """ @font-face {font-family: comic; src: url(comic.ttf);} @font-face {font-family: comic; src: url(comicbd.ttf);font-weight: bold;} @font-face {font-family: comic; src: url(comicz.ttf);font-weight: bold;font-style: italic;} @font-face {font-family: comic; src: url(comici.ttf);font-style: italic;} * {font-family: comic;} """ doc = pymupdf.Document() page = doc.new_page(width=150, height=150) # make small page page.insert_htmlbox(page.rect, text, css=css, archive=arch) doc.subset_fonts(verbose=True) # build subset fonts to reduce file size doc.ez_save(__file__.replace(".py", ".pdf"))
PyMuPDF 1.24.4 中文文档(二)(4)https://developer.aliyun.com/article/1559645