arabic.css:95 · Zain 166 · Cairo 291
Warum setzt meine arabische Schrift meinen englischen Text?
Weil die Schriftdatei nicht das ist, was ihr Name nahelegt. Öffne irgendeine arabische Webschrift mit fontTools und zähle die Codepoints, die sie zwischen U+0041 und U+024F deklariert: Zain, das Face, das diese Website heute ausliefert, hat 166; Cairo, das Face davor, hat 291; Noto Kufi Arabic hat 248. Alle drei tragen obendrein den vollständigen druckbaren ASCII-Satz aus 95 Codepoints. Eine arabische Schrift ganz ohne Latein ist die Ausnahme, nicht die Regel.
Den Rest erledigt das Font-Matching, und zwar pro Zeichen statt pro Run. Für jeden Codepoint geht der Browser die font-family-Liste durch und hält bei der ersten Familie an, die eine Glyphe dafür hat — nirgends im Algorithmus wird gefragt, zu welchem Schriftsystem das Zeichen gehört. Stell eine arabische Familie in einer pauschalen Regel nach vorn, und sie gewinnt auch das Lateinische, lautlos, weil das Ergebnis lesbar und nur ein bisschen falsch ist.
Dieses Repository hat jahrelang das Gegenteil behauptet. Der Kopf von src/styles/locale/arabic.css nannte den Grund dafür, Cairo in jedem Stack nach vorn zu stellen: Die Dateien „tragen nur arabische Glyphen — Latein fällt auf Quicksand durch, und die eigene Identität der Site hält für Namen und Code.“ Gemessen war das falsch. Jeder Projektname, jeder HTML- und CSS-Chip und die Zeichenkette Google AI Essentials auf den arabischen Seiten wurden in Cairo gesetzt, 381 lateinische Runs, über die gesamte Lebensdauer des Builds.
| Schrift | Druckbares ASCII, U+0020 bis U+007E | Arabisch, U+0600 bis U+06FF | Latein, U+0041 bis U+024F |
|---|---|---|---|
| Zain Regular, Bold, ExtraBold | 95 | 109 | 166 |
| Cairo Regular, SemiBold | 95 | 102 | 291 |
| Noto Kufi Arabic Bold | 95 | 256 | 248 |
| Quicksand Regular | 95 | 0 | 357 |
Die letzte Zeile der Tabelle ist gleich doppelt wichtig. Quicksand trägt null arabische Codepoints, und deshalb hat ein arabischer Run, dessen Stack keine arabische Familie nennt, nirgendwohin auszuweichen — das ist die zweite Hälfte dieses Artikels, und dieselbe Website hat sie zur selben Zeit ausgeliefert, in der Gegenrichtung.
CSS Fonts 4 §4.5 · initial U+0-10FFFF
unicode-range ist nicht die Datei, nicht die Familie und nicht das Dokument
Der Deskriptor beantwortet genau eine Frage — darf dieses Face für diesen Codepoint verwendet werden — und CSS Fonts 4 gibt ihm in einem einzigen Absatz zwei Aufgaben. Die Vereinigung der deklarierten Bereiche ist „ein Hinweis für User Agents bei der Entscheidung, ob eine Schriftressource für einen gegebenen Textrun geladen wird“, und dann, deutlich härter: „User Agents dürfen die Schrift für Codepoints außerhalb dieser Menge weder laden noch verwenden.“ Der Initialwert ist U+0-10FFFF, ein Face ohne deklarierte Range darf also für ganz Unicode antworten — genau das taten hier bis August alle Faces.
Der Inhalt der Datei ist es nicht, und die Spezifikation sagt das ausdrücklich: „Die effektive Character Map ist die Schnittmenge der von unicode-range definierten Codepoints mit der Character Map der Schrift.“ Die vier Zain-Faces hier deklarieren 1.264 Codepoints und decken 252 davon ab, 19,9 Prozent. Drei der deklarierten Blöcke sind in der Datei vollständig leer, und U+200F, die Rechts-nach-links-Marke, ebenfalls. Nichts geht kaputt, denn die Schnittmenge ist die Regel, und die Spezifikation segnet es ab, breite Bereiche zu deklarieren, ohne die Schrift zu prüfen.
Subsetting ist es auch nicht, und diese Verwechslung kostet mehr als einen Vokabelpunkt. Subsetting ist ein Build-Schritt, der die Schrift umschreibt, und in diesem Projekt wurde gemessen, dass er das Schriftsystem zerstört: Arabische Punkte sind eigene Glyphen, die erst beim Rendern zusammengesetzt werden, und ein Subsetter, der wegwirft, was er für unerreichbar hält, wirft sie weg. Die Entscheidung fiel für Cairo und gilt unverändert für Zain. Diese Dateien sind mit Absicht vollständige Schriften, mit einer eingeschränkten Range davor.
| Deklarierte Range | Codepoints deklariert | Codepoints abgedeckt |
|---|---|---|
| U+0600-06FF, Arabic | 256 | 109 |
| U+0750-077F, Arabic Supplement | 48 | 0 |
| U+0870-088E, Arabic Extended-B | 31 | 0 |
| U+08A0-08FF, Arabic Extended-A | 96 | 0 |
| U+200F, Rechts-nach-links-Marke | 1 | 0 |
| U+FB50-FDFF, Presentation Forms-A | 688 | 54 |
| U+FE70-FEFF, Presentation Forms-B | 144 | 89 |
| Alle sieben, wie auf jedem Face deklariert | 1.264 | 252 |
Zwei Codepoints laufen andersherum — in der Datei vorhanden, in der Range abwesend. U+0020 und U+0030 stehen beide in Zains Character Map und fallen in keinen der deklarierten Bereiche, dem Browser ist Zain für ein Leerzeichen oder eine Null also verboten. Das ist Absicht, es ist der Grund, warum die Lücke zwischen zwei arabischen Wörtern bei Quicksand landet, und es hat eine Folge im letzten Abschnitt, die hier noch niemand gemessen hat.
nav-lang.css · 5 faces · 70 routes
Warum dieselbe Technik auf der Website davor scheiterte
Der Vorgänger dieser Website hat unicode-range zuerst probiert und daraus geschlossen, dass es nicht funktioniert. Er hängte Faces mit arabischer Range unter die eigenen Familiennamen der Site — Cairo unter Quicksand, Cairo noch einmal unter Quicksand Label, Noto Kufi Arabic unter Quicksand Display — genau um einen Kaskadenkampf mit einem 183 KB großen gemeinsamen Stylesheet zu vermeiden. Fünf Faces, auf allen 70 Routen. Sein eigener Kommentar hält das Ergebnis fest: „Chromium löste arabische Runs weiter über die Quicksand-Faces mit voller Range auf und fiel auf das System-Naskh.“
Die Messung stimmte, der Schluss daraus nicht. unicode-range wird konsultiert, nachdem ein Face bereits über den Stil gewählt wurde, nicht davor, und Faces fallen für diesen Schritt nur dann zu einem Composite Face zusammen, wenn ihre übrigen Deskriptoren identisch sind: „Eine Gruppe von Faces, die über @font-face-Regeln mit identischen Deskriptorwerten, aber unterschiedlichen unicode-range-Werten definiert sind, gilt für diesen Schritt als ein einziges Composite Font Face.“ Diese hier waren nicht identisch. Die lateinischen Faces der Site deklarierten Gewichtsbereiche von 200 300, 400 und 500 900; die arabischen 100 500 und 501 900.
Die Verengung über das Gewicht ließ also mehr als ein Face stehen, und die Spezifikation gibt die Entscheidung dann aus der Hand. Der User Agent „muss eine einzelne Schrift aus der passenden Menge wählen“, und „welche Schrift gewählt wird, kann sich zwischen mehreren User Agents und mehreren Betriebssystemplattformen unterscheiden“. Wählte er ein lateinisches Face, war die arabische Glyphe nicht da, und der nächste Satz macht die Tür zu: „Glyphen aus anderen Faces der Familie werden nicht berücksichtigt.“ Die Familie wird als Ganzes verworfen, der nächste Familienname wird probiert, und am Ende der Liste läuft der Fallback auf installierte Schriften, dessen Ergebnis „zwischen User Agents variieren kann“. Das ist das System-Naskh.
/* die arabische Schrift, gehängt unter den eigenen Familiennamen */
@font-face {
font-family: 'Quicksand';
src: url('Cairo-SemiBold.ar.v2.woff2') format('woff2');
font-weight: 501 900;
unicode-range: U+0600-06FF, U+0750-077F, U+08A0-08FF,
U+FB50-FDFF, U+FE70-FEFF, U+200C-200F;
}
/* und, anderswo im selben 183-KB-Sheet, das echte Quicksand */
@font-face {
font-family: 'Quicksand';
src: url('Quicksand-SemiBold.woff2') format('woff2');
font-weight: 500 900;
/* keine unicode-range: der Initialwert ist ganz Unicode */
}
/* die Deskriptoren unterscheiden sich, also sind das zwei Faces, kein */
/* Composite Face. Die Stilverengung läuft zuerst und behält evtl. beide. */
Dasselbe Repository liefert das Gegenbeispiel, und es lohnt sich, es neben den Fehler zu stellen. Zehn Demo-Schriftverzeichnisse tragen Google Fonts’ eigenen latin- und latin-ext-Split, erzeugt von tools/localize-demo-fonts.mjs: 109 Faces behalten, 100 Faces anderer Schriftsysteme weggelassen. Diese Faces haben bis auf die Range tatsächlich identische Deskriptoren, bilden also ein Composite Face, und das Ergebnis ist in jeder Engine deterministisch. Google teilt ein Design über Bereiche auf. Der Golden Master hängte ein anderes Design unter einen bestehenden Namen bei einem anderen Gewicht — die eine Anordnung, die die Composite-Regel nicht abdeckt.
legacy 11-route probe · comment said 2.5 KB
Ein arabisches Wort kostete 50.096 Bytes auf jeder englischen Seite
Die zweite Rechnung aus dieser Anordnung wurde mit einer Fetch-Probe über elf Routen der alten Site gemessen. Cairo-SemiBold.ar.v2.woff2, 50.096 Bytes, lag nirgends im Preload und wurde auf allen elf Routen geladen — auch auf jeder englischen und deutschen. Sie kam mit initiatorType css zwischen 610 und 640 ms auf fünf gewöhnlichen Routen an, mit 1.118,4 ms auf der 404-Seite und mit 1.464,2 ms auf der Startseite.
Der Auslöser war eine einzige Zeichenkette: das arabische Label des Sprachumschalters, vier Zeichen in einem Navigationsmenü, das niemand geöffnet hatte. Der Kommentar neben dem Code behauptete, englische und deutsche Seiten lüden dafür einmalig rund 2,5 KB. Das Architektur-Protokoll benennt die Diskrepanz nüchtern — um etwa das Zwanzigfache daneben — und ergänzt den Teil, der daraus einen Defekt statt einer Kuriosität macht: Wer gegen den Kommentar statt gegen die Messung optimiert, findet die 50 KB nie.
| Datei | Preload auf | Geladen auf | Bytes |
|---|---|---|---|
| Quicksand-Regular.woff2 | en, de, ar | 11 von 11 | 30.844 |
| Quicksand-Medium.woff2 | en, de | 10 von 11 | 30.784 |
| Quicksand-SemiBold.woff2 | en, de | 11 von 11 | 31.084 |
| Quicksand-Light.woff2 | nie | 0 von 11 | 29.000 |
| Cairo-Regular.ar.v2.woff2 | ar | 1 von 11 | 49.084 |
| Cairo-SemiBold.ar.v2.woff2 | nie | 11 von 11 | 50.096 |
| NotoKufiArabic-Bold.ar.v2.woff2 | ar | 1 von 11 | 60.720 |
| Alle sieben Dateien | — | — | 281.612 |
Ob eine versteckte Zeichenkette etwas kostet, ist eine Frage nach Boxen, nicht nach Sichtbarkeit. Die Basisregel des alten Menüpanels trug im ausgelieferten Stylesheet den offenen clip-path; der geschlossene Zustand, display none und eine flachgedrückte Ellipse, wurde beim Boot von der Laufzeit als Inline-Style geschrieben. Das Panel wurde also beim ersten Paint gelayoutet und erst danach versteckt — und ein gelayouteter Textrun ist ein Textrun, für den die Download-Regel gilt.
Dieser Build deklariert den geschlossenen Zustand statisch — das Panel trägt display none im ausgelieferten Sheet, im eigenen Kommentar als der endgültige und der ausgegebene Zustand beschrieben. Das sollte heißen: keine Boxen und kein Fetch, bis das Menü öffnet. Auf diesem Build ist es nicht gemessen, und der vorige Build hat die entsprechende Behauptung als falsch gemessen — behandle es also als Mechanismus mit angehängter Prüfung, nicht als Ergebnis. Öffne das Netzwerk-Panel, filtere auf Font, lade eine englische Seite, öffne dann das Menü und schau zu.
arabic.css:104-146 · 4 faces, one range
Die Range, wie ausgeliefert, und das Loch, das sie am selben Tag aufriss
Die Regel kam mit dem Schriftwechsel, und der Grund war Drift. Zains Latein ist ein anderes Design als das von Cairo: Versalhöhe 69,5 Prozent des Gevierts gegen Cairos 69,1, aber x-Höhe 46,0 gegen 50,0 — 8 Prozent kleiner bei praktisch gleicher Versalhöhe. Eine unentdeckte Capture wäre unter dem neuen Face sichtbar schlimmer geworden, also brachte die Änderung, die Zain einführte, auch die Deklaration mit, die Zain das Antworten für Latein ganz verbietet.
Vier Faces, zwei synthetische Familien, eine identische Range auf allen, geteilt bei Gewicht 650, damit die bestehenden 600er- und 700er-Anhebungen ohne eine einzige geänderte Regel den richtigen Schnitt wählen. Die Nutzlast ist bis auf ein Kilobyte genau neutral: 161.096 Bytes Zain gegen die 159.900, die die beiden ersetzten Familien kosteten, ein Unterschied von 1.196 Bytes.
@font-face {
font-family: 'Zain AB';
src: url('../assets/fonts/zain/regular.woff2') format('woff2');
font-weight: 100 500;
font-display: swap;
size-adjust: 112%;
unicode-range:
U+0600-06FF, U+0750-077F, U+0870-088E, U+08A0-08FF,
U+200F, U+FB50-FDFF, U+FE70-FEFF;
}
html[lang='ar'] body,
html[lang='ar'] body * {
font-family: 'Zain AB', Quicksand, Arial, sans-serif;
}
/* Zain hier ZULETZT, mit Absicht: Ziffern und Bezeichner behalten die Mono */
html[lang='ar'] :is(code, pre, .cdx-code) {
font-family: ui-monospace, menlo, 'Zain AB', 'Courier New', monospace;
}
Eine Range hinzuzufügen schafft sofort eine Klasse von Codepoints, die die Familie nicht mehr setzen darf, und der erste davon ist das Leerzeichen zwischen zwei arabischen Wörtern. Jeder Stack, der Zain nennt, braucht jetzt eine Familie dahinter, die alles abdeckt, was die Range ausschließt — und einer hatte keine. Dem Display-Stack fehlte Quicksand, also landeten das Leerzeichen in einer zweiwörtigen arabischen Überschrift, das Und-Zeichen in einer anderen und jede Ziffer bei Arial: zwei Arial-Glyphen in der größten Schrift der Seite, gefunden mit CSS.getPlatformFontsForNode.
Die Messung, die belegt, dass die Range wirkt, ist dieselbe Messung, die das Loch fand, das sie aufriss. Nach dem Fix löst jede lateinische Glyphe auf den arabischen Seiten zu Quicksand oder Menlo auf und keine einzige zu Zain.
Die beiden size-adjust-Werte in dieser Auflistung, 112 Prozent für den Fließtext und 103 Prozent für Display, stammen aus einer eigenen Messung — dem Verhältnis von Zahn und Alif.
P11-03 · found and fixed 2026-08-26
Der umgekehrte Fehler: ein arabisches Wort ohne arabisches Face
Der Sprachumschalter druckt alle drei Sprachnamen in ihrer eigenen Sprache auf allen drei Dokumenten, und genau darum geht es: Wer die aktuelle Seite nicht lesen kann, erkennt trotzdem die eigene. Das Markup sagte bereits die Wahrheit. Der arabische Anker trägt hreflang, lang und dir, und die Zeichenkette ist einmal deklariert und in allen drei Content-Bundles als geteilt gelistet, sodass kein Locale sie übersetzt.
Was ihr fehlte, war ein Face. Jede arabische Regel im Stylesheet war auf das Dokumentelement bezogen, und das ist eine Tatsache über die Seite, nicht über den Textrun. Auf dem englischen und dem deutschen Dokument erbte das Label stattdessen das lateinische Label-Token — Quicksand Label, dann Arial, dann sans-serif — und Quicksand enthält exakt null Codepoints zwischen U+0600 und U+06FF. Der Browser hat den Stack erschöpft und den Fallback auf installierte Schriften laufen lassen.
Gemessen unter macOS in WebKit und in Chromium: Das Label wurde im Naskh der Plattform gesetzt, neben vier Quicksand-Labels, in anderem Gewicht, anderer Farbdichte und anderer optischer Größe. Auf einem Gerät ohne arabische Systemschrift erscheint es als Tofu. An dem Run war nichts falsch außer dem CSS, und das CSS hatte eine Frage über das Dokument gestellt, wo die Frage vier Zeichen betraf.
- العربية
- Die Zeichenkette selbst, einmal deklariert und im englischen, deutschen und arabischen Bundle als geteilt gelistet, damit alle drei Dokumente sie auf Arabisch drucken, statt sie zu übersetzen. Von Anfang an richtig.
- das Markup
- src/components/site-nav.html setzt hreflang, lang und dir auf den Anker und fragt den Splitter nach Zeilen statt nach Wörtern und Zeichen. Von Anfang an richtig.
- das Stylesheet
- Jede Regel, die ein arabisches Face hätte liefern können, war auf das Dokumentelement bezogen. Auf den beiden lateinischen Dokumenten traf keine einzige zu, und der Run fiel ganz aus dem Stack.
Er überlebte ein eigens gebautes RTL-Gate, ein dreisprachiges Content-Register und einen Build, der Parität über drei Locales erzwingt — denn keines davon stellt der englischen Seite eine Frage über Arabisch. Gefunden wurde er, indem jemand den Kopf der Datei las und ihm nicht glaubte.
Der arabische Anker des Umschalters verlässt sich auf den lateinischen Seiten für seine Bidi-Behandlung auf das User-Agent-Stylesheet, und das ist die Art von Lücke, die logische Eigenschaften nicht schließen.
utilities beats components, no !important
Auf die Sprache zielen, nicht auf das Dokument
Der Fix sind zwei Regeln, und die zweite Hälfte der ersten trägt Last, statt Gürtel und Hosenträger zu sein. Die Familie kommt auf das Sprachattribut und auf seine Nachfahren, weil components/button.css font-family aus einem Token auf dem span im Anker setzt — und Vererbung schlägt niemals eine Deklaration am Element selbst. Eine Regel nur auf dem Anker würde ausgerechnet an dem Element verlieren, das die Tinte trägt.
Sie kommt ohne !important aus, wegen der Layer-Reihenfolge. src/styles/order.json deklariert reset, tokens, base, layout, components, sections, utilities, overrides; locale/arabic.css sitzt in utilities und button.css in components, zwei Layer früher, und ein späterer Layer schlägt jede Spezifität, die ein früherer schreiben kann. Die arabische Pauschalregel daneben trägt ein einziges !important, für letter-spacing, und sie ist die einzige gerechtfertigte Pauschalregel ihrer Art im Projekt. Diese Regel braucht keines.
- Die Sprache benennen, nicht die Seite Ersetze den dokumentbezogenen Selektor durch einen auf dem Sprachattribut, überall dort, wo etwas für einen arabischen Run gelten muss, wo immer er auftaucht. Der Dokumentbezug bleibt für alles, was wirklich das Dokument betrifft, etwa die Laufrichtung.
- Das Element erreichen, das die Tinte trägt Ergänze die Nachfahren-Hälfte des Selektors. Ein Kind, das seine eigene font-family deklariert, erbt deine nicht — die Regel muss dieses Kind also mittreffen.
- Den Layer entscheiden lassen Leg die Regel in einen Layer nach dem, der das Token deklariert hat. Kein Spezifitätskampf, kein !important, und derselbe Mechanismus hindert ein Section-Sheet daran, eine Locale-Korrektur zu überschreiben.
- Die Laufweite auf null setzen Der Body setzt letter-spacing auf minus 0,005em, und der Label-Span deklariert keine eigene, also erbte das arabische Wort sie auf den lateinischen Seiten. Arabisch wird nie gesperrt: Laufweite bricht die verbundene Schrift, die Buchstaben berühren sich nicht mehr, und das Wort hört auf, ein Wort zu sein.
- Zeilenbox und Clip-Fenster gemeinsam öffnen Das Label steht durch die Button-Komponente auf line-height 1 und wird bei 94 Prozent seiner Box beschnitten. Zains Content-Box liegt bei size-adjust 112 Prozent bei 1,859em, bei line-height 1 ist das halbe Leading also negativ und die Tinte läuft über den Schnitt hinaus. Die Regel setzt 1,55 und schiebt das Fenster auf 114 Prozent.
- Für die Metriken auf die Sprach-Pseudoklasse wechseln Die Metrik-Regel nutzt :lang(ar) statt des Attributselektors. Die Inhaltssprache wird an Nachfahren vererbt und über erweiterte Filterung gematcht, sie deckt also auch ar-EG und ar-Arab-EG ab, was ein exakter Attributtreffer nicht tut.
Die Clip-Arithmetik reproduziert sich exakt, und so wurden die Zahlen in dieser Regel gewählt statt geraten. Zains hhea gibt Ascent 869 und Descent minus 459 auf 800 Einheiten pro Geviert; bei size-adjust 112 Prozent sind das 1,2166em und 0,6426em, eine Content-Box von 1,8592em. Bei line-height 1 beträgt das halbe Leading minus 0,4296em, und die Grundlinie liegt 0,7870em unter der Oberkante der Zeilenbox, was die tiefste Tinte des Labels auf 1,2505em bringt, gegen einen Schnitt bei 0,9400em. Bei 1,55 mit dem Fenster auf 114 Prozent landet die Tinte bei 1,5716em, das Fenster bei 1,7670em und der Hover-Zwilling bei 1,8454em, in dieser Reihenfolge. Glyphentiefen sind an isolierten Formen gemessen.
Die Datei formuliert die Invariante als Zwilling unter Fenster, Fenster unter Tinte, und sie hält fest, was passiert, wenn sie bricht. Im Footer-Link der alten Site lief die Tinte fünf Pixel über den Schnitt hinaus, ein finales Yeh verlor seine Punkte, und das Wort wurde als ein anderes Wort gesetzt — eine Falschschreibung der eigenen Handschrift des Inhabers. Auf den arabischen Seiten war es behoben und auf den englischen und deutschen seither live, an dem einen arabischen Wort, das diese Dokumente enthalten.
Die 1,55 in dieser Regel ist eine Clip-Fenster-Zahl und keine Kollisionszahl, und der Unterschied zwischen beiden ist der gemessene Boden für arabische Zeilenhöhe.
split-registry.js:121-190 · read once
Derselbe Fehler, eine Ebene tiefer, im JavaScript
Die Split-Registry liest die Sprache des Dokuments genau einmal, beim Boot, und ihr Kommentar nennt einen ordentlichen Grund: lang und dir werden vom Build auf das Dokumentelement geschrieben und ändern sich danach nie, pro Element zu fragen wären also 55 Lesungen einer einzigen Tatsache und 55 Gelegenheiten, sie unterschiedlich zu lesen. Aus dieser einen Lesung entscheidet sie dann, ob ein Run in Zeichen zerlegt werden darf.
Auf den arabischen Seiten stimmt das. Auf den englischen und deutschen ist es der Fehler des Stylesheets in einer anderen Sprache: Das Dokument ist en oder de, das arabische Label würde also pro Zeichen zerlegt. Zeichenweises Zerlegen ist in einer verbundenen Schrift nicht kosmetisch. Die Reveal-Komponente macht aus jedem Zeichen ein inline-block, und das muss sie, weil transform auf eine nicht ersetzte Inline-Box nicht wirkt — und eine atomare Box pro Buchstabe ist eine Box, über die der Shaper nicht verbinden kann. Das Wort kommt in isolierten Formen heraus, also falsch geschrieben.
Das Markup umgeht das von Hand. Dieser eine Span fragt den Splitter nach Zeilen, während die englischen und deutschen Labels nach Wörtern und Zeichen fragen, und der eigene Kommentar des Moduls räumt es ein: Die Komponentendatei war für das eine Label, das jemandem aufgefallen war, längst von Hand zu diesem Schluss gekommen.
| Fehler | Wo | Was der Browser tat | Was es kostete |
|---|---|---|---|
| Zu viel Capture | die arabischen Seiten, Cairo zuerst und ohne Range | beantwortete jeden lateinischen Codepoint aus der arabischen Datei | 381 lateinische Runs in der falschen Schrift |
| Zu viel Capture, beim Download | die alten englischen und deutschen Seiten | ließ zwei Faces stehen und wählte willkürlich eines | 50.096 B auf 11 von 11 Routen |
| Zu wenig Capture | die englischen und deutschen Seiten, jede Regel am Dokument | erschöpfte den Stack und lief in den Fallback auf installierte Schriften | 1 Wort im System-Naskh |
| Zu wenig Capture, im Skript | die englischen und deutschen Seiten, die Split-Registry | hätte eine verbundene Schrift in atomare Inline-Boxen zerlegt | 1 Wort falsch geschrieben auf dem Schirm |
Das automatische Gate kann drei der vier Zeilen unten nicht sehen. tools/golden/rtl.mjs läuft über jeden Textknoten und prüft, dass jeder arabische Run Zain anfordert und jeder lateinische Run einen lateinischen Fallback behält — aber nur auf arabischen Routen. Es lädt die englische Startseite als Kontrolle für den Splitter, den Header, das Diptychon und die Leiste und stellt ihr die Schriftfrage nie. Ein Grep über Tests und Contract-Checks liefert für die neue sprachbezogene Regel weiterhin nichts. Ein eigens gebautes RTL-Gate lief grün, während der Defekt auf den beiden meistbesuchten Dokumenten des Produkts lebte.
Warum ein inline-block pro Buchstabe ein arabisches Wort zerbricht und was ein Splitter stattdessen tun muss, ist das Problem des Splittens verbundener Schriften.
glyphCount, not fontFamily
Wie beweist du, welches Face tatsächlich gewonnen hat?
Der Computed Style sagt es dir nicht. Er meldet den Stack, den du geschrieben hast, und das eigene Gate dieses Repositorys sagt das in einem Kommentar an der Stelle, an der es aufgibt: Ein rein lateinischer Run in einem arabischen Dokument darf legitim zuerst die arabische Familie anfordern, unicode-range ist das, was ihn weiter zu Quicksand leitet, und ein Computed Style kann das nicht sehen. Was das Gate prüfen kann, ist die schwächere, immer noch nützliche Eigenschaft: dass kein Run eine Familie anfordert, hinter der kein lateinischer Fallback steht.
CSS.getPlatformFontsForNode über das Chrome DevTools Protocol entscheidet die Sache. Es liefert pro Knoten die Plattform-Familien, die den Text tatsächlich gesetzt haben, ob jede geladen oder lokal aufgelöst wurde und wie viele Glyphen jede beigesteuert hat. Das hat die beiden Arial-Glyphen in einer Display-Überschrift gefunden und danach bestätigt, dass keine lateinische Glyphe auf den arabischen Seiten Zain ist. Von Hand ist es der Block Rendered Fonts unten im Computed-Bereich.
- CSS.getPlatformFontsForNode
- Liefert familyName, isCustomFont und glyphCount für jede Plattformschrift, die den Text eines Knotens gesetzt hat. Das einzige der vier, das beantwortet, welches Face gewonnen hat.
- Rendered Fonts, Computed-Bereich
- Dieselben Daten von Hand, unten im Bereich. Mehrere Familien an einem Element bedeuten, dass darin ein Fallback stattgefunden hat.
- das Netzwerk-Panel, gefiltert auf Font
- Eine andere Frage: ob die Datei überhaupt geladen wurde. Eine geladene Datei kann trotzdem jeden Codepoint an eine Familie vor ihr verloren haben.
- document.fonts.check()
- Eine Falle. MDN sagt, es sei nicht dafür gedacht zu prüfen, ob ein bestimmter Schriftstil gerendert werden kann, und sein Text-Argument ist per Vorgabe ein einzelnes Leerzeichen — gegen ein Face, dessen Range U+0020 ausschließt, ist die Antwort gehaltlos. Übergib die echte Zeichenkette.
Eine Folge dieser Range ist hier nicht gemessen, und sie ist auf jeder Site einen Blick wert, die ch für das Zeilenmaß nutzt. Die Einheit ch ist die Vorbreite von U+0030 in der Schrift, die es setzt, und U+0030 liegt außerhalb der Zain-Deklaration, der Browser darf Zain dafür also nicht verwenden und Quicksand liefert es stattdessen. Der Weg von Cairo zu Quicksand hat 1ch um 0,09 Prozent verschoben. Wäre Zain in voller Range geblieben, wäre 1ch um 25,4 Prozent gewachsen, über mehr als zwanzig ch-basierte Maße in diesem Stylesheet.
Dieselbe Klausel hat eine leisere Wirkung. Die erste verfügbare Schrift — die, gegen die ex, cap und lh auflösen — ist definiert als die erste Schrift, für die U+0020 nicht durch eine unicode-range ausgeschlossen ist, und U+0020 liegt ebenfalls außerhalb der Zain-Range, Zain kann hier also auf keinem Dokument je die erste verfügbare Schrift sein. Die Vorbreiten oben sind gemessen; die gerenderten Spaltenbreiten auf den arabischen Seiten davor und danach nicht. Vergleiche sie selbst, bevor du dich in die eine oder andere Richtung festlegst.
Der dreisprachige Build, aus dem jede einzelne dieser Zahlen gelesen wurde, ist einer der zehn Konzept-Websites.