modm_data.pdf2html
PDF to HTML Pipeline
1# Copyright 2022, Niklas Hauser 2# SPDX-License-Identifier: MPL-2.0 3 4""" 5# PDF to HTML Pipeline 6""" 7 8from .render import annotate_debug_info 9from .convert import convert, patch 10from .html import format_document, write_html 11 12__all__ = [ 13 "stmicro", 14 "convert", 15 "annotate_debug_info", 16 "format_document", 17 "write_html", 18 "patch", 19 "ast", 20 "cell", 21 "figure", 22 "line", 23 "page", 24 "table", 25]
def
convert( doc: pypdfium2._helpers.document.PdfDocument, page_range: Iterable[int], output_path: pathlib.Path, figures_path: pathlib.Path = None, format_chapters: bool = False, pretty: bool = True, render_html: bool = True, render_pdf: bool = False, render_all: bool = False, show_ast: bool = False, show_tree: bool = False, show_tags: bool = False) -> bool:
55def convert( 56 doc: pp.PdfDocument, 57 page_range: Iterable[int], 58 output_path: Path, 59 figures_path: Path = None, 60 format_chapters: bool = False, 61 pretty: bool = True, 62 render_html: bool = True, 63 render_pdf: bool = False, 64 render_all: bool = False, 65 show_ast: bool = False, 66 show_tree: bool = False, 67 show_tags: bool = False, 68) -> bool: 69 document = None 70 debug_doc = None 71 debug_index = 0 72 for page in doc.pages(page_range): 73 # The previous pages create millions of long-lived objects, which the 74 # garbage collector would otherwise traverse again in every collection 75 # of the oldest generation, taking a third of the conversion time. 76 gc.freeze() 77 if not render_all and not page.is_relevant: 78 continue 79 print(f"\n\n=== {page.top} #{page.number} ===\n") 80 81 if show_tags: 82 for struct in page.structures: 83 print(struct.descr()) 84 85 if show_tree or render_html or show_ast: 86 areas = page.content_ast 87 if show_ast: 88 print() 89 for area in areas: 90 print(RenderTree(area)) 91 if show_tree or render_html: 92 for area in areas: 93 document = merge_area(document, area) 94 95 if render_pdf: 96 debug_doc = annotate_debug_info(page, debug_doc, debug_index) 97 debug_index += 1 98 99 if render_pdf: 100 with open(f"debug_{output_path.stem}.pdf", "wb") as file_handle: 101 pp.PdfDocument(debug_doc).save(file_handle) 102 103 if show_tree or render_html: 104 if document is None: 105 print("No pages parsed, empty document!") 106 return True 107 108 document = doc._normalize(document) 109 if show_tree: 110 print(RenderTree(document)) 111 112 if render_html: 113 names = set() 114 if format_chapters: 115 for chapter in document.children: 116 if chapter.name == "chapter": 117 print(f"\nFormatting HTML for '{chapter.title}'") 118 output_file = f"{output_path}/chapter_{chapter._filename}.html" 119 _write_figures(chapter, output_file, figures_path or output_path / "figures", names) 120 html = format_document(chapter) 121 print(f"\nWriting HTML '{output_file}'") 122 write_html(html, output_file, pretty=pretty) 123 else: 124 print("\nFormatting HTML") 125 _write_figures(document, output_path, figures_path or output_path.with_suffix("") / "figures", names) 126 html = format_document(document) 127 print(f"\nWriting HTML '{str(output_path)}'") 128 write_html(html, str(output_path), pretty=pretty) 129 130 return True
def
annotate_debug_info( page: modm_data.pdf2html.page.Page, new_doc: pypdfium2._helpers.document.PdfDocument = None, index: int = 0) -> pypdfium2._helpers.document.PdfDocument:
11def annotate_debug_info(page: Page, new_doc: pp.PdfDocument = None, index: int = 0) -> pp.PdfDocument: 12 """ 13 Copies each page into a new or existing PDF document and overlays the internal information on top of the content. 14 In addition to the information overlayed in `modm_data.pdf.annotate_debug_info`, this function: 15 - renders all content areas in ORANGE. 16 - renders all graphic cluster in content areas in GREEN. 17 - renders all tables in content areas in BLUE. 18 19 :param page: The page to be annotated. 20 :param new_doc: The PDF document to copy the page to. If not provided, a new document is created. 21 :param index: The index of the page in the new document. 22 :return: The new document with the annotated page added. 23 """ 24 new_doc = pdf_annotate_debug_info(page, new_doc, index) 25 # return new_doc 26 new_page = pp.raw.FPDF_LoadPage(new_doc, index) 27 rotation = page.rotation 28 width, height = page.width, page.height 29 30 if False: 31 for ii in range(20): 32 _vline(new_page, rotation, width * ii / 20, 0, height, width=1, stroke="black") 33 _hline(new_page, rotation, height * ii / 20, 0, width, width=1, stroke="black") 34 35 # for name, distance in page._spacing.items(): 36 # if name.startswith("x_"): 37 # _vline(new_page, rotation, distance, 0, height, width=0.5, stroke=0xFFA500) 38 # else: 39 # _hline(new_page, rotation, distance, 0, width, width=0.5, stroke=0xFFA500) 40 41 for name, area in page._areas.items(): 42 if isinstance(area, list): 43 for rect in area: 44 _rect(new_page, rotation, rect, width=0.5, stroke=0xFFA500) 45 else: 46 _rect(new_page, rotation, area, width=0.5, stroke=0xFFA500) 47 48 for obj in page.content_graphics: 49 if obj.cbbox is not None: 50 _rect(new_page, rotation, obj.cbbox, width=2, stroke=0x9ACD32) 51 if obj.bbox is not None: 52 _rect(new_page, rotation, obj.bbox, width=2, stroke=0x00FF00) 53 54 for table in page.content_tables: 55 _rect(new_page, rotation, table.bbox, width=1.5, stroke=0x0000FF) 56 57 for lines in table._xgrid.values(): 58 for line in lines: 59 _line(new_page, rotation, line, width=0.75, stroke=0x0000FF) 60 for lines in table._ygrid.values(): 61 for line in lines: 62 _line(new_page, rotation, line, width=0.75, stroke=0x0000FF) 63 64 for cell in table.cells: 65 for line in cell.lines: 66 for cluster in line.clusters(): 67 _rect(new_page, rotation, cluster.bbox, width=0.33, stroke=0x808080) 68 if cell.borders.left: 69 _vline( 70 new_page, 71 rotation, 72 cell.bbox.left, 73 cell.bbox.bottom, 74 cell.bbox.top, 75 width=cell.borders.left, 76 stroke=0xFF0000, 77 ) 78 if cell.borders.right: 79 _vline( 80 new_page, 81 rotation, 82 cell.bbox.right, 83 cell.bbox.bottom, 84 cell.bbox.top, 85 width=cell.borders.right, 86 stroke=0x0000FF, 87 ) 88 if cell.borders.bottom: 89 _hline( 90 new_page, 91 rotation, 92 cell.bbox.bottom, 93 cell.bbox.left, 94 cell.bbox.right, 95 width=cell.borders.bottom, 96 stroke=0x00FF00, 97 ) 98 if cell.borders.top: 99 _hline( 100 new_page, 101 rotation, 102 cell.bbox.top, 103 cell.bbox.left, 104 cell.bbox.right, 105 width=cell.borders.top, 106 stroke=0x808080, 107 ) 108 109 assert pp.raw.FPDFPage_GenerateContent(new_page) 110 pp.raw.FPDF_ClosePage(new_page) 111 return new_doc
Copies each page into a new or existing PDF document and overlays the internal information on top of the content.
In addition to the information overlayed in modm_data.pdf.annotate_debug_info, this function:
- renders all content areas in ORANGE.
- renders all graphic cluster in content areas in GREEN.
- renders all tables in content areas in BLUE.
Parameters
- page: The page to be annotated.
- new_doc: The PDF document to copy the page to. If not provided, a new document is created.
- index: The index of the page in the new document.
Returns
The new document with the annotated page added.
def
format_document(document):
277def format_document(document): 278 html = etree.Element("html") 279 280 head = etree.Element("head") 281 html.append(head) 282 283 link = etree.Element("link") 284 link.set("rel", "stylesheet") 285 link.set("href", "../style.css") 286 head.append(link) 287 288 body = etree.Element("body") 289 html.append(body) 290 291 _format_html(body, document, with_newlines=True) 292 293 html = etree.ElementTree(html) 294 return html
def
write_html(html, path, pretty=True):
def
patch( doc, data_module, output_path: pathlib.Path, patch_file: pathlib.Path = None) -> bool:
133def patch(doc, data_module, output_path: Path, patch_file: Path = None) -> bool: 134 if patch_file is None: 135 # First try the patch file for the specific version 136 patch_file = f"{doc.name}.patch" 137 if not pkg_file_exists(data_module, patch_file): 138 # Then try the patch file shared between versions 139 patch_file = f"{doc.name.split('-')[0]}.patch" 140 if not pkg_file_exists(data_module, patch_file): 141 return True 142 return pkg_apply_patch(data_module, patch_file, output_path) 143 return apply_patch(patch_file, output_path)