Skip to content

Commit 740dfd8

Browse files
committed
docs(fonts[loading]): switch to font-display block with inline CSS
why: font-display swap causes visible text reflow (FOUT). Matching the tony.nl/cv approach: block rendering until preloaded fonts arrive, and inline the @font-face CSS to eliminate the extra fonts.css request. what: - Change font-display from swap to block - Move @font-face CSS from external fonts.css to inline <style> in <head> - Use pathto() in template for correct relative font URLs - Remove _generate_css() function (CSS now generated in Jinja template)
1 parent 73c88ff commit 740dfd8

File tree

2 files changed

+49
-41
lines changed

2 files changed

+49
-41
lines changed

docs/_ext/sphinx_fonts.py

Lines changed: 21 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Sphinx extension for self-hosted fonts via Fontsource CDN.
22
3-
Downloads font files at build time, caches them locally, and generates
4-
CSS with @font-face declarations and CSS variable overrides.
3+
Downloads font files at build time, caches them locally, and passes
4+
structured font data to the template context for inline @font-face CSS.
55
"""
66

77
from __future__ import annotations
@@ -66,37 +66,6 @@ def _download_font(url: str, dest: pathlib.Path) -> bool:
6666
return True
6767

6868

69-
def _generate_css(
70-
fonts: list[dict[str, t.Any]],
71-
variables: dict[str, str],
72-
) -> str:
73-
lines: list[str] = []
74-
for font in fonts:
75-
family = font["family"]
76-
font_id = font["package"].split("/")[-1]
77-
subset = font.get("subset", "latin")
78-
for weight in font["weights"]:
79-
for style in font["styles"]:
80-
filename = f"{font_id}-{subset}-{weight}-{style}.woff2"
81-
lines.append("@font-face {")
82-
lines.append(f' font-family: "{family}";')
83-
lines.append(f" font-style: {style};")
84-
lines.append(f" font-weight: {weight};")
85-
lines.append(" font-display: swap;")
86-
lines.append(f' src: url("../fonts/{filename}") format("woff2");')
87-
lines.append("}")
88-
lines.append("")
89-
90-
if variables:
91-
lines.append("body {")
92-
for var, value in variables.items():
93-
lines.append(f" {var}: {value};")
94-
lines.append("}")
95-
lines.append("")
96-
97-
return "\n".join(lines)
98-
99-
10069
def _on_builder_inited(app: Sphinx) -> None:
10170
if app.builder.format != "html":
10271
return
@@ -109,10 +78,9 @@ def _on_builder_inited(app: Sphinx) -> None:
10978
cache = _cache_dir()
11079
static_dir = pathlib.Path(app.outdir) / "_static"
11180
fonts_dir = static_dir / "fonts"
112-
css_dir = static_dir / "css"
11381
fonts_dir.mkdir(parents=True, exist_ok=True)
114-
css_dir.mkdir(parents=True, exist_ok=True)
11582

83+
font_faces: list[dict[str, str]] = []
11684
for font in fonts:
11785
font_id = font["package"].split("/")[-1]
11886
version = font["version"]
@@ -125,10 +93,14 @@ def _on_builder_inited(app: Sphinx) -> None:
12593
url = _cdn_url(package, version, font_id, subset, weight, style)
12694
if _download_font(url, cached):
12795
shutil.copy2(cached, fonts_dir / filename)
128-
129-
css_content = _generate_css(fonts, variables)
130-
(css_dir / "fonts.css").write_text(css_content, encoding="utf-8")
131-
logger.info("generated fonts.css with %d font families", len(fonts))
96+
font_faces.append(
97+
{
98+
"family": font["family"],
99+
"style": style,
100+
"weight": str(weight),
101+
"filename": filename,
102+
}
103+
)
132104

133105
preload_hrefs: list[str] = []
134106
preload_specs: list[tuple[str, int, str]] = app.config.sphinx_font_preload
@@ -140,9 +112,13 @@ def _on_builder_inited(app: Sphinx) -> None:
140112
filename = f"{font_id}-{subset}-{weight}-{style}.woff2"
141113
preload_hrefs.append(filename)
142114
break
143-
app._font_preload_hrefs = preload_hrefs # type: ignore[attr-defined]
144115

145-
app.add_css_file("css/fonts.css")
116+
fallbacks: list[dict[str, str]] = app.config.sphinx_font_fallbacks
117+
118+
app._font_preload_hrefs = preload_hrefs # type: ignore[attr-defined]
119+
app._font_faces = font_faces # type: ignore[attr-defined]
120+
app._font_fallbacks = fallbacks # type: ignore[attr-defined]
121+
app._font_css_variables = variables # type: ignore[attr-defined]
146122

147123

148124
def _on_html_page_context(
@@ -153,10 +129,14 @@ def _on_html_page_context(
153129
doctree: t.Any,
154130
) -> None:
155131
context["font_preload_hrefs"] = getattr(app, "_font_preload_hrefs", [])
132+
context["font_faces"] = getattr(app, "_font_faces", [])
133+
context["font_fallbacks"] = getattr(app, "_font_fallbacks", [])
134+
context["font_css_variables"] = getattr(app, "_font_css_variables", {})
156135

157136

158137
def setup(app: Sphinx) -> SetupDict:
159138
app.add_config_value("sphinx_fonts", [], "html")
139+
app.add_config_value("sphinx_font_fallbacks", [], "html")
160140
app.add_config_value("sphinx_font_css_variables", {}, "html")
161141
app.add_config_value("sphinx_font_preload", [], "html")
162142
app.connect("builder-inited", _on_builder_inited)

docs/_templates/page.html

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,34 @@
44
{%- for href in font_preload_hrefs|default([]) %}
55
<link rel="preload" href="{{ pathto('_static/fonts/' + href, 1) }}" as="font" type="font/woff2" crossorigin="">
66
{%- endfor %}
7+
{%- if font_faces is defined and font_faces %}
8+
<style id="sphinx-fonts">
9+
{%- for face in font_faces %}
10+
@font-face {
11+
font-family: "{{ face.family }}";
12+
font-style: {{ face.style }};
13+
font-weight: {{ face.weight }};
14+
font-display: block;
15+
src: url("{{ pathto('_static/fonts/' + face.filename, 1) }}") format("woff2");
16+
}
17+
{%- endfor %}
18+
{%- for fb in font_fallbacks|default([]) %}
19+
@font-face {
20+
font-family: "{{ fb.family }}";
21+
src: {{ fb.src }};
22+
size-adjust: {{ fb.size_adjust }};
23+
ascent-override: {{ fb.ascent_override }};
24+
descent-override: {{ fb.descent_override }};
25+
line-gap-override: {{ fb.line_gap_override }};
26+
}
27+
{%- endfor %}
28+
body {
29+
{%- for var, value in font_css_variables.items() %}
30+
{{ var }}: {{ value }};
31+
{%- endfor %}
32+
}
33+
</style>
34+
{%- endif %}
735
{%- if theme_show_meta_manifest_tag == true %}
836
<link rel="manifest" href="/manifest.json">
937
{% endif -%}

0 commit comments

Comments
 (0)