Variable Fonts subsetten mit pyftsubset: von 277 auf 29 KB
Kennst du diesen Moment, wenn du im Lighthouse-Report nachliest, warum deine Seite den Text eine halbe Sekunde später zeigt als sie müsste? Und dann steht da, ganz unschuldig: fonts/MeineSchrift-Regular.ttf, -Medium.ttf, -SemiBold.ttf, -Bold.ttf. Vier Dateien. Jede davon schleppt Glyphen für Vietnamesisch, Türkisch, Polnisch und Kyrillisch mit sich rum, obwohl auf der ganzen Seite ausschließlich deutsche Sätze stehen.
Dagegen hilft eine Variable Font, die du subsettest. Genau das habe ich an diesem Blog gerade aufgeräumt. Die Schrift heißt Cabinet Grotesk, brav selbst gehostet, alles ordentlich. Am Ende der Übung war es eine Datei mit 29 Kilobyte, und die Gewichte von 400 bis 700 sind trotzdem alle stufenlos da. Das ist weniger als ein mittelmäßiges JPEG vom Team-Event.
Kurzfassung: die zwei Befehle
Eine Variable Font subsettest du mit zwei Befehlen. fonttools varLib.instancer beschneidet die Gewichtsachse auf den Bereich, den dein Design tatsächlich nutzt. pyftsubset wirft danach alle Glyphen raus, die du nicht brauchst, und speichert das Ergebnis als WOFF2. Für eine deutsche Website reichen sechs Unicode-Ranges. Bei mir wurden so aus 277 Kilobyte 29.
fonttools varLib.instancer CabinetGrotesk-Variable.woff2 \
wght=400:700 --output /tmp/instanced.ttf
pyftsubset /tmp/instanced.ttf \
--unicodes="U+0020-007E,U+00A0-00FF,U+2010-2015,U+2018-201E,U+2026,U+20AC" \
--name-IDs+=7,13,14 \
--flavor=woff2 \
--output-file=CabinetGrotesk-Variable-subset.woff2
Aufwendig ist das nicht. Zwei Zeilen in der Shell, ein bisschen Nachdenken über Sonderzeichen, fertig. Ich gehe jeden Schritt mit den Zahlen durch, die bei mir rausgekommen sind, damit du direkt mitmachen kannst. Und ich sage dir auch bei dem Schritt, der bei mir nichts gebracht hat, dass er nichts gebracht hat.
Was du brauchst: fonttools
Alles, was jetzt kommt, läuft mit fonttools, einer Python-Bibliothek. Aktuell ist Version 4.65.0 (Anfang September 2026), Python 3.10 oder neuer wird gebraucht. Ich installiere das immer in ein wegwerfbares Virtual Environment, weil ich es zwei Mal im Jahr brauche und es nicht global rumliegen haben will:
python3 -m venv .venv-fonts
source .venv-fonts/bin/activate
pip install "fonttools[woff]" brotli
Die eckigen Klammern sind wichtig. fonttools[woff] zieht die Abhängigkeiten für WOFF2 mit rein. Ohne die bekommst du beim Speichern eine Fehlermeldung über fehlendes Brotli und rätselst erst mal fünf Minuten.
Ein eigenes Paket für pyftsubset suchst du übrigens vergeblich. Das Kommando steckt in fonttools mit drin, genauso wie das fonttools varLib.instancer aus Schritt 1. Ein pip install, und beide Werkzeuge liegen im Pfad des Virtual Environments.
Kurzer Check, ob alles da ist:
pyftsubset --version # bzw. fonttools --version
Dazu brauchst du natürlich die Variable Font selbst, als TTF oder WOFF2. Und die Lizenzdatei, die daneben lag. Warum die, dazu unten mehr.
Warum eine Variable Font gewinnt, noch bevor du subsettest
Ein Schieberegler statt einer Schublade voller fertiger Schnitte. So ungefähr.
Klassisch läuft es so: Der Schriftgestalter zeichnet Regular. Dann Bold. Dann Light, Medium, SemiBold, Black. Sechs eigenständige Zeichnungen, sechs Dateien, sechs Downloads. Und wenn du im Layout plötzlich ein Gewicht zwischen Medium und SemiBold brauchst, hast du Pech gehabt.
Eine Variable Font speichert stattdessen eine Grundform plus Deltas, also Verschiebungsanweisungen für jeden einzelnen Punkt der Umrisse. Wörtlich etwa: „Bei Gewicht 700 wandert dieser Punkt am H-Stamm 82 Einheiten nach rechts.“ Der Browser rechnet daraus jedes Gewicht in Echtzeit aus. Auch 543, wenn du das willst. Das Verfahren heißt Interpolation, und die Regler nennt man Achsen: wght für Gewicht, wdth für Breite, slnt für Neigung, opsz für optische Größe.
Fürs Laden zählt vor allem ein Request statt sechs. Jede Font-Datei ist ein eigener Roundtrip, und solange sie unterwegs ist, wartet der Text oder blitzt kurz im Fallback auf. Dazu kommt, dass sechs statische Schnitte sechs Mal fast identische Kurvendaten enthalten. Die Variable Font legt sie einmal ab und packt nur die Unterschiede obendrauf. Als Nebeneffekt kannst du font-weight: 550 schreiben, ohne eine weitere Datei zu laden.
Der Unterschied lässt sich messen. Ich habe die Schrift dieses Blogs in sechs statische TTF-Dateien zerlegt, Light bis ExtraBold. Also genau das, was normalerweise aus so einem Foundry-Download fällt:
CabinetGrotesk-Light.ttf 47.320 Bytes
CabinetGrotesk-Regular.ttf 47.340 Bytes
CabinetGrotesk-Medium.ttf 47.308 Bytes
CabinetGrotesk-SemiBold.ttf 47.360 Bytes
CabinetGrotesk-Bold.ttf 47.376 Bytes
CabinetGrotesk-ExtraBold.ttf 47.348 Bytes
─────────────────────────────────────────
284.052 Bytes ≈ 277 KB
Rund 300 Kilobyte für Schrift. Die komplette Variable Font mit allen Gewichten von 100 bis 900 wiegt als TTF dagegen 99.484 Bytes, und als WOFF2 komprimiert nur noch 41.860. Ein Sechstel, ohne dass du irgendetwas weggeworfen hättest.
Das ist der Startpunkt. Jetzt wird gekürzt.
Schritt 0: Reinschauen, bevor du schneidest
Bitte nicht blind loslegen. Du willst wissen, was in der Datei steckt. Welche Achsen? Wie viele Zeichen? Und, wichtig für später: sind die Zeichen überhaupt drin, die du für deutschen Text brauchst?
Dieses kleine Skript sagt dir das Wesentliche:
# inspect.py
from fontTools.ttLib import TTFont
import sys
font = TTFont(sys.argv[1])
cmap = font.getBestCmap()
print("Glyphen: ", len(font.getGlyphOrder()))
print("Codepoints: ", len(cmap))
if "fvar" in font:
for axis in font["fvar"].axes:
print(f"Achse {axis.axisTag}: {axis.minValue} bis {axis.maxValue}"
f" (default {axis.defaultValue})")
else:
print("Keine Variable Font (keine fvar-Tabelle)")
# Deutsche Sonderzeichen prüfen
german = {"ä": 0x00E4, "ö": 0x00F6, "ü": 0x00FC, "Ä": 0x00C4,
"Ö": 0x00D6, "Ü": 0x00DC, "ß": 0x00DF, "ẞ": 0x1E9E,
"€": 0x20AC, "„": 0x201E, "“": 0x201C, "–": 0x2013}
fehlt = [z for z, cp in german.items() if cp not in cmap]
print("Fehlt: ", fehlt or "nichts")
Bei meiner Schrift kommt raus:
Glyphen: 465
Codepoints: 405
Achse wght: 100.0 bis 900.0 (default 900.0)
Fehlt: ['ẞ']
Schau dir die letzte Zeile an. Das große Eszett ẞ ist in dieser Schrift überhaupt nicht enthalten. Das ist kein Subsetting-Problem, sondern war einfach nie da. Genau deshalb machst du den Check vorher. Sonst suchst du hinterher eine Stunde nach einem Fehler in deinem pyftsubset-Aufruf, den es nie gab.
Nebenbei: Der Default steht bei 900. Bleibt der so und dein CSS sagt nur font-weight: 400, siehst du im Browser plötzlich fette Schrift, obwohl alles richtig aussieht.
Schritt 1: die Gewichte wegschneiden, die du nie einsetzt
405 Zeichen, Gewichte von 100 bis 900. Ich brauche Regular (400) und Bold (700). Hairline 100 und Black 900 benutze ich nie, die können weg.
Dafür gibt es varLib.instancer. Der kann Achsen komplett festnageln oder, und das ist hier der Trick, nur einen Ausschnitt behalten:
fonttools varLib.instancer \
src/assets/fonts/CabinetGrotesk-Variable.woff2 \
wght=400:700 \
--output /tmp/instanced.ttf
Der Doppelpunkt heißt „von bis“. Danach ist es weiterhin eine Variable Font, nur mit kürzerem Regler: 400 bis 700, stufenlos. Alles dazwischen funktioniert wie vorher, alles darunter und darüber klemmt der Browser auf die Grenzwerte.
Und jetzt die Stelle, an der ich ehrlich sein muss, weil es überall anders behauptet wird. Was der Schritt an Bytes bringt:
Variable Font komplett (100–900), WOFF2: 41.860 Bytes
Variable Font beschnitten (400–700): 44.540 Bytes
Es wird größer. Nicht dramatisch, aber größer. Der Grund: Der Instancer normalisiert die Deltas neu und schreibt dabei eine avar-Tabelle, die im Original schlanker war. Die Gewichtsdaten selbst sind ein winziger Teil der Datei.
Ist der Schritt damit sinnlos? Nein, aber die Begründung ist eine andere als die, die man liest. Du machst das für die Korrektheit, nicht für die Größe. Die beschnittene Font passt exakt zu deinem font-weight: 400 700 im CSS, und niemand kann versehentlich ein Gewicht setzen, das dein Design nie gesehen hat. Wenn dir das egal ist, überspring ihn. Das Geld liegt woanders.
Schritt 2: pyftsubset, hier fallen die Bytes
Die Bytes stecken in den Glyphen. 405 Zeichen, und ich schätze, ich benutze knapp die Hälfte davon.
pyftsubset wirft alles raus, was du nicht ausdrücklich behalten willst. Angeben kannst du das auf zwei Wege: als konkreten Text oder als Unicode-Ranges. Ranges sind die haltbarere Variante, dazu gleich mehr. Erst mal der Befehl, der bei mir im Repo steht:
pyftsubset /tmp/instanced.ttf \
--unicodes="U+0020-007E,U+00A0-00FF,U+2010-2015,U+2018-201E,U+2026,U+20AC" \
--name-IDs+=7,13,14 \
--flavor=woff2 \
--output-file=src/assets/fonts/CabinetGrotesk-Variable-subset.woff2
Ergebnis: 29.400 Bytes. Von 277 Kilobyte auf 29, und der Regler von 400 bis 700 funktioniert immer noch.
236 Glyphen (vorher 465)
199 Codepoints (vorher 405)
Achse wght: 400.0 bis 700.0
Die Achse steht noch in der Datei. Das ist der Punkt, an dem die meisten skeptisch werden: pyftsubset entfernt zusammen mit einer Glyphe auch deren Variations-Deltas, lässt die fvar-Tabelle und die Interpolationsdaten der behaltenen Glyphen aber in Ruhe. Du bekommst eine kleinere Variable Font, keine statische.
Welche Unicode-Ranges eine deutsche Website wirklich braucht
Das ist der Teil, über den man tatsächlich nachdenken muss. Der Rest ist Copy-Paste. Gehen wir die Liste einzeln durch, ich schreibe bei jeder Range dazu, wie viele Zeichen sie in meiner Font beisteuert.
U+0020-007E, Basic Latin, 95 Zeichen. Leerzeichen, a–z, A–Z, 0–9, Satzzeichen. Das absolute Minimum. Ich höre bei 007E auf, also bei der Tilde, und nicht bei 007F. Das wäre das Steuerzeichen DELETE, und das braucht keine Glyphe.
U+00A0-00FF, Latin-1 Supplement, 94 Zeichen. Hier wohnen deine Umlaute: ä ö ü Ä Ö Ü ß. Dazu das geschützte Leerzeichen U+00A0, das du für „5 km“ oder „Prof. Müller“ brauchst, außerdem °, ×, ½, © und ®. Ohne diese Range ist eine deutsche Seite kaputt. Ja, es kommen ein paar Passagiere mit, die du nie benutzt, æ und þ und ÿ. Egal, sie kosten fast nichts, und die Range in einem Stück zu nehmen ist weniger fehleranfällig als eine handverlesene Liste.
U+2010-2015, Striche, in meiner Font 2 Zeichen. Der Gedankenstrich (–) ist U+2013 und nicht der Bindestrich. Wenn du im Deutschen typografisch halbwegs korrekt setzen willst, brauchst du ihn, und er steht garantiert irgendwo in deinen Texten.
U+2018-201E, Anführungszeichen, 6 Zeichen. Die deutschen Gänsefüßchen unten und oben, „ (U+201E) und “ (U+201C). Dazu die einfachen Varianten und, wichtiger als man denkt, das typografische Apostroph ’ (U+2019). Fehlt das, steht im Frontend ein Kästchen mitten in „Peter’s“. Diese Range wird am häufigsten vergessen.
U+2026, die Auslassungspunkte … Ein einzelnes Zeichen. Kostet praktisch nichts, und irgendein Redakteur wird es benutzen.
U+20AC, das Eurozeichen €. Der Klassiker unter den Fallen: Das € liegt nicht im Latin-1 Supplement. Es wurde erst 1998 in Unicode ergänzt und landete oben bei den Währungssymbolen. Wenn du also nur U+0020-00FF nimmst und im Onlineshop dann alle Preise als Rechteck erscheinen, hast du hier deinen Grund.
Und was habe ich weggelassen?
U+0100-017F, Latin Extended-A. Das sind 128 Plätze für ā ă ą ć č ď ę ł ő ř ś ż und Verwandte. Also Polnisch, Tschechisch, Ungarisch, Türkisch, Baltisch. Was das kostet, habe ich gemessen:
Subset ohne Latin Extended-A: 29.400 Bytes
Subset mit Latin Extended-A: 35.104 Bytes
─────────────
+ 5.704 Bytes
5,7 KB. Und hier habe ich eine klare Meinung: Nimm sie mit, sobald auf deiner Seite Namen von Menschen stehen. Impressum, Teamseite, Kommentare, Autorenzeilen, Kundenreferenzen. Kollegin Wiśniewska in einer hässlichen Fallback-Font zu setzen, um 5 Kilobyte zu sparen, ist ein schlechter Tausch. Bei einer reinen Marketing-Landingpage mit drei handgeschriebenen Sätzen lass sie weg.
Was ich außerdem nicht drin habe und auch nicht vermisse: Kyrillisch, Griechisch, Vietnamesisch, mathematische Symbole, Pfeile. Sowas liegt in einer Foundry-Font gern als Beigabe drin.
Der Sparfuchs-Modus: nur die Zeichen, die du wirklich tippst
Man kann weiter gehen und statt Ranges eine handverlesene Liste angeben:
pyftsubset /tmp/instanced.ttf \
--unicodes="U+0020-007E,U+00C4,U+00D6,U+00DC,U+00E4,U+00F6,U+00FC,U+00DF,U+2013,U+2014,U+201C,U+201E,U+2019,U+2026,U+20AC" \
--flavor=woff2 --output-file=/tmp/tight.woff2
Das landet bei 22.544 Bytes, also 7 KB weniger. Klingt verlockend. Ich mache es trotzdem nicht, und zwar aus einem sehr praktischen Grund: Die Liste ist eine Zeitbombe. In sechs Monaten schreibt jemand „Café“ oder „naïv“ oder ein Preisschild mit ½ in den CMS-Text, und du hast einen kaputten Buchstaben mitten im Absatz. Der Build läuft trotzdem durch, es gibt keine Warnung, nur ein hässliches Kästchen, das dir irgendwann ein Nutzer meldet.
Sieben Kilobyte sind kein Grund, sich diese Klasse von Bug einzubauen. Nimm die ganze Latin-1-Range.
Eine Ausnahme gibt es: Wenn du eine Zweitschrift nur für ein Logo, eine einzige Headline oder eine Zahlenanzeige lädst, dann ist --text genau richtig.
pyftsubset /tmp/instanced.ttf \
--text="Hallo Welt, schöne Grüße aus Köln; 42 € fürs Ticket!" \
--flavor=woff2 --output-file=/tmp/nurdiesersatz.woff2
Das sind dann 8.588 Bytes. Es gibt auch --text-file=texte.txt, wenn du deine Headlines aus einer Datei ziehen willst. Aber wirklich nur für Text, der sich nie ändert.
Die anderen pyftsubset-Flags, kurz erklärt
--flavor=woff2 ist Pflicht. Ohne das Flag bekommst du eine unkomprimierte TTF-Datei zurück, und der Unterschied ist erheblich:
Subset als TTF: 68.460 Bytes
Subset als WOFF2: 29.400 Bytes
Subset als WOFF: 35.180 Bytes
WOFF2 nutzt Brotli plus eine font-spezifische Vorverarbeitung. WOFF (also WOFF1) brauchst du 2026 nicht mehr als Fallback, WOFF2 läuft in jedem Browser, den du realistisch bedienst.
--name-IDs+=7,13,14 behält drei Einträge in der Namenstabelle: Trademark, License Description und License Info URL. pyftsubset wirft die per Default raus, weil sie Platz kosten. Ich lasse sie drin, weil die Lizenzinfo in eine Font-Datei gehört. Kostet ein paar hundert Bytes und erspart dir eine unangenehme E-Mail.
Eine Sache macht man dabei leicht falsch: die Layout-Features wegwerfen. pyftsubset behält per Default einen sinnvollen Satz, darunter kern, liga, calt, mark und frac. Manche Tutorials empfehlen, die zu killen. Was das bringt:
mit Default-Features: 29.400 Bytes
ohne kern und liga: 23.928 Bytes
5,5 KB für das Unterschneiden zwischen AV und To und für vernünftige Ligaturen. Das ist genau der Grund, warum du überhaupt eine gute Schrift gekauft hast. Lass es drin.
In der anderen Richtung liest man oft --layout-features="*", also alle Features behalten. Gebrauchen kannst du das, wenn deine Schrift Sachen mitbringt, die du im CSS wirklich ansteuerst: echte Kapitälchen über font-variant-caps, Tabellenziffern über font-variant-numeric, alternative Glyphen über font-feature-settings. Weißt du nicht, ob du eines davon nutzt, dann nutzt du es nicht. Bleib beim Default, der ist für Fließtext genau richtig gewählt.
Das @font-face für eine subsettete Variable Font
Der wichtigste Unterschied zu einer statischen Font ist die Bereichsangabe bei font-weight:
@font-face {
font-family: "Cabinet Grotesk";
src: url("/fonts/CabinetGrotesk-Variable-subset.woff2") format("woff2-variations");
font-weight: 400 700; /* Bereich, nicht Einzelwert */
font-style: normal;
font-display: swap;
unicode-range: U+0020-007E, U+00A0-00FF, U+2010-2015, U+2018-201E, U+2026, U+20AC;
}
body {
font-family: "Cabinet Grotesk", system-ui, sans-serif;
}
Zwei Details entscheiden hier über Erfolg oder Frust.
font-weight: 400 700, zwei Werte mit einem Leerzeichen dazwischen. Schreibst du nur font-weight: 400, hält der Browser die Datei für einen statischen Regular und synthetisiert für Bold eine künstlich verfettete Version. Sieht matschig aus, und der ganze Aufwand war umsonst.
Die unicode-range sollte zu deinem Subset passen. Der Browser prüft vorab, ob im gerenderten Text überhaupt ein Zeichen aus diesem Bereich vorkommt, und lädt die Datei sonst gar nicht. Bei einer einzelnen Datei ist das eine Randnotiz. Interessant wird es, wenn du zwei Subsets baust, einen Kern-Latin und einen zweiten nur mit Latin Extended-A. Dann holt der Browser die Namen-Glyphen ausschließlich auf den Seiten, wo sie gebraucht werden. Genau so macht es Google Fonts, wenn es dir dreißig @font-face-Blöcke für eine Schrift ausliefert.
Ein Preload-Hint spart dazu den Roundtrip, bei dem der Browser erst das CSS parsen muss, um überhaupt zu merken, dass er eine Schrift braucht:
<link rel="preload" href="/fonts/CabinetGrotesk-Variable-subset.woff2"
as="font" type="font/woff2" crossorigin />
Das crossorigin ist Pflicht, auch bei Fonts von der eigenen Domain. Ohne das Attribut lädt der Browser die Datei zwei Mal. Wirklich.
Und benutz preload sparsam. Für eine Schrift, die tatsächlich über der Falz sichtbar ist, lohnt es sich. Vier Fonts vorzuladen heißt nur, dass sie sich gegenseitig die Bandbreite wegnehmen.
Wenn du mit Astro arbeitest (ab Version 7 ist die Fonts-API stabil), übernimmt das Framework das @font-face, den Hash im Dateinamen und die Fallback-Metriken:
// astro.config.ts
import { defineConfig, fontProviders } from "astro/config";
export default defineConfig({
fonts: [
{
provider: fontProviders.local(),
name: "Cabinet Grotesk",
cssVariable: "--font-cabinet",
fallbacks: ["sans-serif"],
optimizedFallbacks: true,
display: "swap",
options: {
variants: [
{
weight: "400 700",
style: "normal",
src: ["./src/assets/fonts/CabinetGrotesk-Variable-subset.woff2"],
},
],
},
},
],
});
Auf optimizedFallbacks würde ich dabei nicht verzichten. Astro liest die Metriken deiner Schrift aus und baut daraus eine angepasste Fallback-Schrift mit passender Zeilenhöhe und Zeichenbreite. Damit ruckelt beim Font-Swap das Layout nicht mehr, und dein CLS-Wert bleibt sauber.
Subsetting reproduzierbar machen, sonst vergisst du es
Der Befehl, den du heute in die Shell tippst, ist in vier Monaten verloren. Dann kommt ein Schrift-Update von der Foundry, du lädst die neue Datei runter und rätselst, wie du das letzte Mal gekürzt hast.
Bei mir stehen die Befehle deshalb als Kommentar direkt an der Font-Konfiguration. Sauberer ist ein Script:
#!/usr/bin/env bash
# scripts/subset-font.sh – benötigt: pip install "fonttools[woff]" brotli
set -euo pipefail
SRC="src/assets/fonts/CabinetGrotesk-Variable.woff2"
OUT="src/assets/fonts/CabinetGrotesk-Variable-subset.woff2"
TMP="$(mktemp -d)"
# Deutsch: Basic Latin, Latin-1 (Umlaute), Striche, Anführungszeichen, …, €
RANGES="U+0020-007E,U+00A0-00FF,U+2010-2015,U+2018-201E,U+2026,U+20AC"
fonttools varLib.instancer "$SRC" wght=400:700 --output "$TMP/instanced.ttf"
pyftsubset "$TMP/instanced.ttf" \
--unicodes="$RANGES" \
--name-IDs+=7,13,14 \
--flavor=woff2 \
--output-file="$OUT"
printf "%s: %s Bytes (Quelle: %s Bytes)\n" \
"$OUT" "$(wc -c < "$OUT")" "$(wc -c < "$SRC")"
Dazu ein Eintrag in der package.json, damit das Script auffindbar bleibt:
{
"scripts": {
"fonts:subset": "./scripts/subset-font.sh"
}
}
Wichtig: Die Originaldatei bleibt im Repo. Immer. Ein Subset ist ein Einweg-Schnitt, aus den 29 KB bekommst du die verworfenen Glyphen nicht zurück.
Vor dem Deploy: nachmessen
Zwei Kontrollen reichen.
Erstens das Inspect-Skript von oben auf das Ergebnis loslassen. Achse noch da? Umlaute noch da? Euro noch da?
Zweitens im Browser nachsehen. Öffne die Seite, setz einen Absatz mit den kritischen Zeichen rein und schau in den DevTools unter Elements → Computed → Rendered Fonts nach. Steht dort dein Font-Name statt des Fallbacks, passt es. Im Network-Tab siehst du außerdem auf einen Blick, wie viele Font-Requests wirklich rausgehen. Manchmal ist es doch noch einer mehr als gedacht.
Ach, und eine Sache, die im Optimierungseifer gerne untergeht: Prüf die Lizenz. Die meisten Open-Source-Fonts (SIL OFL, Apache) erlauben Subsetting ausdrücklich. Bei kommerziellen Webfont-Lizenzen steht das nicht immer drin, und manche Foundries verlangen, dass du die Datei unverändert ausliefern musst. Ein Blick in die Lizenzdatei kostet zwei Minuten.
Vier Fehler, die beim Subsetten immer wieder passieren
U+20ACvergessen. Alle Preise erscheinen als Rechteck. Das Eurozeichen liegt nicht im Latin-1 Supplement.font-weight: 400statt400 700. Der Browser fettet Bold künstlich nach, und das Subsetting war für nichts.crossoriginbeim Preload weglassen. Die Datei geht zwei Mal über die Leitung. Aus einer Optimierung wird eine Verschlechterung.- Nur die Achse beschneiden und beim Subsetting aufhören. Die Gewichte sind ein winziger Teil der Datei. Bei mir wurde sie dadurch sogar größer.
Häufige Fragen zum Subsetten von Variable Fonts
Was ist Font Subsetting? Subsetting heißt, aus einer Schriftdatei alle Glyphen zu entfernen, die auf deiner Website nicht vorkommen. Aus einer Font mit 405 Zeichen für ein Dutzend Sprachen werden 199 Zeichen für deutschen Text. Die Datei wird dadurch kleiner und lädt schneller, das Aussehen der behaltenen Zeichen bleibt identisch.
Bleibt eine Variable Font nach dem Subsetten variabel?
Ja. pyftsubset entfernt mit einer Glyphe auch deren Variations-Deltas, lässt die fvar-Tabelle und die Interpolationsdaten der behaltenen Glyphen aber unangetastet. Nach meinem Subset stand die wght-Achse mit 400 bis 700 noch drin, und font-weight: 550 funktionierte weiter.
Wie groß darf eine Webfont sein? Als Orientierung: Unter 30 KB pro Datei merkt kein Nutzer, dass da eine Schrift geladen wird. Ab ungefähr 100 KB wird es spürbar, besonders im Mobilfunknetz. Wichtiger als die absolute Zahl ist die Anzahl der Requests, weil jede Datei einen eigenen Roundtrip kostet.
Brauche ich fonttools, oder geht es auch mit einem Online-Tool? Für einmalige Arbeit reicht ein Online-Subsetter. Sobald die Schrift ein Update bekommt, gewinnt das Script: Ein Befehl, dasselbe Ergebnis, nachvollziehbar im Repo. Die zwei Minuten für das Virtual Environment holst du beim ersten Foundry-Update wieder rein.
Darf ich eine gekaufte Schrift überhaupt subsetten? Bei SIL OFL und Apache ausdrücklich ja. Bei kommerziellen Webfont-Lizenzen steht es manchmal nicht drin, und einzelne Foundries verlangen die unveränderte Datei. Lies die Lizenzdatei, bevor du schneidest.
Der Weg von 277 auf 29 Kilobyte in Zahlen
| Schritt | Größe |
|---|---|
| 6 statische Schnitte als TTF | 284.052 B |
| Variable Font, komplett, WOFF2 | 41.860 B |
| Gewichte auf 400 bis 700 beschnitten | 44.540 B |
| + Subset auf deutsche Ranges | 29.400 B |
| (mit Latin Extended-A für Namen) | 35.104 B |
| (nur ein fester Satz Text) | 8.588 B |
Zwei Dinge nehme ich mit. Die Variable Font selbst bringt schon den größten Sprung, noch bevor du irgendwas wegschneidest: ein Sechstel der Bytes bei mehr Möglichkeiten im Layout. Und beim Subsetting kommt die Ersparnis fast komplett aus den Glyphen, nicht aus den Gewichten. Wer nur die Achse beschneidet, hat am falschen Hebel gezogen.
Für eine deutsche Website bleiben am Ende sechs Unicode-Ranges übrig. Denk an das Eurozeichen und an die Anführungszeichen, und spar nicht an den letzten sieben Kilobyte, indem du einzelne Buchstaben von Hand aufzählst. Diesen Bug findest du sonst erst, wenn ihn dir ein Nutzer meldet.
Quellen und Weiterlesen: