GitHub user mistercrunch deleted a comment on the discussion: Recommended approach to add custom fonts with the new theming system and frontend structure
I asked Claude Code to analyze this, here's what it came up with --- # Deep Analysis: Custom Fonts in Apache Superset ## Current State Analysis The discussion reveals several key insights about the current font loading mechanism in Superset: 1. **Theme.tsx as the Central Point**: Currently, the recommended approach is to import fonts directly in `superset-frontend/src/theme/Theme.tsx`, which serves as the canonical location for theme-related imports. 2. **Separation of Concerns**: Font files must be loaded separately from theme configuration. The theme JSON only references fonts by name through the `fontFamily` token, but doesn't handle the actual loading. 3. **CSP Considerations**: When using external fonts (CDNs), Content Security Policy updates are required in `config.py`, adding operational complexity. ## Problem Statement The current approach has several limitations: 1. **Lack of Standardization**: No standardized mechanism for font loading exists, leading to ad-hoc implementations 2. **Build-time Coupling**: Fonts must be imported at build time in Theme.tsx, requiring code changes and rebuilds 3. **Limited Flexibility**: No runtime configuration option for fonts without modifying source code 4. **CSP Management**: External font usage requires manual CSP configuration ## Proposed Solution: Dynamic Font Loading via Flask/Jinja2 Building on @mistercrunch's suggestion, here's a comprehensive solution that would provide a clean, configurable approach to font loading: ### 1. Configuration Layer Add font configuration to `superset_config.py`: ```python # Font configuration CUSTOM_FONTS = { "primary": { "name": "Inter", "urls": [ "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" ], "fallback": "Helvetica, Arial, sans-serif" }, "monospace": { "name": "JetBrains Mono", "urls": [ "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" ], "fallback": "Monaco, Consolas, monospace" } } # Automatically update CSP for configured font sources FONT_CSP_SOURCES = [] for font_config in CUSTOM_FONTS.values(): for url in font_config.get("urls", []): domain = urlparse(url).netloc if domain: FONT_CSP_SOURCES.append(f"https://{domain}") # Merge with existing CSP config TALISMAN_CONFIG = { "content_security_policy": { "font-src": ["'self'"] + list(set(FONT_CSP_SOURCES)), # ... other CSP directives } } ``` ### 2. Backend Integration Create a Flask endpoint to serve font configuration: ```python # superset/views/fonts.py from flask import current_app, jsonify from flask_appbuilder import expose, BaseView class FontsView(BaseView): route_base = "/api/v1/fonts" @expose("/config", methods=["GET"]) def get_font_config(self): """Get configured custom fonts""" return jsonify({ "fonts": current_app.config.get("CUSTOM_FONTS", {}), "enabled": current_app.config.get("ENABLE_CUSTOM_FONTS", True) }) ``` ### 3. Frontend Integration Create a dynamic font loader component: ```typescript // superset-frontend/src/components/FontLoader/index.tsx import { useEffect, useState } from 'react'; import { SupersetClient } from '@superset-ui/core'; interface FontConfig { name: string; urls: string[]; fallback: string; } interface FontsConfig { primary?: FontConfig; monospace?: FontConfig; [key: string]: FontConfig | undefined; } export const FontLoader: React.FC = () => { const [fontsLoaded, setFontsLoaded] = useState(false); useEffect(() => { const loadFonts = async () => { try { const response = await SupersetClient.get({ endpoint: '/api/v1/fonts/config', }); const { fonts, enabled } = response.json; if (!enabled || !fonts) { setFontsLoaded(true); return; } // Create link elements for each font URL const linkPromises = Object.entries(fonts).flatMap(([key, config]) => { const fontConfig = config as FontConfig; return fontConfig.urls.map(url => { return new Promise((resolve, reject) => { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = url; link.onload = resolve; link.onerror = reject; document.head.appendChild(link); }); }); }); await Promise.all(linkPromises); // Update CSS variables with font families Object.entries(fonts).forEach(([key, config]) => { const fontConfig = config as FontConfig; const fontFamily = `"${fontConfig.name}", ${fontConfig.fallback}`; document.documentElement.style.setProperty( `--font-family-${key}`, fontFamily ); }); setFontsLoaded(true); } catch (error) { console.error('Failed to load custom fonts:', error); setFontsLoaded(true); // Continue with defaults } }; loadFonts(); }, []); return null; }; ``` ### 4. Theme Integration Update the theme system to use CSS variables: ```typescript // superset-frontend/src/theme/index.ts export const theme = { typography: { families: { sansSerif: 'var(--font-family-primary, Inter, Helvetica, Arial, sans-serif)', monospace: 'var(--font-family-monospace, "JetBrains Mono", Monaco, Consolas, monospace)', }, }, // ... rest of theme }; ``` ### 5. Jinja2 Template Alternative For even faster loading, implement a Jinja2 template approach: ```html <!-- superset/templates/appbuilder/baselayout.html --> {% if config.CUSTOM_FONTS %} <style id="superset-custom-fonts"> {% for key, font_config in config.CUSTOM_FONTS.items() %} {% for url in font_config.urls %} @import url('{{ url }}'); {% endfor %} :root { --font-family-{{ key }}: "{{ font_config.name }}", {{ font_config.fallback }}; } {% endfor %} </style> {% endif %} ``` ## Benefits of This Approach 1. **Zero Build-time Changes**: Fonts can be configured without modifying source code or rebuilding 2. **Automatic CSP Management**: CSP policies are automatically updated based on font configuration 3. **Progressive Enhancement**: Falls back gracefully if font loading fails 4. **Performance Optimized**: Fonts load in parallel with the application 5. **Developer Friendly**: Simple configuration in superset_config.py 6. **Extensible**: Easy to add new font categories (heading, display, etc.) ## Migration Path 1. **Phase 1**: Implement the configuration layer and API endpoint 2. **Phase 2**: Add the FontLoader component and integrate with existing Theme.tsx 3. **Phase 3**: Update documentation and provide migration examples 4. **Phase 4**: Deprecate direct imports in Theme.tsx (with backward compatibility) ## Configuration Examples ### Example 1: Using Google Fonts ```python CUSTOM_FONTS = { "primary": { "name": "Roboto", "urls": ["https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"], "fallback": "Arial, sans-serif" } } ``` ### Example 2: Using Self-hosted Fonts ```python CUSTOM_FONTS = { "primary": { "name": "CustomBrand", "urls": ["/static/assets/fonts/custom-brand.css"], "fallback": "Arial, sans-serif" } } ``` ### Example 3: Multiple Font Families ```python CUSTOM_FONTS = { "primary": { "name": "Inter", "urls": ["https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"], "fallback": "Arial, sans-serif" }, "heading": { "name": "Playfair Display", "urls": ["https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&display=swap"], "fallback": "Georgia, serif" }, "monospace": { "name": "Fira Code", "urls": ["https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap"], "fallback": "Consolas, monospace" } } ``` ## Conclusion This solution provides a robust, future-proof approach to custom font management in Superset that: - Eliminates the need for build-time modifications - Provides runtime configuration flexibility - Maintains performance and security best practices - Offers a smooth migration path from the current approach The implementation leverages existing Superset patterns (configuration, API endpoints, theme system) while introducing minimal complexity. This approach would make Superset more flexible for enterprise deployments where brand consistency is important, while maintaining the simplicity that individual users expect. GitHub link: https://github.com/apache/superset/discussions/34397#discussioncomment-13939614 ---- This is an automatically sent email for notifications@superset.apache.org. To unsubscribe, please send an email to: notifications-unsubscr...@superset.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@superset.apache.org For additional commands, e-mail: notifications-h...@superset.apache.org