DriverTrac/venv/lib/python3.12/site-packages/streamlit/runtime/theme_util.py
2025-11-28 09:08:33 +05:30

149 lines
5.7 KiB
Python

# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from typing import TYPE_CHECKING
from streamlit.errors import StreamlitAPIException
from streamlit.url_util import is_url
if TYPE_CHECKING:
from streamlit.proto.NewSession_pb2 import CustomThemeConfig
def _parse_font_config(
font_config: str | None,
property_name: str,
) -> tuple[str, str | None]:
"""Parse a single font configuration string.
Args:
font_config: The font configuration string (e.g., "Inter" or "Inter:https://...")
property_name: The property name for error messages ("font", "codeFont", "headingFont")
Returns
-------
A tuple of (font_name, source_url). source_url is None if not provided or not valid.
"""
if not font_config:
return "", None
# Strip leading/trailing whitespace from the config
font_config = font_config.strip()
if ":" not in font_config:
# No colon found, treat the entire string as the font name
return font_config, None
# Note: it is possible there are multiple colons in the string, so we need to split on the first colon.
font_name, source_url = font_config.split(":", 1)
# Strip whitespace from both font name and source
font_name = font_name.strip()
source_url = source_url.strip()
# Check that the href does not contain multiple fonts, so we confirm that "family="
# only shows up once in the source string (structure applies to Google Fonts only)
family_occurrences = source_url.count("family=")
if family_occurrences > 1:
raise StreamlitAPIException(
f"The source URL specified in the {property_name} property of config.toml contains multiple fonts. "
"Please specify only one font in the source URL."
)
is_valid_url = is_url(source_url)
# If the source is a valid URL (http/https) but no font name is provided, throw an exception
if is_valid_url and not font_name:
raise StreamlitAPIException(
f"A font family name is required when specifying a source URL "
f"for the {property_name} property in config.toml."
)
if is_valid_url:
return font_name, source_url
return font_name, None
def _get_font_source_config_name(property_name: str, section: str) -> str:
"""Get the config name for font sources based on property and section. This is used on the FE
as the id for the font source link in the html head.
"""
if section == "theme":
return property_name
return f"{property_name}-sidebar"
def parse_fonts_with_source(
msg: CustomThemeConfig,
body_font_config: str | None,
code_font_config: str | None,
heading_font_config: str | None,
section: str,
) -> CustomThemeConfig:
"""Populate the CustomThemeConfig message with the font, codeFont, and headingFont fields set,
as well as the font_sources field to be added to the html head.
Args:
msg: CustomThemeConfig message to be populated.
body_font_config: A string with just the font name (e.g., "Inter") or in the format
"<font_family_name_here>:<source_url_here>".
code_font_config: A string with just the font name (e.g., "Roboto Mono") or in the format
"<code_font_family_name_here>:<source_url_here>".
heading_font_config: A string with just the font name (e.g., "Inter Bold") or in the format
"<heading_font_family_name_here>:<source_url_here>".
section: The section of the config.toml file to parse the fonts from.
Examples
--------
body_font_config: "Inter" (just font name)
code_font_config: "Tagesschrift:https://fonts.googleapis.com/css2?family=Tagesschrift&display=swap" (with source)
heading_font_config: "playwrite-cc-za:https://use.typekit.net/xxs7euo.css"
Returns
-------
Updated CustomThemeConfig message with the font, codeFont, and headingFont fields set.
Also sets sources in font_sources field to be added to the html (only when source URLs are provided).
"""
# Parse body font config
body_font_name, body_font_source = _parse_font_config(body_font_config, "font")
if body_font_name:
msg.body_font = body_font_name
if body_font_source:
config_name = _get_font_source_config_name("font", section)
msg.font_sources.add(config_name=config_name, source_url=body_font_source)
# Parse code font config
code_font_name, code_font_source = _parse_font_config(code_font_config, "codeFont")
if code_font_name:
msg.code_font = code_font_name
if code_font_source:
config_name = _get_font_source_config_name("codeFont", section)
msg.font_sources.add(config_name=config_name, source_url=code_font_source)
# Parse heading font config
heading_font_name, heading_font_source = _parse_font_config(
heading_font_config, "headingFont"
)
if heading_font_name:
msg.heading_font = heading_font_name
if heading_font_source:
config_name = _get_font_source_config_name("headingFont", section)
msg.font_sources.add(config_name=config_name, source_url=heading_font_source)
return msg