Print a rendered report page to a paginated PDF.
The PDF is the report page itself, printed by the web engine the
report editor already uses (Qt WebEngine's printToPdf), so what is
issued is what the page shows. The page is loaded with #print, which
draws its figures at print resolution, and its print stylesheet keeps a
figure off a page break. With the report feature report_identity on,
the page's margins carry the laboratory, the report's number and
revision, the title and page n of N, numbered by the print engine's own
page counters.
The printing runs in the web engine's processes while the
application's thread turns the event loop, so a window stays painted.
Called from a worker thread, the print is handed to the application's
thread and the worker waits for it. It needs a Qt application, and
makes one when a script has none.
Functions:
| Name |
Description |
print_pdf |
Print page (a report rendered by render_html) to a PDF at
|
Functions:
print_pdf
print_pdf(page: str, path: str | PathLike, *, page_size: str = 'letter', timeout: float = 300.0) -> bytes
Print page (a report rendered by render_html) to a PDF at
path, and return the PDF's bytes.
page_size is 'letter' or 'a4'. The PDF is printed beside the page
in a temporary folder and moved to path only once it is whole, so
a failed print leaves path as it was.
Raises:
| Type |
Description |
ValueError
|
For a page size that is not one of PAGE_SIZES.
|
OSError
|
When the page does not load, or the print fails or does not
finish within timeout seconds.
|
Source code in src/visualdynamics/report/pdf.py
| def print_pdf(page: str, path: str | os.PathLike, *,
page_size: str = 'letter', timeout: float = 300.0) -> bytes:
"""Print `page` (a report rendered by `render_html`) to a PDF at
`path`, and return the PDF's bytes.
`page_size` is 'letter' or 'a4'. The PDF is printed beside the page
in a temporary folder and moved to `path` only once it is whole, so
a failed print leaves `path` as it was.
Raises
------
ValueError
For a page size that is not one of `PAGE_SIZES`.
OSError
When the page does not load, or the print fails or does not
finish within `timeout` seconds.
"""
if page_size not in PAGE_SIZES:
raise ValueError(f'unknown page size {page_size!r}; the sizes are: '
f'{", ".join(PAGE_SIZES)}')
from PySide6.QtCore import QMarginsF, QUrl
from PySide6.QtGui import QPageLayout, QPageSize
from PySide6.QtWebEngineCore import QWebEnginePage
from PySide6.QtWebEngineWidgets import QWebEngineView
app = _application()
if not app.thread().isCurrentThread():
return _on_gui_thread(app, lambda: print_pdf(
page, path, page_size=page_size, timeout=timeout))
target = os.path.expanduser(str(path))
size = (QPageSize.PageSizeId.Letter if page_size == 'letter'
else QPageSize.PageSizeId.A4)
# the margins the identity rides in (`identity.MARGINS`, which the
# page's own print stylesheet also states, for a page printed from
# a browser)
from .identity import MARGINS
top, side = MARGINS
layout = QPageLayout(QPageSize(size), QPageLayout.Orientation.Portrait,
QMarginsF(side, top, side, top),
QPageLayout.Unit.Inch)
with tempfile.TemporaryDirectory(prefix='vd-print-') as folder:
source = os.path.join(folder, 'report.html')
printed = os.path.join(folder, 'report.pdf')
with open(source, 'w', encoding='utf-8') as out:
out.write(page)
view = QWebEngineView()
web = QWebEnginePage()
view.setPage(web)
view.resize(PRINT_WIDTH, 900)
state: dict[str, Any] = {}
web.loadFinished.connect(lambda ok: state.update(loaded=ok))
web.pdfPrintingFinished.connect(
lambda _file, ok: state.update(printed=ok))
url = QUrl.fromLocalFile(source)
url.setFragment('print')
try:
view.load(url)
if not _wait(app, lambda: 'loaded' in state, timeout) \
or not state['loaded']:
raise OSError(f'the report page did not load within '
f'{timeout:g} s, so nothing was printed')
web.printToPdf(printed, layout)
if not _wait(app, lambda: 'printed' in state, timeout):
raise OSError(f'the PDF was not finished within {timeout:g} '
's; nothing was written')
if not state['printed'] or not os.path.isfile(printed):
raise OSError(f'the web engine could not print {target}')
with open(printed, 'rb') as handle:
data = handle.read()
shutil.move(printed, target)
finally:
_stand_down(app, view, web)
return data
|