Давай поиграем со шрифтами

Никита Дубко, Яндекс Контест

Давай поиграем
со шрифтами

Никита Дубко, Яндекс Контест

Давай поиграем

со шрифтами

Никита Дубко, Яндекс Контест

Никита
Дубк�

Шрифты повсюду

Р̷̢̃а̴̼͇͓̅̾з̴͈͇̌н̴̛̗̖͠ы̶̦̟̉̍е̸̧͑̇ ̴̻̓ш̸͍̩͐̚р̴͇͓̺͑̍̽ӥ̷͖̫́̎ф̶̨͚̔͗̆͜т̶̺͐ы̴̲̐͆

Web Alamanac 2024. Fonts

Часть 1
Что такое шрифт

Дисклеймер

Спикер — не профессиональный шрифтовик, он всего лишь несколько раз разбирал шрифт на глифы и отключал одну лигатуру. Если вы зачем-то продолжаете читать этот длинный текст, то вот вам пара интересных фактов. Известный шрифт Comic Sans был разработан для использования в детских программах и облачках слов виртуальных помощников. Самым распространенным шрифтом в мире является шрифт Helvetica, созданный Максом Мидингером в 1957 году.

Шрифт — набор таблиц

SFNT
Scalable Font File

head

macStyle

head — Font Header Table
head — Font Header Table

cmap
Character to Glyph Mapping

U+262F

☯︎

FontForge

OS/2

GSUB, GPOS

Ў

Шрифт — программа

Font with Built-in Syntax Highlighting
Fontemon

llama.ttf

CVE-2025-27363

CVE-2025-27363 // FreeType

Часть 2
Как браузер
рисует шрифт

Пайплайн

  1. Стилизация
  2. Разметка
  3. Шейпинг
  4. Растеризация
  5. Композиция
<html>
   <head>
      <title>Demo</title>
   </head>
   <body>
      <p class="arial">Arial text!</p>
      <p class="times">Times text?</p>
      <p>Just text.</p>
   </body>
</html>
        
.arial {
    font-family: Arial;
    font-weight: 400;
}

@font-face {
    font-family: Arial;
    src: url(arial-bold.woff2) format("woff2");
    font-weight: 700; /* ❌ */
}

@font-face {
    font-family: Arial;
    src: url(arial-regular.woff2) format("woff2");
    font-weight: 400; /* ✅ */
}
<html>
   <head>
      <title>Demo</title>
   </head>
   <body>
      <p class="arial">Arial text!</p>
      <p class="times">Times text?</p>
      <p>Just text.</p>
   </body>
</html>
        
.times {
    font-family: MyTimes;
    font-weight: 600;
}

@font-face {
    font-family: MyTimes;
    src: url(my-times.woff2) format("woff2");
    font-weight: 400; /* 🤔 */
}
How Is Missing CSS font-weight Substituted by the Browser?

Варианты событий

<html>
   <head>
      <title>Demo</title>
   </head>
   <body>
      <p class="arial">Arial text!</p>
      <p class="times">Times text?</p>
      <p>Just text.</p>
   </body>
</html>
        

Times

Пайплайн

  1. ✅ Стилизация
  2. 👉 Разметка
  3. Шейпинг
  4. Растеризация
  5. Композиция

Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur suscipit inventore dolore cum voluptates dignissimos aliquam saepe qui earum quisquam illum quam ipsum similique, fuga accusantium fugit laudantium molestias amet!

Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur suscipit inventore dolore cum voluptates dignissimos aliquam saepe qui earum quisquam illum quam ipsum similique, fuga accusantium fugit laudantium molestias amet!

Lorem ipsum dolor sit amet consectetur adipisicing elit. Consectetur suscipit inventore dolore cum voluptates dignissimos aliquam saepe qui earum quisquam illum quam ipsum similique, fuga accusantium fugit laudantium molestias amet!

Пайплайн

  1. ✅ Стилизация
  2. ✅ Разметка
  3. 👉 Шейпинг
  4. Растеризация
  5. Композиция

Пайплайн

  1. ✅ Стилизация
  2. ✅ Разметка
  3. ✅ Шейпинг
  4. 👉 Растеризация
  5. Композиция

й

Пайплайн

  1. ✅ Стилизация
  2. ✅ Разметка
  3. ✅ Шейпинг
  4. ✅ Растеризация
  5. 👉 Композиция
Why web fonts matter: a short history (and future) of font rendering

А почему у вас
не пиксель-пёрфект?

Text Rendering Hates You

Пайплайн

  1. 🧑‍🎨 Стилизация
  2. 👽 Разметка
  3. 🕺 Шейпинг
  4. 👽 Разметка
  5. 🧑‍🎨 Стилизация
  6. 👽 Разметка
  7. 🕺 Шейпинг
  8. 💃 Растеризация
  9. 🥶 Композиция

HarfBuzz

HarfBuzz
HarfBuzz
Font Fingerprinting

Часть 3
Как управлять шрифтом

Deep dive CSS: font metrics, line-height and vertical-align
FontDrop!
cap = em-size *  units-per-em / cap-height
lh = ascent + descent + line-gap
bottom = descent
top = ascent - cap-height
--font: Helvetica;
--fm-unitsPerEm: 2048;
--fm-capitalHeight: 1469;

--cap: (var(--fm-capitalHeight) / var(--fm-unitsPerEm));
            --cap: 1cap;
        
--font: Helvetica;

--fm-unitsPerEm: 2048;
--fm-descender: 471;
--fm-ascender: 1577;
--fm-linegap: 0;

--lh: (
    (var(--fm-ascender) + var(--fm-descender) + var(--fm-linegap))
        / var(--fm-unitsPerEm)
);
            --lh: 1lh;
        

Font Relative Units

CSS Values and Units Module Level 4

Часть 4
Как загружать шрифты

@font-face

@font-face {
    font-family: "Trickster";
    src: url("trickster.woff2") format("woff2");
}
@font-face {
    font-family: "Trickster";
    src: url("trickster.woff2") format("woff2");
    font-display: swap;
    font-style: italic;
    font-weight: 400;

    ascent-override: 90%;
    descent-override: 85%;
    line-gap-override: 125%;
    size-adjust: 90%;

    font-variation-settings: "xhgt" 0.7;
    font-feature-settings: "swsh" 2;
}
<p style="font-family: Trickster">Some text!</p>
<p>Just text.</p>
        
@font-face {
    font-family: "Trickster";
    src: url("trickster.woff2") format("woff2");
}

FOUT
Flash of Unstyled Text
IE4, 1997

FOIT
Flash of Invisible Text
Safari, 2008

⏱️ 3 секунды
Firefox, 2011

🥷 Невидимый запасной шрифт

font-display

5 steps to faster web fonts
5 steps to faster web fonts
5 steps to faster web fonts
5 steps to faster web fonts

👍 font-display: swap

A Comprehensive Guide to Font Loading Strategies
<link rel="preload"
      href="font.woff2"
      as="font"
      type="font/woff2">

Font Subsetting

npm install -g glyphhanger

glyphhanger https://www.filamentgroup.com/ --subset=Roboto-Regular.ttf --formats=woff2
Subsetting Roboto-Regular.ttf to Roboto-Regular-subset.woff2 (was 298.45 KB, now 10.63 KB)

It’s Dangerous to Go Stallone. Take Glyphhanger
@font-face {
    font-family: Roboto;
    src: url(Roboto-Regular-subset.woff2) format("woff2");
    unicode-range: U+20-7E;
}

🍰 Разбить на кусочки

1 символ ≈ 0.1 КБ *

Lessons learned from 222,557 font file subsets?

⚠️ Лицензия на шрифт

@font-face {
    font-family: MyFont;
    src:
        local("MyFont Bold"), /* лучше не надо */
        local("MyFont-Bold"), /* лучше не надо */
        url("MyFont-Bold.woff2");
    font-weight: bold;
}

А почему у вас
не пиксель-пёрфект?

DnD Tokenizer
try {
    const availableFonts = await window.queryLocalFonts();

    for (const fontData of availableFonts) {
        fontData.fullName;
        fontData.postscriptName;
    }
} catch (err) {
    console.error(err.name, err.message);
    this.loadFallback();
}
DnD Tokenizer

Системные шрифты

Modern Font Stacks
font-family:
            -apple-system,
            BlinkMacSystemFont,
            ... sans-serif;
font-family:
            system-ui,
            sans-serif;
It's time to ditch BlinkMacSystemFont and -apple-system

Google Fonts?

Узбагойся
<link rel="preconnect"
      href="https://fonts.googleapis.com">
<link rel="preconnect"
      href="https://fonts.gstatic.com"
      crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
      rel="stylesheet">

TLS, DNS 🐢
Privacy 🕵️

<!-- [1] -->
<link rel="preconnect"
      href="https://fonts.gstatic.com"
      crossorigin />

<!-- [2] -->
<link rel="preload"
      as="style"
      href="$CSS&display=swap" />
Speed Up Google Fonts
<!-- [3] -->
<link rel="stylesheet"
      href="$CSS&display=swap"
      media="print" onload="this.media='all'" />

<!-- [4] -->
<noscript>
    <link rel="stylesheet"
          href="$CSS&display=swap" />
</noscript>
Speed Up Google Fonts

Cache 👍
Service Worker 👍

Вариативные шрифты 🤔

🧪 Incremental Font Transfer

Explainer

Но!

Часть 4
CSS и шрифты

@font-face {
    font-family: "Trickster";
    src: url("trickster.woff2") format("woff2");
    font-display: swap;
    font-style: italic;
    font-weight: 400;

    ascent-override: 90%;
    descent-override: 85%;
    line-gap-override: 125%;
    size-adjust: 90%;

    font-variation-settings: "xhgt" 0.7;
    font-feature-settings: "swsh" 2;
}

🔀 Фолбеки

body {
    font-family: "Poppins", poppins-fallback, sans-serif;
}

@font-face {
    font-family: poppins-fallback;
    src: local("Arial");
    size-adjust: 60.85099821%;
    ascent-override: 164.3358416%;
    descent-override: 57.51754455%;
    line-gap-override: 16.43358416%;
}
Improved font fallbacks
.adj-times-ex-height {
    font-size-adjust: ex-height 0.85;
}

.adj-times-cap-height {
    font-size-adjust: cap-height 0.73;
}
font-size-adjust
Fallback Font Generator

text-box-trim

text-box-trim

font-feature-settings

font-feature-settings
Wakamai Fondue

Резюме
Чек-лист для разработчика

Чек-лист

Gist с материалами

Берегите природу!

mefody.dev/talks/fonts-loading/
@dark_mefody
t.me/mefody_dev QR-код на голосование

Бонус
Эмодзи 🤩

Emoji under the hood

font-variant-emoji: text;
☎☑☺

font-variant-emoji: emoji;
☎☑☺

Берегите природу!

mefody.dev/talks/fonts-loading/
@dark_mefody
t.me/mefody_dev QR-код на голосование