diff --git a/examples/mdview2/docs/encoding-plan.md b/examples/mdview2/docs/encoding-plan.md new file mode 100644 index 0000000..1a4d632 --- /dev/null +++ b/examples/mdview2/docs/encoding-plan.md @@ -0,0 +1,125 @@ +# mdview2 — поддержка кодировок (CP866 / CP1251 / KOI8-R / UTF-8) + +Статус: ПЛАН (2026-06-24, ревизия 2). Реализация — отдельными фазами. + +## Цель + +- 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 для CP1251/KOI8; кодировка задаётся константой для теста. +2. **UTF-8 набор**: utf_phys + конвертация + отдельный индекс/кэш; ручной + выбор UTF-8. +3. **Детекция** (BOM + эвристика) при загрузке. +4. **Нон-блокирующий main loop + ленивый idle-build** второго набора. +5. **F8** + статус/меню. + +## Память +- 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 символы) — итеративно по факту.