Mercurial > hgrepos > Python2 > PyMuPDF
view tests/test_story.py @ 1:1d09e1dec1d9 upstream
ADD: PyMuPDF v1.26.4: the original sdist.
It does not yet contain MuPDF. This normally will be downloaded when
building PyMuPDF.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:37:51 +0200 |
| parents | |
| children |
line wrap: on
line source
import pymupdf import os import textwrap def test_story(): otf = os.path.abspath(f'{__file__}/../resources/PragmaticaC.otf') # 2023-12-06: latest mupdf throws exception if path uses back-slashes. otf = otf.replace('\\', '/') CSS = f""" @font-face {{font-family: test; src: url({otf});}} """ HTML = """ <p style="font-family: test;color: blue">We shall meet again at a place where there is no darkness.</p> """ MEDIABOX = pymupdf.paper_rect("letter") WHERE = MEDIABOX + (36, 36, -36, -36) # the font files are located in /home/chinese arch = pymupdf.Archive(".") # if not specified user_css, the output pdf has content story = pymupdf.Story(HTML, user_css=CSS, archive=arch) writer = pymupdf.DocumentWriter("output.pdf") more = 1 while more: device = writer.begin_page(MEDIABOX) more, _ = story.place(WHERE) story.draw(device) writer.end_page() writer.close() def test_2753(): def rectfn(rect_num, filled): return pymupdf.Rect(0, 0, 200, 200), pymupdf.Rect(50, 50, 100, 150), None def make_pdf(html, path_out): story = pymupdf.Story(html=html) document = story.write_with_links(rectfn) print(f'test_2753(): Writing to: {path_out=}.') document.save(path_out) return document doc_before = make_pdf( textwrap.dedent(''' <p>Before</p> <p style="page-break-before: always;"></p> <p>After</p> '''), os.path.abspath(f'{__file__}/../../tests/test_2753-out-before.pdf'), ) doc_after = make_pdf( textwrap.dedent(''' <p>Before</p> <p style="page-break-after: always;"></p> <p>After</p> '''), os.path.abspath(f'{__file__}/../../tests/test_2753-out-after.pdf'), ) path = os.path.normpath(f'{__file__}/../../tests/test_2753_out') doc_before.save(f'{path}_before.pdf') doc_after.save(f'{path}_after.pdf') assert len(doc_before) == 2 assert len(doc_after) == 2 # codespell:ignore-begin springer_html = ''' <article> <aside> <img src="springer.jpg"> <br><i>Michael Springer ist Schriftsteller und Wis­sen­schafts­publizist. Eine Sammlung seiner Einwürfe ist 2019 als Buch unter dem Titel <b>»Lauter Überraschungen. Was die Wis­senschaft weitertreibt«</b> erschienen.<br><a>www.spektrum.de/artikel/2040277</a></i> </aside> <h1>SPRINGERS EINWÜRFE: INTIME VERBINDUNGEN</h1> <h2>Wieso kann unsereins so vieles, was eine Maus nicht kann? Unser Gehirn ist nicht bloß größer, sondern vor allem überraschend vertrackt verdrahtet.</h2> <p>Der Heilige Gral der Neu­ro­wis­sen­schaft ist die komplette Kartierung des menschlichen Gehirns – die ge­treue Ab­bildung des Ge­strüpps der Nervenzellen mit den baum­för­mi­gen Ver­ästel­ungen der aus ihnen sprie­ßen­den Den­dri­ten und den viel län­ge­ren Axo­nen, wel­che oft der Sig­nal­über­tragung von einem Sin­nes­or­gan oder zu einer Mus­kel­fa­ser die­nen. Zum Gesamtbild gehören die winzigen Knötchen auf den Dendriten; dort sitzen die Synapsen. Das sind Kontakt- und Schalt­stel­len, leb­haf­te Ver­bin­dungen zu anderen Neuronen.</p> <p>Dieses Dickicht bis zur Ebene einzelner Zel­len zu durchforsten und es räumlich dar­zu­stel­len, ist eine gigantische Aufgabe, die bis vor Kurzem utopisch anmuten musste. Neu­er­dings vermag der junge For­schungs­zweig der Konnektomik (von Englisch: con­nect für ver­bin­den) das Zusammenspiel der Neurone immer besser zu verstehen. Das gelingt mit dem Einsatz dreidimensionaler Elek­tro­nen­mik­ros­ko­pie. Aus Dünn­schicht­auf­nah­men von zerebralen Ge­we­be­pro­ben lassen sich plastische Bil­der ganzer Zellverbände zu­sam­men­setzen.</p> <p>Da frisches menschliches Hirn­ge­we­be nicht ohne Wei­te­res zu­gäng­lich ist – in der Regel nur nach chirurgischen Eingriffen an Epi­lep­sie­pa­tien­ten –, hält die Maus als Mo­dell­or­ga­nis­mus her. Die evolutionäre Ver­wandt­schaft von Mensch und Nager macht die Wahl plau­sibel. Vor allem das Team um Moritz Helmstaedter am Max-Planck-Institut (MPI) für Hirnforschung in Frankfurt hat in den ver­gangenen Jahren Expertise bei der kon­nek­tomischen Analyse entwickelt.</p> <p>Aber steckt in unserem Kopf bloß ein auf die tausendfache Neu­ro­nen­an­zahl auf­ge­bläh­tes Mäu­se­hirn? Oder ist menschliches Ner­ven­ge­we­be viel­leicht doch anders gestrickt? Zur Beantwortung dieser Frage unternahm die MPI-Gruppe einen detaillierten Vergleich von Maus, Makake und Mensch (Science 377, abo0924, 2022).</p> <p>Menschliches Gewebe stammte diesmal nicht von Epileptikern, son­dern von zwei wegen Hirntumoren operierten Patienten. Die For­scher wollten damit vermeiden, dass die oft jahrelange Behandlung mit An­ti­epi­lep­ti­ka das Bild der synaptischen Verknüpfungen trübte. Sie verglichen die Proben mit denen eines Makaken und von fünf Mäusen.</p> <p>Einerseits ergaben sich – einmal ab­ge­se­hen von den ganz of­fen­sicht­li­chen quan­titativen Unterschieden wie Hirngröße und Neu­ro­nen­anzahl – recht gute Über­ein­stim­mun­gen, die somit den Gebrauch von Tier­modellen recht­fer­ti­gen. Doch in einem Punkt erlebte das MPI-Team eine echte Über­raschung.</p> <p>Gewisse Nervenzellen, die so genannten In­ter­neurone, zeichnen sich dadurch aus, dass sie aus­schließ­lich mit anderen Ner­ven­zel­len in­ter­agieren. Solche »Zwi­schen­neu­rone« mit meist kurzen Axonen sind nicht primär für das Verarbeiten externer Reize oder das Aus­lösen körperlicher Reaktionen zuständig; sie be­schäf­ti­gen sich bloß mit der Ver­stär­kung oder Dämpfung interner Signale.</p> <p>Just dieser Neuronentyp ist nun bei Makaken und Menschen nicht nur mehr als doppelt so häufig wie bei Mäusen, sondern obendrein be­son­ders intensiv untereinander ver­flochten. Die meisten Interneurone kop­peln sich fast ausschließlich an ihresgleichen. Dadurch wirkt sich ihr konnektomisches Ge­wicht ver­gleichs­weise zehnmal so stark aus.</p> <p>Vermutlich ist eine derart mit sich selbst be­schäf­tigte Sig­nal­ver­ar­beitung die Vor­be­ding­ung für ge­stei­gerte Hirn­leis­tungen. Um einen Ver­gleich mit verhältnismäßig pri­mi­ti­ver Tech­nik zu wagen: Bei küns­tli­chen neu­ro­na­len Netzen – Algorithmen nach dem Vor­bild verknüpfter Nervenzellen – ge­nü­gen schon ein, zwei so genannte ver­bor­ge­ne Schich­ten von selbst­be­züg­li­chen Schaltstellen zwischen Input und Output-Ebene, um die ver­blüf­fen­den Erfolge der künstlichen Intel­ligenz her­vor­zu­bringen.</p> </article> ''' #codespell:ignore-end def test_fit_springer(): if not hasattr(pymupdf, 'mupdf'): print(f'test_fit_springer(): not running on classic.') return verbose = 0 story = pymupdf.Story(springer_html) def check(call, expected): ''' Checks that eval(call) returned parameter=expected. Also creates PDF using path that contains `call` in its leafname, ''' fit_result = eval(call) print(f'test_fit_springer(): {call=} => {fit_result=}.') if expected is None: assert not fit_result.big_enough else: document = story.write_with_links(lambda rectnum, filled: (fit_result.rect, fit_result.rect, None)) path = os.path.abspath(f'{__file__}/../../tests/test_fit_springer_{call}_{fit_result.parameter=}_{fit_result.rect=}.pdf') document.save(path) print(f'Have saved document to {path}.') assert abs(fit_result.parameter-expected) < 0.001, f'{expected=} {fit_result.parameter=}' check(f'story.fit_scale(pymupdf.Rect(0, 0, 200, 200), scale_min=1, verbose={verbose})', 3.685728073120117) check(f'story.fit_scale(pymupdf.Rect(0, 0, 595, 842), scale_min=1, verbose={verbose})', 1.0174560546875) check(f'story.fit_scale(pymupdf.Rect(0, 0, 300, 421), scale_min=1, verbose={verbose})', 2.02752685546875) check(f'story.fit_scale(pymupdf.Rect(0, 0, 600, 900), scale_min=1, scale_max=1, verbose={verbose})', 1) check(f'story.fit_height(20, verbose={verbose})', 10782.3291015625) check(f'story.fit_height(200, verbose={verbose})', 2437.4990234375) check(f'story.fit_height(2000, verbose={verbose})', 450.2998046875) check(f'story.fit_height(5000, verbose={verbose})', 378.2998046875) check(f'story.fit_height(5500, verbose={verbose})', 378.2998046875) check(f'story.fit_width(3000, verbose={verbose})', 167.30859375) check(f'story.fit_width(2000, verbose={verbose})', 239.595703125) check(f'story.fit_width(1000, verbose={verbose})', 510.85546875) check(f'story.fit_width(500, verbose={verbose})', 1622.1272945404053) check(f'story.fit_width(400, verbose={verbose})', 2837.507724761963) check(f'story.fit_width(300, width_max=200000, verbose={verbose})', None) check(f'story.fit_width(200, width_max=200000, verbose={verbose})', None) # Run without verbose to check no calls to log() - checked by assert. check('story.fit_scale(pymupdf.Rect(0, 0, 600, 900), scale_min=1, scale_max=1, verbose=0)', 1) check('story.fit_scale(pymupdf.Rect(0, 0, 300, 421), scale_min=1, verbose=0)', 2.02752685546875) def test_write_stabilized_with_links(): def rectfn(rect_num, filled): ''' We return one rect per page. ''' rect = pymupdf.Rect(10, 20, 290, 380) mediabox = pymupdf.Rect(0, 0, 300, 400) #print(f'rectfn(): rect_num={rect_num} filled={filled}') return mediabox, rect, None def contentfn(positions): ret = '' ret += textwrap.dedent(''' <!DOCTYPE html> <body> <h2>Contents</h2> <ul> ''') for position in positions: if position.heading and (position.open_close & 1): text = position.text if position.text else '' if position.id: ret += f' <li><a href="#{position.id}">{text}</a>' else: ret += f' <li>{text}' ret += f' page={position.page_num}\n' ret += '</ul>\n' ret += textwrap.dedent(f''' <h1>First section</h1> <p>Contents of first section. <ul> <li>External <a href="https://artifex.com/">link to https://artifex.com/</a>. <li><a href="#idtest">Link to IDTEST</a>. <li><a href="#nametest">Link to NAMETEST</a>. </ul> <h1>Second section</h1> <p>Contents of second section. <h2>Second section first subsection</h2> <p>Contents of second section first subsection. <p id="idtest">IDTEST <h1>Third section</h1> <p>Contents of third section. <p><a name="nametest">NAMETEST</a>. </body> ''') return ret.strip() document = pymupdf.Story.write_stabilized_with_links(contentfn, rectfn) # Check links. links = list() for page in document: links += page.get_links() print(f'{len(links)=}.') external_links = dict() for i, link in enumerate(links): print(f' {i}: {link=}') if link.get('kind') == pymupdf.LINK_URI: uri = link['uri'] external_links.setdefault(uri, 0) external_links[uri] += 1 # Check there is one external link. print(f'{external_links=}') if hasattr(pymupdf, 'mupdf'): assert len(external_links) == 1 assert 'https://artifex.com/' in external_links out_path = __file__.replace('.py', '.pdf') document.save(out_path) def test_archive_creation(): s = pymupdf.Story(archive=pymupdf.Archive('.')) s = pymupdf.Story(archive='.') def test_3813(): import pymupdf HTML = """ <p>Count is fine:</p> <ol> <li>Lorem <ol> <li>Sub Lorem</li> <li>Sub Lorem</li> </ol> </li> <li>Lorem</li> <li>Lorem</li> </ol> <p>Broken count:</p> <ol> <li>Lorem <ul> <li>Sub Lorem</li> <li>Sub Lorem</li> </ul> </li> <li>Lorem</li> <li>Lorem</li> </ol> """ MEDIABOX = pymupdf.paper_rect("A4") WHERE = MEDIABOX + (36, 36, -36, -36) story = pymupdf.Story(html=HTML) path = os.path.normpath(f'{__file__}/../../tests/test_3813_out.pdf') writer = pymupdf.DocumentWriter(path) more = 1 while more: device = writer.begin_page(MEDIABOX) more, _ = story.place(WHERE) story.draw(device) writer.end_page() writer.close() with pymupdf.open(path) as document: page = document[0] text = page.get_text() text_utf8 = text.encode() text_expected_utf8 = b'Count is \xef\xac\x81ne:\n1. Lorem\n1. Sub Lorem\n2. Sub Lorem\n2. Lorem\n3. Lorem\nBroken count:\n1. Lorem\n\xe2\x80\xa2 Sub Lorem\n\xe2\x80\xa2 Sub Lorem\n2. Lorem\n3. Lorem\n' text_expected = text_expected_utf8.decode() print(f'text_utf8:\n {text_utf8!r}') print(f'text_expected_utf8:\n {text_expected_utf8!r}') print(f'text:\n {textwrap.indent(text, " ")}') print(f'text_expected:\n {textwrap.indent(text_expected, " ")}') assert text == text_expected
