From 5460d759b0a962cb496276c2c66897106bda00b8 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sat, 3 Jan 2026 18:08:15 -0800 Subject: [PATCH] Add fallbacks for system fonts to reduce layout shift. Signed-off-by: Danila Fedorin --- assets/scss/fonts.scss | 20 +++++++++ assets/scss/variables.scss | 4 +- chatgpt-adjust-fallback.py | 90 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 chatgpt-adjust-fallback.py diff --git a/assets/scss/fonts.scss b/assets/scss/fonts.scss index da9d676..1d0d022 100644 --- a/assets/scss/fonts.scss +++ b/assets/scss/fonts.scss @@ -43,3 +43,23 @@ @include font-raleway(400); @include font-raleway(700); @include font-stixgeneral(); + +/* Generated from chatgpt-adjust-fallback.py */ + +@font-face { + font-family: "Raleway Fallback"; + src: local("Arial"); + size-adjust: 100.09%; + ascent-override: 94.00%; + descent-override: 23.40%; + line-gap-override: 0.00%; +} + +@font-face { + font-family: "Lora Fallback"; + src: local("Times New Roman"); + size-adjust: 111.79%; + ascent-override: 100.60%; + descent-override: 27.40%; + line-gap-override: 0.00%; +} diff --git a/assets/scss/variables.scss b/assets/scss/variables.scss index b87abe2..b03a07a 100644 --- a/assets/scss/variables.scss +++ b/assets/scss/variables.scss @@ -9,8 +9,8 @@ $background-color-dark: #1b1d1f; $standard-border-width: .075rem; $standard-border: $standard-border-width solid $border-color; -$font-heading: "Lora", serif; -$font-body: "Raleway", serif; +$font-heading: "Lora", "Lora Fallback", serif; +$font-body: "Raleway", "Raleway Fallback", sans-serif; $font-code: "Inconsolata", monospace, "STIXGeneral"; $warning-background-color: #ffee99; diff --git a/chatgpt-adjust-fallback.py b/chatgpt-adjust-fallback.py new file mode 100644 index 0000000..625e74b --- /dev/null +++ b/chatgpt-adjust-fallback.py @@ -0,0 +1,90 @@ +""" +Generate @font-face settings to help make the fallback font look similar +to the non-fallback font. + +Genererated by ChatGTP 5.2-instant. Not human-modified. +""" + +from fontTools.ttLib import TTFont + +USE_TYPO_METRICS = 1 << 7 + +def get_metrics(path): + font = TTFont(path) + + head = font["head"] + os2 = font["OS/2"] + hhea = font["hhea"] + + use_typo = bool(os2.fsSelection & USE_TYPO_METRICS) + + if use_typo: + asc = os2.sTypoAscender + desc = os2.sTypoDescender + gap = os2.sTypoLineGap + source = "OS/2.sTypo*" + else: + asc = hhea.ascent + desc = -hhea.descent + gap = hhea.lineGap + source = "hhea.*" + + # x-height ALWAYS comes from OS/2 + if not hasattr(os2, "sxHeight") or os2.sxHeight <= 0: + raise ValueError(f"{path} has no usable sxHeight") + + x_height = os2.sxHeight + + print("Source", path, source) + + return { + "unitsPerEm": head.unitsPerEm, + "ascender": asc, + "descender": desc, + "lineGap": gap, + "xHeight": x_height, + } + +def compute_overrides(target, fallback): + # size-adjust: match x-height + size_adjust = ( + (target["xHeight"] / target["unitsPerEm"]) / + (fallback["xHeight"] / fallback["unitsPerEm"]) + ) + + # overrides: force target vertical metrics + ascent_override = target["ascender"] / target["unitsPerEm"] + descent_override = abs(target["descender"]) / target["unitsPerEm"] + line_gap_override = target["lineGap"] / target["unitsPerEm"] + + return { + "size_adjust": size_adjust * 100, + "ascent_override": ascent_override * 100, + "descent_override": descent_override * 100, + "line_gap_override": line_gap_override * 100, + } + +def emit_css(family_name, local_name, values): + return f""" +@font-face {{ + font-family: "{family_name}"; + src: local("{local_name}"); + size-adjust: {values['size_adjust']:.2f}%; + ascent-override: {values['ascent_override']:.2f}%; + descent-override: {values['descent_override']:.2f}%; + line-gap-override: {values['line_gap_override']:.2f}%; +}} +""".strip() + +# ---- Example usage ---- + +target = get_metrics("static/fonts/gen/Lora-Regular.woff2") +fallback = get_metrics("/System/Library/Fonts/Supplemental/Times New Roman.ttf") + +values = compute_overrides(target, fallback) + +print(emit_css( + family_name="Lora Fallback", + local_name="Times New Roman", + values=values, +))