# Текстовые стили — `$.font`

Подсистема `font.js` — именованные наборы параметров текста: размер, цвет,
выравнивание, межстрочный интервал. Один стиль описывается один раз и
применяется к любому числу узлов — правка в одном месте меняет весь HUD,
подписи меню и реплики диалогов:

```js
$.ready(() => {
    $.font.define('hud',   { size: 20, color: '#ffffff', align: 'left' });
    $.font.define('title', { base: 'hud', size: 40, color: '#ffd166' });

    $('<ui.label>', { id: 'score', text: 'Счёт: 0' }).at(120, 40).size(240, 30);
    $.font.apply('#score', 'hud');
    $('#score').textStyle('title');        // то же самое методом узла
});
```

Модуль — **только надстройка**: он не заменяет `$.gfx.text()`, `.fontSize()` и
`.text()`, а раскладывает готовый набор по уже существующим свойствам узла
(`size`, `color`/`text_color`, `attrs.align`, `attrs.lineHeight`), которые
читает отрисовка.

---

## 1. Поля стиля

| Поле | Тип | По умолчанию | Смысл |
|---|---|---|---|
| `size` | number | 20 | размер шрифта в пикселях |
| `color` | цвет | `'#ffffff'` | цвет текста (см. §2 — у контролов он уезжает в `text_color`) |
| `align` | `'left'`\|`'center'`\|`'right'` | `'left'` | выравнивание (`attrs.align`) |
| `lineHeight` | number | 1.25 | множитель межстрочного интервала (`attrs.lineHeight`) |
| `base` | string | — | имя родительского стиля (§4) |

Любые **другие** поля описания копируются в `attrs` узла как есть: `$.font` о
них ничего не знает, но их может читать другая подсистема
(`$.font.define('glow', { size: 20, letterSpacing: 3 })` → `attrs.letterSpacing`).

## 2. Куда попадает цвет

У контролов свойство `color` — это **фон**, а цвет подписи лежит в
`text_color`. `apply()` выбирает свойство сам, иначе стиль перекрашивал бы фон
кнопки:

| Теги | Свойство цвета |
|---|---|
| `ui.button`, `ui.bar`, `ui.checkbox`, `ui.slider`, `ui.input`, `ui.list`, `ui.dialog` | `text_color` |
| `text`, `ui.label`, `ui.panel`, остальные | `color` |

Чистая функция для проверки правила — `textColorTarget(tag)` → `'text'` | `'color'`.

## 3. Функции `$.font`

| Функция | Назначение |
|---|---|
| `$.font.define(name, spec)` | объявить стиль; `spec.base` — наследование |
| `$.font.get(name)` | разобранный стиль (копия) или `null`, если стиля нет |
| `$.font.has(name)` | есть ли такой стиль |
| `$.font.list()` | имена стилей по алфавиту |
| `$.font.remove(name)` | удалить стиль |
| `$.font.apply(target, name)` | применить стиль к узлу, обёртке `$(…)`, селектору или массиву |
| `$.font.of(target)` | имя стиля, последним применённого к узлу (или `null`) |
| `$.font.styleOf(target)` | что реально читает отрисовка: `{ font, size, align, lineHeight, color }` |
| `$.font.measure(text, name)` | ширина строки в пикселях этим стилем |
| `$.font.defaults()` | значения по умолчанию (копия) |

```js
$.font.apply('#score', 'hud');       // селектор
$.font.apply($('#score'), 'hud');    // обёртка
$.font.apply($('#score').get(0), 'hud');   // сам узел
$.font.apply($('ui.button'), 'hud');       // все кнопки
```

## 4. Метод узла `.textStyle()`

| Вызов | Что делает |
|---|---|
| `.textStyle('hud')` | применить объявленный стиль |
| `.textStyle({ size: 30, color: '#f00' })` | разовый набор без регистрации (имя не запоминается) |
| `.textStyle()` | имя действующего стиля или `null` |

## 5. Наследование

Цепочка: `default` (если объявлен) → `base` → сам стиль. Незаданные поля
наследуются, заданные — перекрывают:

```js
$.font.define('default', { size: 18, color: '#c8d4e8' });   // база для всех
$.font.define('hud', { align: 'left' });                     // size/color из default
$.font.define('title', { base: 'hud', size: 40 });           // + свой размер
$.font.get('title');   // { size: 40, color: '#c8d4e8', align: 'left', lineHeight: 1.25 }
```

Цикл в `base` (`a → b → a`) разбор не вешает: повторное имя просто
пропускается. Ссылка на несуществующую базу — предупреждение в лог один раз,
стиль продолжает работать без неё.

## 6. Ограничения

* кроме `size`/`color`/`align`/`lineHeight` движок ничего у текста не умеет:
  жирный, курсив, свои файлы шрифтов и перенос строк стилем не задать;
* `apply()` пишет в `attrs.align` — у тега `<text>` это то же поле, что и у
  `<ui.label>`, поэтому стиль с `align` действует на оба;
* `align` вне `left/center/right` подменяется на `left` с предупреждением;
* неизвестный стиль: `get()` → `null`, `apply()` пишет в лог и **не меняет**
  узел; `measure()` берёт размер по умолчанию;
* `.textStyle()` — метод обёртки `$`, его имя уникально и не пересекается со
  списком §5 контракта (`fontSize` при этом остаётся отдельным методом);
* реестр стилей живёт в замыкании `installFont()`: повторная установка
  (перезагрузка игры) начинает с чистого списка;
* `$.font.measure()` без движка (qjs) возвращает 0 — тесты проверяют логику,
  а не метрики шрифта.

## 7. Проверка

```bash
build/_deps/quickjs-build/qjs tests/js/font_test.mjs
```

Покрыто: разбор и слияние стилей, наследование (`base`, неявный `default`),
циклы, выбор свойства для цвета, копия из `get()`, неизвестный стиль,
`.textStyle()`, `measure()`, `styleOf()`.
