Skip to content

Commit b80eac7

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 cdeee5b commit b80eac7

2 files changed

Lines changed: 49 additions & 41 deletions

File tree

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
@@ -68,37 +68,6 @@ def _download_font(url: str, dest: pathlib.Path) -> bool:
6868
return True
6969

7070

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

85+
font_faces: list[dict[str, str]] = []
11886
for font in fonts:
11987
font_id = font["package"].split("/")[-1]
12088
version = font["version"]
@@ -127,10 +95,14 @@ def _on_builder_inited(app: Sphinx) -> None:
12795
url = _cdn_url(package, version, font_id, subset, weight, style)
12896
if _download_font(url, cached):
12997
shutil.copy2(cached, fonts_dir / filename)
130-
131-
css_content = _generate_css(fonts, variables)
132-
(css_dir / "fonts.css").write_text(css_content, encoding="utf-8")
133-
logger.info("generated fonts.css with %d font families", len(fonts))
98+
font_faces.append(
99+
{
100+
"family": font["family"],
101+
"style": style,
102+
"weight": str(weight),
103+
"filename": filename,
104+
}
105+
)
134106

135107
preload_hrefs: list[str] = []
136108
preload_specs: list[tuple[str, int, str]] = app.config.sphinx_font_preload
@@ -142,9 +114,13 @@ def _on_builder_inited(app: Sphinx) -> None:
142114
filename = f"{font_id}-{subset}-{weight}-{style}.woff2"
143115
preload_hrefs.append(filename)
144116
break
145-
app._font_preload_hrefs = preload_hrefs # type: ignore[attr-defined]
146117

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

149125

150126
def _on_html_page_context(
@@ -155,10 +131,14 @@ def _on_html_page_context(
155131
doctree: t.Any,
156132
) -> None:
157133
context["font_preload_hrefs"] = getattr(app, "_font_preload_hrefs", [])
134+
context["font_faces"] = getattr(app, "_font_faces", [])
135+
context["font_fallbacks"] = getattr(app, "_font_fallbacks", [])
136+
context["font_css_variables"] = getattr(app, "_font_css_variables", {})
158137

159138

160139
def setup(app: Sphinx) -> SetupDict:
161140
app.add_config_value("sphinx_fonts", [], "html")
141+
app.add_config_value("sphinx_font_fallbacks", [], "html")
162142
app.add_config_value("sphinx_font_css_variables", {}, "html")
163143
app.add_config_value("sphinx_font_preload", [], "html")
164144
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)