Files
Sprinter-SDCC/examples/mdview2/docs/encoding-plan.md
T
snark13 a14f19b657 mdview2: поддержка кодировок CP866/CP1251/KOI8-R/UTF-8 (F8)
- Детекция при открытии (BOM + эвристика по первым 4 КБ).
- 8-битные (CP866/CP1251/KOI8-R) — общий индекс/кэш, переключение
  мгновенным ремапом глифов [128-255] на отрисовке по attr (структурные
  глифы — рамка/HR/маркеры — не ремапятся).
- UTF-8 — отдельный набор: декодирование в CP866 (кириллица + ходовые
  символы: стрелки/галка/буллет/тире/кавычки/box), свой индекс/кэш.
- Два набора (docset_t g_doc[2]) со свапом «живых» глобалов; второй
  строится ЛЕНИВО при первом F8-переходе в него (build-on-demand).
- F8: цикл CP866→CP1251→KOI8R→UTF8; метка в меню видна только когда
  переключение возможно; во время сборки 8-бит первичного F8 крутит 8-бит.
- F1-справка: секция Encoding; меню разбито на блоки (F1/F8/F10).
- Фикс: g_doc обязан быть инициализирован (SDCC z80 не обнуляет статики
  надёжно) — иначе мусорный built вёл к показу неинициализированного набора.
- Убрана отладка времени обработки из статус-бара.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 11:00:49 +03:00

145 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# mdview2 — поддержка кодировок (CP866 / CP1251 / KOI8-R / UTF-8)
Статус: РЕАЛИЗОВАНО (2026-06-25). Все фазы сделаны; Фаза 4 — eager-кооперативным
вариантом (см. ниже), а не idle-build. Осталась только проверка на железе и
возможная оптимизация «не строить заведомо бесполезный вторичный набор».
## Цель
- CP1251, KOI8-R — простой 1:1 маппинг байтов [128-255] в CP866 (кириллица
+ основная пунктуация). KOI8-R равнозначна, входит в общий цикл.
- UTF-8 — декодирование с маппингом кириллицы в CP866 + подстановка части
некириллических символов (стрелки, галочки, тире, буллеты, box-drawing) в
CP866/ASCII-глифы.
- Автоопределение кодировки при открытии (BOM + дешёвая эвристика).
- F8 (Codepage) — переключение по циклу CP866 → CP1251 → KOI8-R → UTF8.
## Ключевые наблюдения (определяют архитектуру)
1. **8-битные кодировки имеют ОДНУ структуру.** markdown-разметка вся в ASCII
(`# * | -` …); 8-битные различаются только глифами [128-255]. Значит индекс
и кэш (смещения, переносы, ширины таблиц) для CP866/CP1251/KOI8-R —
**общие**; переключение между ними = ремап глифов [128-255], БЕЗ
переиндексации.
2. **UTF-8 — другая структура** (кириллица 2 байта). Нужен отдельный
конвертированный CP866-буфер со своим индексом/кэшем.
3. **Проблема рамок таблиц / маркеров.** В кэше намешаны контентные глифы
(байты-из-источника [128-255] — НАДО ремапить) и вставленные нами CP866-
глифы (box `│─┼…`, маркер списка 0x07, цитата 0xB3, HR 0xC4 — ремапить
НЕЛЬЗЯ). По значению байта не различить → различаем по **атрибуту**.
## Архитектура
### Буферы
- `src_phys[]` — оригинальные байты файла (грузим как сейчас; храним всегда —
под ремап и raw-view, см. memory/mdview2_file_phys_preserve).
- 8-битный режим: `fb()` читает `src_phys` напрямую. Кэш контентных ячеек
хранит **исходный байт** (не пред-конвертированный).
- UTF-8 режим: отдельные EMM-страницы `utf_phys[]` = UTF8→CP866 конвертация
src; свой индекс/кэш; `fb()` читает `utf_phys`.
### Различение контент/структура по attr
Каждый структурный глиф получает НЕ-контентный attr:
- box-рамка таблиц → новый `ATTR_BOX` (сейчас TBL_ATTR=ATTR_TEXT — поменять);
- HR (0xC4) → ATTR_HR; маркер списка (0x07) → ATTR_LIST_MARKER; цитата
(0xB3) → ATTR_QUOTE_MARKER — уже различимы.
Правило ремапа: ремапить `char>=128` только если attr ∈ контентных
(TEXT/BOLD/ITALIC/UNDER/CODE/STRIKE/TITLE1-4). Структурные — как есть.
### Ремап на этапе отрисовки
`draw_line_from_cache`:
- encoding == CP866 или UTF-8: прямой `win_rest` из кэш-страницы (ремап не
нужен — CP866 identity; UTF-8-кэш уже CP866).
- encoding == CP1251/KOI8-R: читаем кэш-строку в near-буфер, ремапим
контентные байты [128-255] через активную таблицу (структурные пропускаем
по attr), пишем в scratch-страницу, `win_rest` из неё. Только видимые ~30
строк, на скролле — дёшево.
Итого: переключение между 8-битными — мгновенно (меняем активную таблицу +
redraw, draw ремапит). Переиндексация только при переходе в/из UTF-8.
## Детекция кодировки (дешёвый скан байтов, ДО построения)
1. **BOM**: первые 3 байта EF BB BF → UTF8 (и пропустить BOM).
2. **UTF-8 валидность** (если нет BOM): проход, проверка структуры
(лид-байты 0xC2-0xDF/0xE0-0xEF/0xF0-0xF4 + континюэйшны 0x80-0xBF;
одиночный 0x80-0xBF, 0xC0/0xC1, 0xF5+ → нарушение). 0 нарушений И есть
≥1 multibyte → UTF8. Любое нарушение → 8-бит.
3. **8-бит дизамбигуация** (CP866/CP1251/KOI8-R): счёт попаданий в байты
самых ходовых строчных русских букв (о е а и н т с р в л) каждой кодировки;
максимум выигрывает. (Предрасчёт байт-наборов по таблицам.)
4. **Фолбэк**: нет байт ≥0x80 или неоднозначно → CP866 (родная).
## Таблицы (static const, CODE/const-сегмент)
- `cp1251_to_866[256]`, `koi8r_to_866[256]` — байт→байт (ASCII identity;
кириллица по раскладкам; en/em-dash, «ёлочки», … → CP866-аналоги или '?').
- UTF-8: `utf_cyr_to_866[]` для U+0400..U+045F + компактная таблица символов
`utf_sym[]` (codepoint→CP866): U+2192→'>'/стрелка, U+2190→'<', U+2713/14 ✓
→'v'/box, U+2022 •→0x07/0xF9, U+2014/2013 —→'-', U+2026 …→"...",
U+00A0→' ', U+2500.. box→CP866 box; прочее → '?'.
## Поток загрузки
1. Грузим в `src_phys`. Детект-скан → кодировка E.
2. Если E ∈ {UTF8}: строим UTF-8-набор (utf_phys + конвертация + индекс),
показываем UTF-8. Иначе: показываем 8-битный (общий индекс на src,
активная таблица = E).
3. **Ленивый build второго набора в простое.** Главный цикл — НЕ блокирующий
getkey, а kbhit-поллинг: пока нет клавиш и второй набор (UTF-8 при
стартовом 8-бит, либо 8-бит при стартовом UTF-8) не построен — докручиваем
его инкрементально. После — F8 в любую сторону мгновенно.
## F8 — переключение
- Цикл g_encoding: CP866 → CP1251 → KOI8-R → UTF8 → CP866.
- 8-бит↔8-бит: сменить активную таблицу + redraw (без переиндексации).
- в/из UTF-8: переключить активный индекс/кэш на соответствующий набор
(если построен; иначе достроить — но при ленивом build обычно уже готов).
- Статус-бар: имя кодировки; меню (строка 31): «F8 Codepage».
## Фазы реализации (ИТОГ)
1.**Ядро 8-бит**: ATTR_BOX; кэш хранит исходный байт; таблицы CP1251/KOI8R;
ремап в draw (`win_rest_remap`).
2.**UTF-8 набор**: `alloc_and_convert_utf8` + `utf8_convert` (декодер 1/2/3-
байт, 4-байтные/битые → `?`); `utf_cyr_to_866[96]` + символьные подстановки
в `conv_emit_cp` (стрелки 0x18-0x1B, галка 0xFB, буллет 0xF9, тире/кавычки/
box/° и т.п.; «» → `<`/`>`, т.к. в CP866 гильеметов нет).
3.**Детекция** (BOM + эвристика), сэмпл — первые **4 КБ** (быстро); хвост-
обрезка multibyte на границе сэмпла не штрафуется.
4.**Сосуществование 2 наборов** (`docset_t g_doc[2]` + `doc_save/load/switch`,
свап «живых» глобалов). Сборка — **ленивая (build-on-demand)**: при старте
строится только первичный (показываемый) набор; UTF-8 конвертация тоже
ленивая (в `build_doc`, не до первого экрана → старт быстрый). Второй набор
достраивается `switch_encoding()` при первом F8-переходе в него (спиннер,
потом кэш). Eager-вариант отвергнут: удваивал старт и блокировал F8 на время
фоновой сборки.
5.**F8** полный цикл CP866→CP1251→KOI8R→UTF8→CP866 (внутри 8-бит — ремап,
на границе — `doc_switch`/ленивая сборка). Статус: `enc_name` кол.37. Меню
«F8 Codepage» показывается только когда переключение возможно (`g_f8_enabled`):
во время сборки 8-битного первичного F8 разрешён в `load_key` (только цикл
8-бит); во время сборки UTF-8 первичного метка F8 скрыта. F1-справка: секция
Encoding.
NB: меню-строка разбита на 10 блоков по 8 колонок, метки Fn кладутся в блок
(n-1)*8 (F1→0, F8→7, F10→9).
## Память
- 8-бит: src + scratch-страница для ремапа (1 стр.). Доп. индекса нет.
- UTF-8 набор: utf_phys (≤8 стр.) + свой индекс/кэш-контент. Строится лениво.
- Бюджет 215 свободных страниц (sprinter_emm_budget) — с запасом.
## Открытые вопросы (к реализации)
1. Имя кодировки в статусе — где (зона имени файла / отдельный слот).
2. Набор UTF-8-подстановок символов — приоритет (→ ← ✓ ✗ • — … « » box).
3. Глубина таблиц пунктуации CP1251/KOI8 (минимум кириллица+dash или полнее).
4. Объём детект-сэмпла (весь файл или первые N КБ).
## Риски
- Корректность различения контент/структура по attr — нужно, чтобы ВСЕ
вставленные глифы имели не-контентный attr (проверить box/markers).
- Два набора индекс/кэш (8-бит + UTF-8) + переключение активного — учёт
страниц, чтобы не течь и не путать.
- Точность таблиц (особенно UTF-8 символы) — итеративно по факту.