You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
104 lines
3.1 KiB
104 lines
3.1 KiB
from __future__ import annotations |
|
|
|
import base64 |
|
import io |
|
import urllib.parse |
|
from pathlib import Path |
|
from typing import TYPE_CHECKING, Optional, Tuple, Union |
|
|
|
from fastapi.responses import FileResponse, Response, StreamingResponse |
|
|
|
from . import globals # pylint: disable=redefined-builtin |
|
from .helpers import is_file |
|
from .version import __version__ |
|
|
|
if TYPE_CHECKING: |
|
from .page import page |
|
|
|
|
|
def create_favicon_route(path: str, favicon: Optional[Union[str, Path]]) -> None: |
|
if is_file(favicon): |
|
globals.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: |
|
favicon = page.favicon or globals.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: |
|
if not globals.favicon: |
|
raise ValueError(f'invalid favicon: {globals.favicon}') |
|
favicon = str(globals.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://') or favicon.startswith('https://') |
|
|
|
|
|
def is_char(favicon: str) -> bool: |
|
return len(favicon) == 1 |
|
|
|
|
|
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)
|
|
|