Skip to content

Commit

Permalink
feat: Add PDF Download option (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
ThomasParistech authored Nov 6, 2024
1 parent 0ea3920 commit 782d152
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 6 deletions.
1 change: 1 addition & 0 deletions .devcontainer/packages.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ git
gdal-bin
libgdal-dev
build-essential
libcairo2
libgl1
libglib2.0-0
tk
1 change: 1 addition & 0 deletions .devcontainer/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
cairosvg==2.7.1
dem-stitcher==2.5.5
dnspython==2.6.1
geopy==2.4.1
Expand Down
3 changes: 3 additions & 0 deletions .vscode/.mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ ignore_missing_imports = True
[mypy-tqdm.*]
ignore_missing_imports = True

[mypy-cairosvg.*]
ignore_missing_imports = True

[mypy-cv2.*]
ignore_missing_imports = True

Expand Down
20 changes: 16 additions & 4 deletions pretty_gpx/ui/pages/template/ui_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""Ui Manager."""
from dataclasses import dataclass
from typing import Generic
from typing import Literal
from typing import TypeVar

from nicegui import events
Expand Down Expand Up @@ -119,7 +120,9 @@ def __init__(self, cache: T) -> None:
self.subclass_column = ui.column(align_items="center")
col_3 = ui.column(align_items="center")

ui.button('Download', on_click=self.on_click_download)
with ui.row(align_items="center"):
ui.button('Download SVG', on_click=self.on_click_download_svg)
ui.button('Download PDF', on_click=self.on_click_download_pdf)
self.params_to_hide.visible = False

###
Expand Down Expand Up @@ -209,13 +212,22 @@ def on_dark_mode_switch_change(self, e: events.ValueChangeEventArguments) -> Non
else:
self.theme = self.theme.change(LightTheme.get_mapping())

async def on_click_download(self) -> None:
async def on_click_download_svg(self) -> None:
"""Asynchronously render the high resolution poster and download it as SVG."""
svg_bytes = await self.render_download_svg_bytes()
self.download(svg_bytes, "svg")

async def on_click_download_pdf(self) -> None:
"""Asynchronously render the high resolution poster and download it as PDF."""
svg_bytes = await self.render_download_svg_bytes()
pdf_bytes = await self.plot.svg_to_pdf_bytes(svg_bytes)
self.download(pdf_bytes, "pdf")

def download(self, data: bytes, ext: Literal["svg", "pdf"]) -> None:
"""Download the high resolution poster."""
basename = "poster"
title = self.title.value
if title:
basename += f"_{sanitize_filename(title.replace(' ', '_'))}"
ui.download(svg_bytes, f'{basename}.svg')
logger.info("Poster Downloaded")
ui.download(data, f'{basename}.{ext}')
logger.info(f"{ext.upper()} Poster Downloaded")
25 changes: 23 additions & 2 deletions pretty_gpx/ui/pages/template/ui_plot.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#!/usr/bin/python3
"""Ui Plot."""
import base64
import io
from collections.abc import Callable
from io import BytesIO
from typing import TypeVar

import cairosvg
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.axes import Axes
Expand Down Expand Up @@ -39,6 +41,7 @@ def __init__(self, visible: bool) -> None:
with ui.card().classes(f'w-[{W_DISPLAY_PIX}px]').style(f'{BOX_SHADOW_STYLE};') as self.card:
self.img = ui.image()
self.card.visible = visible
self.svg_bytes: bytes | None = None

@staticmethod
@profile_parallel
Expand All @@ -61,19 +64,37 @@ def draw_svg(func: Callable[[Figure, Axes, T], None], data: T) -> bytes:

async def update_preview(self, draw_func: Callable[[Figure, Axes, T], None], data: T) -> None:
"""Draw the figure and rasterize it to update the preview."""
self.svg_bytes = None
with UiWaitingModal("Updating Preview"):
self.img.source = await run_cpu_bound(UiPlot.draw_png, draw_func, data)

async def render_svg(self, draw_func: Callable[[Figure, Axes, T], None], data: T) -> bytes:
"""Draw the figure and return the SVG bytes."""
with UiWaitingModal("Rendering SVG"):
return await run_cpu_bound(UiPlot.draw_svg, draw_func, data)
if self.svg_bytes is None:
with UiWaitingModal("Rendering Vectorized Poster"):
self.svg_bytes = await run_cpu_bound(UiPlot.draw_svg, draw_func, data)

return self.svg_bytes

async def svg_to_pdf_bytes(self, svg_bytes: bytes) -> bytes:
"""Convert SVG bytes to PDF bytes."""
with UiWaitingModal("Converting to PDF"):
return await run_cpu_bound(svg_to_pdf_bytes, svg_bytes)

def make_visible(self) -> None:
"""Make the layout visible."""
self.card.visible = True


@profile_parallel
def svg_to_pdf_bytes(svg_bytes: bytes) -> bytes:
"""Convert SVG bytes to PDF bytes."""
pdf_bytes = io.BytesIO()
cairosvg.svg2pdf(bytestring=svg_bytes, write_to=pdf_bytes)
pdf_bytes.seek(0)
return pdf_bytes.read()


@profile
def fig_to_rasterized_base64(fig: Figure, dpi: int) -> str:
"""Convert a Matplotlib figure to a rasterized PNG in base64."""
Expand Down

0 comments on commit 782d152

Please sign in to comment.