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

11 KiB
Raw Blame History

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 символы) — итеративно по факту.