HomeDashboard/.venv/lib/python3.12/site-packages/nicegui/favicon.py
2026-01-03 14:54:18 +01:00

108 lines
3.3 KiB
Python

from __future__ import annotations
import base64
import io
import urllib.parse
from pathlib import Path
from typing import TYPE_CHECKING
from fastapi.responses import FileResponse, Response, StreamingResponse
from . import core
from .helpers import is_file
from .version import __version__
if TYPE_CHECKING:
from .page import page
def create_favicon_route(path: str, favicon: str | Path | None) -> None:
"""Create a favicon route for the given path."""
if is_file(favicon):
core.app.add_route('/favicon.ico' if path == '/' else f'{path}/favicon.ico',
lambda _: FileResponse(favicon)) # type: ignore
def get_favicon_url(page: page, prefix: str) -> str:
"""Return the URL of the favicon for a given page."""
favicon = page.favicon or core.app.config.favicon
if not favicon:
return f'{prefix}/_nicegui/{__version__}/static/favicon.ico'
favicon = str(favicon).strip()
if _is_remote_url(favicon):
return favicon
if _is_data_url(favicon):
return favicon
if _is_svg(favicon):
return _svg_to_data_url(favicon)
if _is_char(favicon):
return _svg_to_data_url(_char_to_svg(favicon))
if page.path == '/' or page.favicon is None:
return f'{prefix}/favicon.ico'
return f'{prefix}{page.path}/favicon.ico'
def get_favicon_response() -> Response:
"""Return the FastAPI response for the global favicon."""
if not core.app.config.favicon:
raise ValueError(f'invalid favicon: {core.app.config.favicon}')
favicon = str(core.app.config.favicon).strip()
if _is_svg(favicon):
return Response(favicon, media_type='image/svg+xml')
if _is_data_url(favicon):
media_type, bytes_ = _data_url_to_bytes(favicon)
return StreamingResponse(io.BytesIO(bytes_), media_type=media_type)
if _is_char(favicon):
return Response(_char_to_svg(favicon), media_type='image/svg+xml')
raise ValueError(f'invalid favicon: {favicon}')
def _is_remote_url(favicon: str) -> bool:
return favicon.startswith(('http://', 'https://'))
def _is_char(favicon: str) -> bool:
return len(favicon) == 1 or '\ufe0f' in favicon
def _is_svg(favicon: str) -> bool:
return favicon.strip().startswith('<svg')
def _is_data_url(favicon: str) -> bool:
return favicon.startswith('data:')
def _char_to_svg(char: str) -> str:
return f'''
<svg viewBox="0 0 128 128" width="128" height="128" xmlns="http://www.w3.org/2000/svg" >
<style>
@supports (-moz-appearance:none) {{
text {{
font-size: 100px;
transform: translateY(0.1em);
}}
}}
text {{
font-family: Arial, sans-serif;
}}
</style>
<text y=".9em" font-size="128" font-family="Georgia, sans-serif">{char}</text>
</svg>
'''
def _svg_to_data_url(svg: str) -> str:
svg_urlencoded = urllib.parse.quote(svg)
return f'data:image/svg+xml,{svg_urlencoded}'
def _data_url_to_bytes(data_url: str) -> tuple[str, bytes]:
media_type, base64_image = data_url.split(',', 1)
media_type = media_type.split(':')[1].split(';')[0]
return media_type, base64.b64decode(base64_image)