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(' bool: return favicon.startswith('data:') def _char_to_svg(char: str) -> str: return f''' {char} ''' 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)