Представьте, что вы рассматриваете текст на этом экране через мощную лупу. При стандартном кегле основного текста \(12\,\text{px}\) (\(9\,\text{pt}\) на классическом мониторе с разрешением \(96\,\text{DPI}\)) вся знакоместо буквы представляет собой крошечную дискретную шахматную доску размером всего \(12\) на \(12\) пикселей — лишь \(144\) микроскопических светящихся квадратика.
Тем не менее внутри этой тесной сетки \(12 \times 12\) операционная система десятки тысяч раз в секунду отрисовывает сложнейшие глифы: от иероглифа кандзи «鬱» из \(29\) черт до двухъярусной латинской строчной антиквы «g» или буквы «m» с тремя равноудаленными вертикальными штрихами (Stems). Каждый вертикальный штрих сохраняет идеально ровную толщину и насыщенность, плотные горизонтальные перекладины не сливаются в черное пятно и не проваливаются между центрами пикселей (Dropout), а диагональные кривые лишены ступенчатых зазубрин (Jaggies).
С точки зрения теории информации и цифровой обработки сигналов (DSP) это повседневное явление — настоящее инженерное чудо, бросающее вызов теореме Котельникова (Найквиста — Шеннона). При наивном геометрическом уменьшении векторного контура на сетку \(12 \times 12\) высокие пространственные частоты выше предела Найквиста породили бы катастрофический алиасинг, превратив текст в нечитаемое серое пятно.
За четкостью мелкого текста стоит полвека достижений информатики: вычислительная геометрия (многочлены Бернштейна и сплайны Безье), алгебраическая топология (индекс обхода ориентированных контуров), виртуальные машины (исполняемый байт-код внутри каждого файла .ttf), психофизика зрения и теория КИХ-фильтров (субпиксельный рендеринг ClearType), радиометрия (альфа-смешивание в линейном световом пространстве) и параллельные алгоритмы GPU-шейдеров (MSDF и прямое вычисление кривых Slug).
1. От растрового плена к независимым от разрешения контурным шрифтам
Ранние компьютерные системы полагались на растровые шрифты (Bitmap Fonts), хранившие каждый глиф как бинарную матрицу \(M \in \{0, 1\}^{H \times W}\). Несмотря на четкость в родном размере, они страдали от фатальной зависимости от разрешения: масштабирование или поворот порождали «лесенки» или размытие, а экранные шрифты \(72\,\text{DPI}\) не годились для лазерных принтеров (\(300\,\text{DPI}\)) и фотонаборных автоматов (\(2400\,\text{DPI}\)).
Прорыв совершили контурные векторные шрифты (Outline Fonts): сначала PostScript Type 1 (1984), созданный Джоном Уорноком и Чаком Гешке в Adobe на базе кубических кривых Безье, затем TrueType (.ttf, 1991) от Apple и Microsoft на базе квадратичных кривых Безье и открытой стековой виртуальной машины хинтинга. В 1996 году обе технологии объединились в стандарте OpenType (таблицы glyf и CFF/CFF2).
flowchart TD
A["Файл шрифта OpenType (.ttf / .otf)<br/>Таблицы SFNT: head, cmap, GSUB/GPOS, glyf или CFF"] --> B["1. Шейпинг текста (HarfBuzz / DirectWrite / CoreText)<br/>Кодовые точки Unicode → ID глифов и позиции"]
B --> C["2. Извлечение контуров и аффинное преобразование<br/>Масштабирование из EM-квадрата (2048 upem) в сетку PPEM"]
C --> D["3. Выполнение Grid-Fitting / Хинтинга<br/>Байт-код виртуальной машины TrueType (fpgm, prep, cvt) или автохинтер FreeType<br/>Привязка штрихов к целочисленным границам пикселей"]
D --> E["4. Линеаризация кривых и топология строк развертки<br/>Подразделение де Кастельжо / Формула Ванга + Правило ненулевого индекса"]
E --> F["5. Аналитическое интегрирование трапеций и субпиксельная фильтрация<br/>3-кратная горизонтальная передискретизация + 5-отводный КИХ-фильтр"]
F --> G["6. Линейное альфа-смешивание с гамма-коррекцией<br/>Перевод sRGB в линейную яркость → Кадровый буфер"]
2. Вычислительная геометрия контуров: базис Бернштейна и кривые Безье
Четыре фундаментальных свойства многочленов Бернштейна
Явные многочлены в степенном базисе \(y = a_3 x^3 + a_2 x^2 + a_1 x + a_0\) не способны описать вертикальные касательные и численно неустойчивы при поворотах. Инженеры Поль де Кастельжо (Citroën, 1959) и Пьер Безье (Renault, 1962) определили параметрические кривые через базисные многочлены Бернштейна степени \(n\) и \(n+1\) опорных точек \(\mathbf{P}_0, \dots, \mathbf{P}_n \in \mathbb{R}^2\):
\[\mathbf{B}_n(t) = \sum_{i=0}^{n} B_{i,n}(t)\,\mathbf{P}_i, \qquad B_{i,n}(t) = \binom{n}{i} (1 - t)^{n - i} t^i, \qquad t \in [0, 1]\]- Неотрицательность, разбиение единицы и свойство выпуклой оболочки: На отрезке \(t \in [0, 1]\) всегда \(B_{i,n}(t) \ge 0\), а по биному Ньютона сумма всех базисных функций тождественно равна \(1\): \(\sum_{i=0}^{n} B_{i,n}(t) = \bigl((1 - t) + t\bigr)^n = 1\) Являясь выпуклой комбинацией опорных точек, кривая \(\mathbf{B}_n(t)\) целиком лежит внутри их выпуклой оболочки (Convex Hull), что позволяет за \(O(1)\) отсекать кривые по ограничивающему прямоугольнику (Bounding Box).
- Аффинная инвариантность: Для любого аффинного преобразования \(\Phi(\mathbf{x}) = A\mathbf{x} + \mathbf{b}\) справедливо \(\Phi(\mathbf{B}_n(t)) = \sum B_{i,n}(t)\,\Phi(\mathbf{P}_i)\): достаточно преобразовать только опорные точки.
- Интерполяция концов и управление касательными: \(\mathbf{B}_n(0) = \mathbf{P}_0\), \(\mathbf{B}_n(1) = \mathbf{P}_n\), а векторы производных на концах равны \(\mathbf{B}_n'(0) = n(\mathbf{P}_1 - \mathbf{P}_0)\) и \(\mathbf{B}_n'(1) = n(\mathbf{P}_n - \mathbf{P}_{n-1})\).
- Свойство уменьшения вариации: Никакая прямая не пересекает кривую Безье большее число раз, чем её контрольную ломаную.
Квадратичные кривые (\(n=2\), TrueType) против кубических (\(n=3\), OpenType CFF)
- Квадратичные кривые Безье (\(n=2\), таблица
glyfTrueType): \(\mathbf{B}_2(t) = (1-t)^2 \mathbf{P}_0 + 2(1-t)t \mathbf{P}_1 + t^2 \mathbf{P}_2\) Пересечение со строкой развертки находится напрямую через квадратное уравнение. Кроме того, TrueType использует сжатие через неявные срединные точки на кривой (Implicit On-Curve Midpoints): если в потоке данных подряд идут две внеконтурные точки \(\mathbf{P}_1, \mathbf{P}_2\), декодер автоматически восстанавливает узловую точку на кривой ровно посередине \(\mathbf{M} = \frac{1}{2}(\mathbf{P}_1 + \mathbf{P}_2)\), гарантируя гладкую стыковку \(C^1\) и экономя около \(30\%\) объема файла. - Кубические кривые Безье (\(n=3\), PostScript / таблица
CFFOpenType): \(\mathbf{B}_3(t) = (1-t)^3 \mathbf{P}_0 + 3(1-t)^2 t \mathbf{P}_1 + 3(1-t)t^2 \mathbf{P}_2 + t^3 \mathbf{P}_3\) Поскольку вторая производная квадратичной кривой постоянна (\(\mathbf{B}_2''(t) = \text{const}\)), один квадратичный сегмент не может содержать точку перегиба (например, изгиб буквы «S»). У кубической кривой вторая производная линейна по \(t\), что позволяет описывать S-образные перегибы одним сегментом и независимо регулировать кривизну \(G^2\) на концах.
| Характеристика | Квадратичная Безье (\(n=2\), TrueType glyf) | Кубическая Безье (\(n=3\), OpenType CFF) |
|---|---|---|
| Уравнение \(\mathbf{B}(t)\) | \((1-t)^2\mathbf{P}_0 + 2(1-t)t\mathbf{P}_1 + t^2\mathbf{P}_2\) | \((1-t)^3\mathbf{P}_0 + 3(1-t)^2t\mathbf{P}_1 + 3(1-t)t^2\mathbf{P}_2 + t^3\mathbf{P}_3\) |
| Точки перегиба на сегмент | \(0\) (дуга параболы с постоянным знаком кривизны) | До \(2\) (описывает «S» одним сегментом) |
| Степени свободы (при фикс. касательных) | \(0\) (\(\mathbf{P}_1\) жестко задана пересечением касательных) | \(2\) (длины рычагов управляют кривизной \(G^2\)) |
| Бинарное сжатие | Неявные середины \(\mathbf{M} = \frac{1}{2}(\mathbf{P}_1 + \mathbf{P}_2)\) | Стековые подпрограммы CFF CharString и дельты |
С помощью алгоритма де Кастельжо \(\mathbf{P}_i^{(r)}(t) = (1-t)\mathbf{P}_i^{(r-1)}(t) + t\mathbf{P}_{i+1}^{(r-1)}(t)\) (сводящегося при \(t = \frac{1}{2}\) к сложению и битовому сдвигу >> 1) и формулы Ванга (1985)
кривая без рекурсии разбивается на \(N\) отрезков с точностью \(\tau\).
3. Сканирующая растеризация: индекс обхода и аналитическое интегрирование трапеций
Четно-нечетное правило против правила ненулевого индекса обхода
Чтобы отличить тело глифа от внутрибуквенных просветов (дырок в «O» или «8»), четно-нечетное правило (Even-Odd) дает сбой при самопересечении или наложении штрихов (например, в вариативных шрифтах OpenType), оставляя белые дыры на перекрестьях. Поэтому растеризаторы используют топологическое правило ненулевого индекса обхода (Non-Zero Winding Rule):
Внешние контуры (по часовой стрелке CW в TrueType, \(-1\)) и внутренние отверстия (против часовой стрелки CCW, \(+1\)) взаимно уничтожаются (\(w(P) = 0\)) внутри отверстий, тогда как перекрывающиеся однонаправленные штрихи дают \(w(P) = -2 \ne 0\) и остаются полностью закрашенными.
Аналитическое интегрирование площади трапеций (Libart / FreeType ftgrays / stb_truetype)
Вместо медленного суперсэмплинга алгоритм Рафа Левина (применяемый в FreeType ftgrays.c, stb_truetype и font-rs) вычисляет для каждого отрезка внутри пикселя \((i, j)\) (со знаковым приращением \(\Delta y = y_1 - y_0\) и средней абсциссой \(\bar{x} \in [0, 1]\)) точную площадь трапеции справа от него \(\text{delta\_area}[i] \mathrel{+}= (1 - \bar{x})\Delta y\) и приращение полного покрытия \(\text{delta\_cover}[i+1] \mathrel{+}= \Delta y\) для всех пикселей правее. Один линейный проход префиксной суммы (Prefix Sum) слева направо дает точное покрытие \(\alpha_i \in [0, 1]\) с 256 градациями:
4. Секретная виртуальная машина внутри шрифта: хинтинг и три философии рендеринга
Без хинтинга при \(12\,\text{px}\) три одинаковых вертикальных штриха буквы «m» попадают в разную фазу пиксельной сетки: первый ложится в один пиксель (\(\alpha = 0.80\), четкая черная линия), а второй пересекает границу пикселей и размазывается в мутную двухпиксельную серую полосу (\(\alpha = 0.60\) и \(\alpha = 0.20\)).
Для борьбы с этим каждый файл TrueType содержит байт-код (cvt , fpgm, prep, glyf), исполняемый стековой виртуальной машиной в формате с фиксированной запятой \(26.6\). С помощью вектора проекции (\(\mathbf{P}\)) и вектора свободы (\(\mathbf{F}\)) инструкции MDAP и MIRP привязывают опорные точки к границам пикселей, выравнивают ширину штрихов по глобальной таблице cvt, защищают тонкие линии от исчезновения (Minimum Distance), вносят точечные поправки через DELTAP1 и плавно интерполируют остальные точки кривой командами IUP[0] / IUP[1].
- Философия Microsoft (GDI / ClearType): Максимальная резкость и контраст за счет жесткой привязки штрихов к пиксельной сетке даже ценой искажения пропорций гарнитуры.
- Философия Apple (Quartz / CoreText): Абсолютная верность замыслу шрифтового дизайнера (WYSIWYG) и игнорирование байт-кода хинтинга, раскрывшаяся во всей красе на экранах Retina.
- Автохинтер FreeType (
autofit) и вертикальный хинтинг (Y-only): Автоматический геометрический анализ штрихов и синих зон (Blue Zones) во время выполнения с легкой привязкой только по вертикальной оси Y.
5. Субпиксельный рендеринг (ClearType) и линейное смешивание с гамма-коррекцией
Каждый пиксель ЖК-панели состоит из трех вертикальных субпикселей (\(R, G, B\)) с шагом \(\frac{1}{3}\) пикселя по горизонтали. Поскольку человеческое зрение обладает высокой разрешающей способностью по яркости (\(Y = 0.2126R + 0.7152G + 0.0722B\)) и низкой по цветности, растеризатор выполняет горизонтальную выборку с \(3\)-кратным разрешением. Для подавления цветных ореолов (Color Fringing) применяется 5-отводный симметричный КИХ-фильтр Гибсона (\(\frac{1}{3}[1,1,1] * \frac{1}{3}[1,1,1]\)):
\[h_{\text{Gibson}} = \left[\frac{1}{9},\; \frac{2}{9},\; \frac{3}{9},\; \frac{2}{9},\; \frac{1}{9}\right]\]Сумма коэффициентов с шагом 3 равна \(\frac{3}{9} = \frac{2}{9} + \frac{1}{9} = \frac{1}{3}\), что гарантирует идеальный цветовой баланс. Наконец, альфа-смешивание обязано выполняться в линейном пространстве света (\(\gamma \approx 2.2\)), чтобы избежать искажения толщины шрифта между светлой и темной темами:
\[C_{\text{out}, c} = \Bigl( \alpha_c \, C_{\text{fg}, c}^{\gamma} + (1 - \alpha_c) \, C_{\text{bg}, c}^{\gamma} \Bigr)^{1/\gamma}, \qquad c \in \{R, G, B\},\; \gamma \approx 2.2\]6. Эпоха GPU: SDF, MSDF (msdfgen) и прямые шейдеры Безье (Slug)
flowchart TD
subgraph CPU["Растеризация на CPU (FreeType / DirectWrite)"]
C1["Кривые Безье + ВМ хинтинга"] --> C2["Интегрирование трапеций + 5-отводный КИХ-фильтр"]
C2 --> C3["Кэш растровых битмапов по размерам<br/>Плюс: Идеальная четкость при 10–14px<br/>Минус: Размытие или пересчет при 3D-масштабировании"]
end
subgraph MSDF["Поля расстояний на GPU (Valve SDF / Chlumský MSDF)"]
S1["Офлайн-запекание знакового поля расстояний"] --> S2["MSDF: RGB-кодирование ребер для сохранения острых углов"]
S2 --> S3["Фрагментный шейдер: d = median(r, g, b) + smoothstep<br/>Плюс: Острые углы при любом увеличении из малой текстуры"]
end
subgraph Slug["Прямые GPU-шейдеры Безье (Eric Lengyel 'Slug' / 'Vello')"]
G1["Опорные точки Безье напрямую в буфере GPU"] --> G2["Пространственное разбиение на полосы (Bands)"]
G2 --> G3["Аналитическое решение квадратных уравнений и индекса обхода в шейдере<br/>Плюс: Без текстурных атласов, мгновенная отрисовка 50 000 кандзи"]
end
Для 3D-движков Крис Грин (Valve, 2007) предложил знаковые поля расстояний (SDF), хранящие евклидово расстояние \(d(\mathbf{p})\) до контура. Чтобы острые углы не скруглялись при увеличении, Виктор Хлумский (2015) разработал MSDF (msdfgen), кодирующий смежные ребра угла в каналах RGB и восстанавливающий угол в шейдере через медиану:
А для предельно сложных иероглифов алгоритмы прямой оценки на GPU — Slug Эрика Ленгиела (2017) и Vello Рафа Левина — вычисляют пересечения с кривыми Безье и индекс обхода непосредственно во фрагментном или вычислительном шейдере без промежуточных текстур.