Compare commits
2 Commits
13ef8d2fa5
...
a14f19b657
| Author | SHA1 | Date | |
|---|---|---|---|
| a14f19b657 | |||
| a6e0aacc80 |
@@ -35,8 +35,10 @@ $(DISK_TMP):
|
|||||||
$(README_DISK): README.MD | $(DISK_TMP)
|
$(README_DISK): README.MD | $(DISK_TMP)
|
||||||
iconv -c -f UTF-8 -t CP866 README.MD > $@ || true
|
iconv -c -f UTF-8 -t CP866 README.MD > $@ || true
|
||||||
|
|
||||||
|
# UTF8TEST.MD кладётся на диск КАК ЕСТЬ (в UTF-8, без перекодировки) —
|
||||||
|
# это тестовый вход для проверки UTF-8 рендеринга (Фаза 2 кодировок).
|
||||||
floppy: $(EXAMPLE).exe $(README_DISK)
|
floppy: $(EXAMPLE).exe $(README_DISK)
|
||||||
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXAMPLE).exe $(README_DISK)
|
python3 $(MAKE_DISK) $(FLOPPY_IMG) $(EXAMPLE).exe $(README_DISK) UTF8TEST.MD
|
||||||
@echo
|
@echo
|
||||||
@echo "Floppy ready: $(FLOPPY_IMG)"
|
@echo "Floppy ready: $(FLOPPY_IMG)"
|
||||||
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
@echo "Run: cd $(MAME_DIR) && ./run_mame.sh"
|
||||||
|
|||||||
+11
-10
@@ -33,9 +33,16 @@ Home Начало документа
|
|||||||
End Конец документа
|
End Конец документа
|
||||||
Left Right Горизонтальный сдвиг (только nowrap-строки)
|
Left Right Горизонтальный сдвиг (только nowrap-строки)
|
||||||
F1 Окно справки
|
F1 Окно справки
|
||||||
|
F8 Кодировка: CP866 → CP1251 → KOI8-R → UTF-8 → ...
|
||||||
F10 / Esc Выход из программы
|
F10 / Esc Выход из программы
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Кодировка определяется автоматически при открытии (BOM + эвристика по
|
||||||
|
первым 4 КБ); `F8` переключает её вручную, если детекция ошиблась.
|
||||||
|
8-битные кодировки (CP866/CP1251/KOI8-R) переключаются мгновенно (ремап на
|
||||||
|
отрисовке). Второй набор индекс/кэша (например UTF-8) строится лениво — при
|
||||||
|
первом переключении в него (короткая пауза со спиннером), дальше мгновенно.
|
||||||
|
|
||||||
## Синтаксис Markdown
|
## Синтаксис Markdown
|
||||||
|
|
||||||
### Заголовки
|
### Заголовки
|
||||||
@@ -102,7 +109,7 @@ int main(void) {
|
|||||||
## Технические характеристики
|
## Технические характеристики
|
||||||
|
|
||||||
- **Платформа:** Sprinter, процессор Z80 @ 21 МГц
|
- **Платформа:** Sprinter, процессор Z80 @ 21 МГц
|
||||||
- **Кодировка:** CP866 (DOS Cyrillic)
|
- **Кодировки:** CP866 / CP1251 / KOI8-R / UTF-8 (автоопределение, `F8`)
|
||||||
- **Максимальный размер файла:** 128 КБ
|
- **Максимальный размер файла:** 128 КБ
|
||||||
- **Максимальное число строк в индексе:** 16 384
|
- **Максимальное число строк в индексе:** 16 384
|
||||||
- **Режим памяти:** small
|
- **Режим памяти:** small
|
||||||
@@ -115,15 +122,9 @@ int main(void) {
|
|||||||
разрешить работать с большим кол-вом страниц памяти, пока оттестированно
|
разрешить работать с большим кол-вом страниц памяти, пока оттестированно
|
||||||
на работе с 8-мю страницами по 16Кб.
|
на работе с 8-мю страницами по 16Кб.
|
||||||
|
|
||||||
2. **Форматированные таблицы.** Разбирать строки вида `| ячейка | ячейка |`
|
Сделано: форматированные таблицы с рамкой; поддержка кодировок
|
||||||
с автоматическим выравниванием столбцов и отрисовкой разделительных
|
CP866 / CP1251 / KOI8-R / UTF-8 с автоопределением и переключением по `F8`
|
||||||
линий (строки `|---|---|`). На текущий момент таблицы отображаются
|
(второй набор строится лениво, по первому переключению).
|
||||||
как обычные nowrap-строки без выравнивания.
|
|
||||||
|
|
||||||
3. **Поддержка кодировок CP1251 и UTF-8.** Автоопределение кодировки
|
|
||||||
по BOM, явное указание через аргумент командной строки (`--encoding cp1251`),
|
|
||||||
возможность переключения кодировки во время просмотра (`F8`).
|
|
||||||
Нужно прежде всего для документов на русском языке; CP866 кодировака уже поддерживается.
|
|
||||||
|
|
||||||
4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода
|
4. **Ускорение рендеринга.** Кэш строк экрана. Оптимизация цикла вывода
|
||||||
символов через BIOS WRCHAR (пакетный вывод, DMA).
|
символов через BIOS WRCHAR (пакетный вывод, DMA).
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Тест UTF-8
|
||||||
|
|
||||||
|
Это **проверка** кодировки _UTF-8_ в `mdview2`.
|
||||||
|
Кириллица должна читаться: съешь же ещё этих мягких булок.
|
||||||
|
|
||||||
|
## Символы и пунктуация
|
||||||
|
|
||||||
|
Тире — длинное, и – короткое. Кавычки: «ёлочки» и “лапки”.
|
||||||
|
Многоточие… стрелки → ← ↑ ↓, галочка ✓ и крестик ✗.
|
||||||
|
Градус 25°, пункт списка • буллет, номер № 7.
|
||||||
|
|
||||||
|
## Список
|
||||||
|
|
||||||
|
- Первый пункт
|
||||||
|
- Второй пункт с длинным текстом, чтобы проверить перенос строки по словам на границе экрана восемьдесят символов
|
||||||
|
- Ёжик, ёлка, объём
|
||||||
|
|
||||||
|
> Цитата: «Краткость — сестра таланта».
|
||||||
|
|
||||||
|
## Таблица
|
||||||
|
|
||||||
|
| Язык | Привет | Число |
|
||||||
|
|----------|---------------|-------|
|
||||||
|
| Русский | Здравствуйте | 42 |
|
||||||
|
| English | Hello | 7 |
|
||||||
|
| Deutsch | Grüße | 100 |
|
||||||
|
|
||||||
|
Конец файла.
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
# 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 символы) — итеративно по факту.
|
||||||
+569
-47
@@ -30,7 +30,6 @@
|
|||||||
#include <sprinter.h>
|
#include <sprinter.h>
|
||||||
#include <sprinter_mem.h>
|
#include <sprinter_mem.h>
|
||||||
#include <palette.h>
|
#include <palette.h>
|
||||||
#include <time.h> /* DEBUG: время обработки в статус-баре прогресса */
|
|
||||||
|
|
||||||
/* ---- Геометрия экрана -------------------------------------------- */
|
/* ---- Геометрия экрана -------------------------------------------- */
|
||||||
|
|
||||||
@@ -85,7 +84,8 @@ static const uint8_t md_pallete[16 * 4] = {
|
|||||||
#define ATTR_TEXT_STRIKE COLOR(COLOR_DARKGRAY, COLOR_BLUE)
|
#define ATTR_TEXT_STRIKE COLOR(COLOR_DARKGRAY, COLOR_BLUE)
|
||||||
#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLUE)
|
#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLUE)
|
||||||
#define ATTR_QUOTE_MARKER COLOR(COLOR_CYAN, COLOR_BLUE)
|
#define ATTR_QUOTE_MARKER COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||||
#define ATTR_HR COLOR(COLOR_LIGHTGRAY, COLOR_BLUE)
|
#define ATTR_HR COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||||
|
#define ATTR_BOX COLOR(COLOR_CYAN, COLOR_BLUE) /* рамка таблиц; отдельный (не контентный) attr — чтобы ремап кодировок не трогал box-глифы */
|
||||||
#define ATTR_TRUNC COLOR(COLOR_YELLOW, COLOR_BLUE)
|
#define ATTR_TRUNC COLOR(COLOR_YELLOW, COLOR_BLUE)
|
||||||
#define ATTR_BAR COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
#define ATTR_BAR COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
||||||
#define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN)
|
#define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN)
|
||||||
@@ -104,7 +104,7 @@ static const uint8_t md_pallete[16 * 4] = {
|
|||||||
#define HELP_X 8u /* левая граница рамки */
|
#define HELP_X 8u /* левая граница рамки */
|
||||||
#define HELP_Y 4u /* верхняя граница рамки */
|
#define HELP_Y 4u /* верхняя граница рамки */
|
||||||
#define HELP_W 64u /* ширина рамки (включая │) */
|
#define HELP_W 64u /* ширина рамки (включая │) */
|
||||||
#define HELP_H 22u /* высота рамки (включая ─) */
|
#define HELP_H 26u /* высота рамки (включая ─) */
|
||||||
|
|
||||||
/* ---- Глобальное состояние ---------------------------------------- */
|
/* ---- Глобальное состояние ---------------------------------------- */
|
||||||
|
|
||||||
@@ -287,11 +287,75 @@ static char filename[64] = {0};
|
|||||||
* index_lines() ещё досканирует файл (кооперативно, без прерываний). */
|
* index_lines() ещё досканирует файл (кооперативно, без прерываний). */
|
||||||
static uint8_t g_loading = 0; /* 1 во время index_lines() */
|
static uint8_t g_loading = 0; /* 1 во время index_lines() */
|
||||||
static uint8_t g_first_drawn = 0; /* первый экран уже нарисован */
|
static uint8_t g_first_drawn = 0; /* первый экран уже нарисован */
|
||||||
|
static uint8_t g_f8_enabled = 0; /* можно ли сейчас переключать кодировку (метка F8 в меню + F8 в load_key) */
|
||||||
|
static uint8_t g_utf_failed = 0; /* UTF-8 набор не удалось построить (нет EMM) — пропускаем UTF-8 в цикле F8 */
|
||||||
static uint8_t g_abort = 0; /* F10/Esc во время загрузки → прервать индексацию */
|
static uint8_t g_abort = 0; /* F10/Esc во время загрузки → прервать индексацию */
|
||||||
static uint16_t g_last_prog = 0; /* n_lines на последнем обновлении статуса */
|
static uint16_t g_last_prog = 0; /* n_lines на последнем обновлении статуса */
|
||||||
static datetime_t g_load_t0; /* DEBUG: старт обработки (для времени в статусе) */
|
|
||||||
static void progress_tick(void); /* определена ниже (нужна в index_lines) */
|
static void progress_tick(void); /* определена ниже (нужна в index_lines) */
|
||||||
|
|
||||||
|
/* Кодировки. 8-битные (CP866/CP1251/KOI8R) — общая структура, различаются
|
||||||
|
* только ремапом глифов [128-255] на этапе отрисовки; UTF-8 — отдельный набор. */
|
||||||
|
#define ENC_CP866 0
|
||||||
|
#define ENC_CP1251 1
|
||||||
|
#define ENC_KOI8R 2
|
||||||
|
#define ENC_UTF8 3
|
||||||
|
static uint8_t g_encoding = ENC_CP866; /* активная кодировка */
|
||||||
|
static const uint8_t *g_remap = 0; /* активная таблица ремапа (CP1251/KOI8), 0 = без ремапа */
|
||||||
|
static uint8_t g_scratch_phys = 0; /* EMM-страница под ремап-буфер строки для win_rest */
|
||||||
|
|
||||||
|
/* UTF-8 набор (Фаза 2): src декодируется в отдельный блок EMM-страниц как
|
||||||
|
* чистый CP866. После конвертации активный буфер файла (file_phys/file_size/
|
||||||
|
* file_pages, который читает fb()) перенаправляется на эти страницы, и весь
|
||||||
|
* существующий конвейер индексации/кэша работает по ним без изменений —
|
||||||
|
* конвертированный поток это уже CP866, ремап на отрисовке не нужен. */
|
||||||
|
static uint8_t utf_blk = 0;
|
||||||
|
static uint8_t utf_pages = 0;
|
||||||
|
static uint8_t utf_phys[MAX_PAGES] = {0};
|
||||||
|
static uint32_t utf_size = 0;
|
||||||
|
static uint8_t utf_avail = 0; /* 1 = UTF-8 набор удалось сконвертировать/построить */
|
||||||
|
|
||||||
|
/* Снимок исходного (8-битного) файлового буфера — набор DOC_8BIT читает его
|
||||||
|
* как есть, набор DOC_UTF8 читает utf_phys. */
|
||||||
|
static uint8_t orig_file_blk = 0;
|
||||||
|
static uint8_t orig_file_pages = 0;
|
||||||
|
static uint32_t orig_file_size = 0;
|
||||||
|
static uint8_t orig_file_phys[MAX_PAGES] = {0};
|
||||||
|
|
||||||
|
/* ---------- Два сосуществующих набора индекс/кэш (Фазы 4-5) ----------
|
||||||
|
* DOC_8BIT — исходные байты (CP866/CP1251/KOI8R, различаются ремапом на
|
||||||
|
* отрисовке); DOC_UTF8 — UTF-8, декодированный в CP866. Оба строятся eager
|
||||||
|
* при старте (кооперативно), поэтому F8 переключает активный набор мгновенно.
|
||||||
|
* fb()/idx/cache-аксессоры работают по «живым» глобалам; doc_switch() свапает
|
||||||
|
* их с сохранённой копией нужного набора. */
|
||||||
|
typedef struct {
|
||||||
|
uint8_t built;
|
||||||
|
uint8_t enc; /* кодировка отображения этого набора */
|
||||||
|
uint8_t file_blk, file_pages;
|
||||||
|
uint32_t file_size;
|
||||||
|
uint8_t file_phys[MAX_PAGES];
|
||||||
|
uint8_t index_blk, index_pages, index_truncated;
|
||||||
|
uint16_t n_lines, max_lines;
|
||||||
|
uint8_t index_phys[MAX_INDEX_PAGES];
|
||||||
|
uint8_t cache_dir_blk, cache_dir_pages;
|
||||||
|
uint8_t cache_dir_phys[MAX_CACHE_DIR_PAGES];
|
||||||
|
uint8_t cache_content_n_pages, cache_cur_page_idx;
|
||||||
|
uint16_t cache_cur_off;
|
||||||
|
uint8_t cache_content_blk[MAX_CACHE_CONTENT_PAGES];
|
||||||
|
uint8_t cache_content_phys[MAX_CACHE_CONTENT_PAGES];
|
||||||
|
uint16_t top_line;
|
||||||
|
uint8_t viewport_x;
|
||||||
|
} docset_t;
|
||||||
|
#define DOC_8BIT 0
|
||||||
|
#define DOC_UTF8 1
|
||||||
|
static docset_t g_doc[2] = {{0}, {0}}; /* ОБЯЗАТЕЛЬНО инициализировать: built=0 для обоих
|
||||||
|
* (SDCC z80 не обнуляет неинициализированные статики
|
||||||
|
* надёжно — см. memory/sdcc_static_storage_gotcha) */
|
||||||
|
static uint8_t g_active_doc = DOC_8BIT; /* какой набор сейчас в живых глобалах */
|
||||||
|
static uint8_t g_disp_doc = DOC_8BIT; /* какой набор показан пользователю */
|
||||||
|
|
||||||
|
static void set_encoding(uint8_t enc); /* forward (нужна в doc_load_live) */
|
||||||
|
static void doc_switch(uint8_t target); /* forward (нужна в progress_tick) */
|
||||||
|
|
||||||
#define HPAN_STEP 8u
|
#define HPAN_STEP 8u
|
||||||
|
|
||||||
/* Спиннер в статус-баре (col=8,row=0).
|
/* Спиннер в статус-баре (col=8,row=0).
|
||||||
@@ -560,7 +624,8 @@ static void set_pallete(void) {
|
|||||||
* Загрузка/выгрузка файла
|
* Загрузка/выгрузка файла
|
||||||
* ================================================================== */
|
* ================================================================== */
|
||||||
|
|
||||||
/* Загружает файл в EMM-страницы и подготавливает EMM-индекс.
|
/* Загружает исходные байты файла в EMM-страницы (без индекса/кэша — те
|
||||||
|
* выделяются отдельно на КАЖДЫЙ набор через alloc_set_storage()).
|
||||||
* Возврат: 0 — успех, отрицательное значение — код ошибки. */
|
* Возврат: 0 — успех, отрицательное значение — код ошибки. */
|
||||||
static int load_file(const char *path)
|
static int load_file(const char *path)
|
||||||
{
|
{
|
||||||
@@ -604,10 +669,16 @@ static int load_file(const char *path)
|
|||||||
remaining -= chunk;
|
remaining -= chunk;
|
||||||
}
|
}
|
||||||
close(fd);
|
close(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Выделяем блок индекса в EMM (8 байт на сегмент, 2048 записей/страница).
|
/* Выделяет в живые глобалы блок индекса + директорию рендер-кэша под текущий
|
||||||
* Размер берем с запасом относительно файла; если памяти мало —
|
* набор. file_pages должно быть уже выставлено (размер набора). Сбрасывает
|
||||||
* уменьшаем количество страниц индекса до доступного значения. */
|
* пул контента в пустое состояние (страницы доращиваются лениво при рендере).
|
||||||
|
* Возврат: 0 — успех, -5/-6 — нехватка EMM. */
|
||||||
|
static int alloc_set_storage(void)
|
||||||
|
{
|
||||||
|
/* Блок индекса (8 байт на сегмент, 2048 записей/страница). */
|
||||||
{
|
{
|
||||||
uint8_t want = (uint8_t)(file_pages + 1u);
|
uint8_t want = (uint8_t)(file_pages + 1u);
|
||||||
if (want > MAX_INDEX_PAGES) want = MAX_INDEX_PAGES;
|
if (want > MAX_INDEX_PAGES) want = MAX_INDEX_PAGES;
|
||||||
@@ -624,8 +695,7 @@ static int load_file(const char *path)
|
|||||||
max_lines = (uint16_t)index_pages * INDEX_RECS_PER_PAGE;
|
max_lines = (uint16_t)index_pages * INDEX_RECS_PER_PAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Директория рендер-кэша (Фаза 1) — та же ёмкость, что у индекса:
|
/* Директория рендер-кэша — та же ёмкость, что у индекса. */
|
||||||
* по одной cache_rec_t на каждую логическую строку. */
|
|
||||||
{
|
{
|
||||||
uint8_t want = (uint8_t)(file_pages + 1u);
|
uint8_t want = (uint8_t)(file_pages + 1u);
|
||||||
if (want > MAX_CACHE_DIR_PAGES) want = MAX_CACHE_DIR_PAGES;
|
if (want > MAX_CACHE_DIR_PAGES) want = MAX_CACHE_DIR_PAGES;
|
||||||
@@ -640,20 +710,32 @@ static int load_file(const char *path)
|
|||||||
for (uint8_t i = 0; i < cache_dir_pages; i++)
|
for (uint8_t i = 0; i < cache_dir_pages; i++)
|
||||||
cache_dir_phys[i] = mem_get_page(cache_dir_blk, i);
|
cache_dir_phys[i] = mem_get_page(cache_dir_blk, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Свежий пул контента под этот набор. */
|
||||||
|
cache_content_n_pages = 0;
|
||||||
|
cache_cur_page_idx = 0;
|
||||||
|
cache_cur_off = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Освобождает выделенные EMM-блоки файла и индекса. */
|
static void doc_save_live(uint8_t i); /* forward */
|
||||||
|
|
||||||
|
/* Освобождает все EMM-блоки обоих наборов (индекс/директория/пул контента)
|
||||||
|
* плюс два файловых буфера (исходный 8-битный и UTF-8). */
|
||||||
static void unload_file(void)
|
static void unload_file(void)
|
||||||
{
|
{
|
||||||
if (file_blk) {
|
doc_save_live(g_active_doc); /* зафиксировать живой набор в g_doc */
|
||||||
mem_free_block(file_blk);
|
for (uint8_t s = 0; s < 2; s++) {
|
||||||
|
docset_t *d = &g_doc[s];
|
||||||
|
if (d->index_blk) { mem_free_block(d->index_blk); d->index_blk = 0; }
|
||||||
|
if (d->cache_dir_blk) { mem_free_block(d->cache_dir_blk); d->cache_dir_blk = 0; }
|
||||||
|
for (uint8_t i = 0; i < d->cache_content_n_pages; i++)
|
||||||
|
if (d->cache_content_blk[i]) mem_free_block(d->cache_content_blk[i]);
|
||||||
|
d->cache_content_n_pages = 0;
|
||||||
|
}
|
||||||
|
if (orig_file_blk) { mem_free_block(orig_file_blk); orig_file_blk = 0; }
|
||||||
|
if (utf_blk) { mem_free_block(utf_blk); utf_blk = 0; }
|
||||||
file_blk = 0;
|
file_blk = 0;
|
||||||
}
|
|
||||||
if (index_blk) {
|
|
||||||
mem_free_block(index_blk);
|
|
||||||
index_blk = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================================================================
|
/* ==================================================================
|
||||||
@@ -1345,7 +1427,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
|
|||||||
|
|
||||||
/* ====================== Таблицы: выровненная отрисовка ==================== */
|
/* ====================== Таблицы: выровненная отрисовка ==================== */
|
||||||
#define TBL_MAX_COLS 16
|
#define TBL_MAX_COLS 16
|
||||||
#define TBL_ATTR ATTR_TEXT
|
#define TBL_ATTR ATTR_BOX
|
||||||
/* CP866 light box-drawing. */
|
/* CP866 light box-drawing. */
|
||||||
#define TBL_H 0xC4 /* ─ */
|
#define TBL_H 0xC4 /* ─ */
|
||||||
#define TBL_V 0xB3 /* │ */
|
#define TBL_V 0xB3 /* │ */
|
||||||
@@ -1731,6 +1813,319 @@ static uint8_t emph_to_attr(uint8_t emph, uint8_t base_attr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Таблицы ремапа байтов [128-255] в CP866 (ASCII identity). Сгенерированы
|
||||||
|
* Python codecs. Кириллица + ходовая пунктуация; неизвестное → '?'. */
|
||||||
|
static const uint8_t cp1251_to_866[256] = {
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||||
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||||
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
||||||
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
||||||
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
||||||
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
||||||
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
||||||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
||||||
|
0x3F, 0x3F, 0x27, 0x3F, 0x22, 0x2E, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3C, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||||
|
0x3F, 0x27, 0x27, 0x22, 0x22, 0x07, 0x2D, 0x2D, 0x3F, 0x3F, 0x3F, 0x3E, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||||
|
0xFF, 0xF6, 0xF7, 0x3F, 0xFD, 0x3F, 0x3F, 0x3F, 0xF0, 0x63, 0xF2, 0x3C, 0x3F, 0x3F, 0x72, 0xF4,
|
||||||
|
0xF8, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xFA, 0xF1, 0xFC, 0xF3, 0x3E, 0x3F, 0x3F, 0x3F, 0xF5,
|
||||||
|
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||||
|
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||||
|
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
||||||
|
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
||||||
|
};
|
||||||
|
static const uint8_t koi8r_to_866[256] = {
|
||||||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||||
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||||
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
||||||
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
||||||
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
||||||
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
||||||
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
||||||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
||||||
|
0xC4, 0xB3, 0xDA, 0xBF, 0xC0, 0xD9, 0xC3, 0xB4, 0xC2, 0xC1, 0xC5, 0xDF, 0xDC, 0xDB, 0xDD, 0xDE,
|
||||||
|
0xB0, 0xB1, 0xB2, 0x3F, 0xFE, 0xF9, 0xFB, 0x3F, 0x3F, 0x3F, 0xFF, 0x3F, 0xF8, 0x3F, 0xFA, 0x3F,
|
||||||
|
0xCD, 0xBA, 0xD5, 0xF1, 0xD6, 0xC9, 0xB8, 0xB7, 0xBB, 0xD4, 0xD3, 0xC8, 0xBE, 0xBD, 0xBC, 0xC6,
|
||||||
|
0xC7, 0xCC, 0xB5, 0xF0, 0xB6, 0xB9, 0xD1, 0xD2, 0xCB, 0xCF, 0xD0, 0xCA, 0xD8, 0xD7, 0xCE, 0x63,
|
||||||
|
0xEE, 0xA0, 0xA1, 0xE6, 0xA4, 0xA5, 0xE4, 0xA3, 0xE5, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE,
|
||||||
|
0xAF, 0xEF, 0xE0, 0xE1, 0xE2, 0xE3, 0xA6, 0xA2, 0xEC, 0xEB, 0xA7, 0xE8, 0xED, 0xE9, 0xE7, 0xEA,
|
||||||
|
0x9E, 0x80, 0x81, 0x96, 0x84, 0x85, 0x94, 0x83, 0x95, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||||
|
0x8F, 0x9F, 0x90, 0x91, 0x92, 0x93, 0x86, 0x82, 0x9C, 0x9B, 0x87, 0x98, 0x9D, 0x99, 0x97, 0x9A,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Устанавливает активную кодировку и таблицу ремапа (для 8-битных). */
|
||||||
|
static void set_encoding(uint8_t enc)
|
||||||
|
{
|
||||||
|
g_encoding = enc;
|
||||||
|
g_remap = (enc == ENC_CP1251) ? cp1251_to_866 :
|
||||||
|
(enc == ENC_KOI8R) ? koi8r_to_866 : (const uint8_t *)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Сохранение/восстановление/переключение наборов ----------
|
||||||
|
* doc_save_live: живые глобалы → g_doc[i]. doc_load_live: g_doc[i] → живые
|
||||||
|
* глобалы (+ инвалидация маппинга fb() и активной кодировки). */
|
||||||
|
static void doc_save_live(uint8_t i)
|
||||||
|
{
|
||||||
|
docset_t *d = &g_doc[i];
|
||||||
|
d->enc = g_encoding;
|
||||||
|
d->file_blk = file_blk; d->file_pages = file_pages; d->file_size = file_size;
|
||||||
|
memcpy(d->file_phys, file_phys, MAX_PAGES);
|
||||||
|
d->index_blk = index_blk; d->index_pages = index_pages; d->index_truncated = index_truncated;
|
||||||
|
d->n_lines = n_lines; d->max_lines = max_lines;
|
||||||
|
memcpy(d->index_phys, index_phys, MAX_INDEX_PAGES);
|
||||||
|
d->cache_dir_blk = cache_dir_blk; d->cache_dir_pages = cache_dir_pages;
|
||||||
|
memcpy(d->cache_dir_phys, cache_dir_phys, MAX_CACHE_DIR_PAGES);
|
||||||
|
d->cache_content_n_pages = cache_content_n_pages;
|
||||||
|
d->cache_cur_page_idx = cache_cur_page_idx; d->cache_cur_off = cache_cur_off;
|
||||||
|
memcpy(d->cache_content_blk, cache_content_blk, MAX_CACHE_CONTENT_PAGES);
|
||||||
|
memcpy(d->cache_content_phys, cache_content_phys, MAX_CACHE_CONTENT_PAGES);
|
||||||
|
d->top_line = top_line; d->viewport_x = viewport_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void doc_load_live(uint8_t i)
|
||||||
|
{
|
||||||
|
docset_t *d = &g_doc[i];
|
||||||
|
file_blk = d->file_blk; file_pages = d->file_pages; file_size = d->file_size;
|
||||||
|
memcpy(file_phys, d->file_phys, MAX_PAGES);
|
||||||
|
index_blk = d->index_blk; index_pages = d->index_pages; index_truncated = d->index_truncated;
|
||||||
|
n_lines = d->n_lines; max_lines = d->max_lines;
|
||||||
|
memcpy(index_phys, d->index_phys, MAX_INDEX_PAGES);
|
||||||
|
cache_dir_blk = d->cache_dir_blk; cache_dir_pages = d->cache_dir_pages;
|
||||||
|
memcpy(cache_dir_phys, d->cache_dir_phys, MAX_CACHE_DIR_PAGES);
|
||||||
|
cache_content_n_pages = d->cache_content_n_pages;
|
||||||
|
cache_cur_page_idx = d->cache_cur_page_idx; cache_cur_off = d->cache_cur_off;
|
||||||
|
memcpy(cache_content_blk, d->cache_content_blk, MAX_CACHE_CONTENT_PAGES);
|
||||||
|
memcpy(cache_content_phys, d->cache_content_phys, MAX_CACHE_CONTENT_PAGES);
|
||||||
|
top_line = d->top_line; viewport_x = d->viewport_x;
|
||||||
|
cur_page = 0xFF; /* следующий fb() пере-смаппит страницу */
|
||||||
|
set_encoding(d->enc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void doc_switch(uint8_t target)
|
||||||
|
{
|
||||||
|
if (target == g_active_doc) return;
|
||||||
|
doc_save_live(g_active_doc);
|
||||||
|
doc_load_live(target);
|
||||||
|
g_active_doc = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Самые ходовые строчные русские буквы (о е а и н т с р в л) — их байты
|
||||||
|
* различают 8-битные кодировки по частоте. */
|
||||||
|
static uint8_t common_cp866 (uint8_t b){ return b==0xAE||b==0xA5||b==0xA0||b==0xA8||b==0xAD||b==0xE2||b==0xE1||b==0xE0||b==0xA2||b==0xAB; }
|
||||||
|
static uint8_t common_cp1251(uint8_t b){ return b==0xEE||b==0xE5||b==0xE0||b==0xE8||b==0xED||b==0xF2||b==0xF1||b==0xF0||b==0xE2||b==0xEB; }
|
||||||
|
static uint8_t common_koi8r (uint8_t b){ return b==0xCF||b==0xC5||b==0xC1||b==0xC9||b==0xCE||b==0xD4||b==0xD3||b==0xD2||b==0xD7||b==0xCC; }
|
||||||
|
|
||||||
|
/* Автоопределение кодировки дешёвым сканом байтов (до построения индекса).
|
||||||
|
* BOM → UTF8; иначе валидность UTF-8 (структура multibyte); иначе 8-бит по
|
||||||
|
* частоте ходовых букв; фолбэк CP866. */
|
||||||
|
static uint8_t detect_encoding(void)
|
||||||
|
{
|
||||||
|
if (file_size >= 3 && (uint8_t)fb(0) == 0xEF && (uint8_t)fb(1) == 0xBB && (uint8_t)fb(2) == 0xBF)
|
||||||
|
return ENC_UTF8;
|
||||||
|
|
||||||
|
uint32_t n = file_size;
|
||||||
|
if (n > 4096u) n = 4096u; /* сэмпл: первые 4 КБ — детекции хватает */
|
||||||
|
uint8_t utf_ok = 1, has_mb = 0, has_high = 0, cont = 0;
|
||||||
|
uint16_t s866 = 0, s1251 = 0, skoi = 0;
|
||||||
|
|
||||||
|
for (uint32_t p = 0; p < n; p++) {
|
||||||
|
uint8_t b = (uint8_t)fb(p);
|
||||||
|
if (b < 0x80) { if (cont) { utf_ok = 0; cont = 0; } continue; }
|
||||||
|
has_high = 1;
|
||||||
|
if (common_cp866(b)) s866++;
|
||||||
|
if (common_cp1251(b)) s1251++;
|
||||||
|
if (common_koi8r(b)) skoi++;
|
||||||
|
if (cont) {
|
||||||
|
if ((b & 0xC0) == 0x80) cont--;
|
||||||
|
else { utf_ok = 0; cont = 0; }
|
||||||
|
} else if (b >= 0xC2 && b <= 0xDF) { cont = 1; has_mb = 1; }
|
||||||
|
else if (b >= 0xE0 && b <= 0xEF) { cont = 2; has_mb = 1; }
|
||||||
|
else if (b >= 0xF0 && b <= 0xF4) { cont = 3; has_mb = 1; }
|
||||||
|
else utf_ok = 0; /* битый лид/одиночный континюэйшн */
|
||||||
|
}
|
||||||
|
/* Незавершённая multibyte-последовательность на КОНЦЕ — нарушение только
|
||||||
|
* если это настоящий EOF; на границе сэмпла (n<file_size) это просто
|
||||||
|
* обрезка, не считаем за ошибку. */
|
||||||
|
if (cont && n == file_size) utf_ok = 0;
|
||||||
|
|
||||||
|
if (!has_high) return ENC_CP866; /* чистый ASCII */
|
||||||
|
if (utf_ok && has_mb) return ENC_UTF8;
|
||||||
|
if (s1251 >= s866 && s1251 >= skoi) return ENC_CP1251;
|
||||||
|
if (skoi >= s866) return ENC_KOI8R;
|
||||||
|
return ENC_CP866;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *enc_name(uint8_t enc)
|
||||||
|
{
|
||||||
|
return (enc == ENC_CP1251) ? "CP1251" :
|
||||||
|
(enc == ENC_KOI8R) ? "KOI8-R" :
|
||||||
|
(enc == ENC_UTF8) ? "UTF-8 " : "CP866 ";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Контентный глиф (ремапим при смене кодировки) vs структурный (рамка/HR/
|
||||||
|
* маркеры — уже CP866, не трогаем). Различаем по attr. */
|
||||||
|
static uint8_t attr_is_content(uint8_t a)
|
||||||
|
{
|
||||||
|
return (uint8_t)(a != ATTR_BOX && a != ATTR_HR &&
|
||||||
|
a != ATTR_LIST_MARKER && a != ATTR_QUOTE_MARKER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Вывод среза строки кэша с ремапом контентных глифов [128-255] через
|
||||||
|
* g_remap (CP1251/KOI8). Без активной таблицы — прямой win_rest. */
|
||||||
|
static void win_rest_remap(uint8_t row, uint8_t w, uint8_t page, uint16_t off)
|
||||||
|
{
|
||||||
|
if (!g_remap || w == 0) { win_rest(row, 0, 1, w, page, off); return; }
|
||||||
|
uint8_t buf[SCREEN_W * 2];
|
||||||
|
bank_read(page, off, buf, (uint16_t)w * 2u);
|
||||||
|
for (uint8_t i = 0; i < w; i++) {
|
||||||
|
uint8_t ch = buf[(uint16_t)i * 2u];
|
||||||
|
if (ch >= 0x80 && attr_is_content(buf[(uint16_t)i * 2u + 1u]))
|
||||||
|
buf[(uint16_t)i * 2u] = g_remap[ch];
|
||||||
|
}
|
||||||
|
bank_write(g_scratch_phys, 0, buf, (uint16_t)w * 2u);
|
||||||
|
win_rest(row, 0, 1, w, g_scratch_phys, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================= UTF-8 → CP866 (Фаза 2) ======================== */
|
||||||
|
|
||||||
|
/* Кириллица U+0400..U+045F → CP866. Русский набор + Ё/ё и часть украинских
|
||||||
|
* (Є є Ї ї Ў ў), которые есть в CP866; пропуски → '?' (0x3F). */
|
||||||
|
static const uint8_t utf_cyr_to_866[96] = {
|
||||||
|
/* 0400 */ 0x3F, 0xF0, 0x3F, 0x3F, 0xF2, 0x3F, 0x3F, 0xF4, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xF6, 0x3F,
|
||||||
|
/* 0410 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||||
|
/* 0420 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||||
|
/* 0430 */ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
||||||
|
/* 0440 */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
||||||
|
/* 0450 */ 0x3F, 0xF1, 0x3F, 0x3F, 0xF3, 0x3F, 0x3F, 0xF5, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xF7, 0x3F,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Поток вывода конвертера: пишем в utf_phys чанками по странице, буфер в
|
||||||
|
* near-памяти, флаш через один bank_write (не побайтовый swap W3). */
|
||||||
|
static uint8_t conv_buf[256];
|
||||||
|
static uint16_t conv_n = 0; /* байт в буфере, ещё не сброшено */
|
||||||
|
static uint8_t conv_page = 0; /* индекс текущей страницы в utf_phys */
|
||||||
|
static uint16_t conv_off = 0; /* уже сброшенное смещение в этой странице */
|
||||||
|
|
||||||
|
static void conv_flush(void)
|
||||||
|
{
|
||||||
|
if (conv_n) {
|
||||||
|
bank_write(utf_phys[conv_page], conv_off, conv_buf, conv_n);
|
||||||
|
conv_off = (uint16_t)(conv_off + conv_n);
|
||||||
|
conv_n = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Добавляет один CP866-байт в выходной поток. Запись никогда не пересекает
|
||||||
|
* границу страницы (256 делит 16384 нацело). */
|
||||||
|
static void conv_put(uint8_t b)
|
||||||
|
{
|
||||||
|
if ((uint16_t)(conv_off + conv_n) == PAGE_SIZE) { /* страница заполнена */
|
||||||
|
conv_flush();
|
||||||
|
conv_page++;
|
||||||
|
conv_off = 0;
|
||||||
|
}
|
||||||
|
conv_buf[conv_n++] = b;
|
||||||
|
if (conv_n == (uint16_t)sizeof(conv_buf)) conv_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Кодпойнт → CP866-глиф(ы). Кириллица по таблице; ходовые символы (тире,
|
||||||
|
* кавычки, буллет, стрелки, галочки, box-drawing) — подстановкой; BOM
|
||||||
|
* проглатывается; прочее → '?'. */
|
||||||
|
static void conv_emit_cp(uint32_t cp)
|
||||||
|
{
|
||||||
|
if (cp < 0x80) { conv_put((uint8_t)cp); return; }
|
||||||
|
if (cp >= 0x0400 && cp <= 0x045F) { conv_put(utf_cyr_to_866[cp - 0x0400]); return; }
|
||||||
|
switch (cp) {
|
||||||
|
case 0x00A0: conv_put(' '); break; /* nbsp */
|
||||||
|
case 0x00AB: conv_put('<'); break; /* « (в CP866 гильеметов нет — как в cp1251_to_866) */
|
||||||
|
case 0x00BB: conv_put('>'); break; /* » */
|
||||||
|
case 0x00B0: conv_put(0xF8); break; /* ° */
|
||||||
|
case 0x00B7: conv_put(0xFA); break; /* · */
|
||||||
|
case 0x2013: case 0x2014: conv_put('-'); break; /* – — */
|
||||||
|
case 0x2018: case 0x2019: conv_put('\''); break; /* ‘ ’ */
|
||||||
|
case 0x201C: case 0x201D: conv_put('"'); break; /* “ ” */
|
||||||
|
case 0x2022: conv_put(0xF9); break; /* • → ∙ (пункт списка) */
|
||||||
|
case 0x2026: conv_put('.'); conv_put('.'); conv_put('.'); break; /* … */
|
||||||
|
case 0x2190: conv_put(0x1B); break; /* ← */
|
||||||
|
case 0x2192: conv_put(0x1A); break; /* → */
|
||||||
|
case 0x2191: conv_put(0x18); break; /* ↑ */
|
||||||
|
case 0x2193: conv_put(0x19); break; /* ↓ */
|
||||||
|
case 0x2713: case 0x2714: conv_put(0xFB); break; /* ✓ ✔ → галка */
|
||||||
|
case 0x2715: case 0x2717: case 0x2718: conv_put('x'); break; /* ✕ ✗ ✘ */
|
||||||
|
case 0x2500: conv_put(0xC4); break; /* ─ */
|
||||||
|
case 0x2502: conv_put(0xB3); break; /* │ */
|
||||||
|
case 0x250C: conv_put(0xDA); break; /* ┌ */
|
||||||
|
case 0x2510: conv_put(0xBF); break; /* ┐ */
|
||||||
|
case 0x2514: conv_put(0xC0); break; /* └ */
|
||||||
|
case 0x2518: conv_put(0xD9); break; /* ┘ */
|
||||||
|
case 0x251C: conv_put(0xC3); break; /* ├ */
|
||||||
|
case 0x2524: conv_put(0xB4); break; /* ┤ */
|
||||||
|
case 0x252C: conv_put(0xC2); break; /* ┬ */
|
||||||
|
case 0x2534: conv_put(0xC1); break; /* ┴ */
|
||||||
|
case 0x253C: conv_put(0xC5); break; /* ┼ */
|
||||||
|
case 0x2580: conv_put(0xDF); break; /* ▀ */
|
||||||
|
case 0x2584: conv_put(0xDC); break; /* ▄ */
|
||||||
|
case 0x2588: conv_put(0xDB); break; /* █ */
|
||||||
|
case 0x2591: conv_put(0xB0); break; /* ░ */
|
||||||
|
case 0x2592: conv_put(0xB1); break; /* ▒ */
|
||||||
|
case 0x2593: conv_put(0xB2); break; /* ▓ */
|
||||||
|
case 0x25A0: conv_put(0xFE); break; /* ■ */
|
||||||
|
case 0x2116: conv_put(0xFC); break; /* № */
|
||||||
|
case 0xFEFF: break; /* BOM / ZWNBSP — выкинуть */
|
||||||
|
default: conv_put('?'); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Декодирует активный (исходный UTF-8) буфер в utf_phys как CP866.
|
||||||
|
* Битые/обрезанные последовательности → '?'; 4-байтные (эмодзи и т.п.) → '?'. */
|
||||||
|
static void utf8_convert(void)
|
||||||
|
{
|
||||||
|
conv_n = 0; conv_page = 0; conv_off = 0;
|
||||||
|
uint32_t n = file_size;
|
||||||
|
uint32_t p = 0;
|
||||||
|
while (p < n) {
|
||||||
|
if ((uint16_t)((uint16_t)p & 0x0FFFu) == 0) spinner_tick();
|
||||||
|
uint8_t b = (uint8_t)fb(p++);
|
||||||
|
if (b < 0x80) { conv_emit_cp(b); continue; }
|
||||||
|
|
||||||
|
uint32_t cp;
|
||||||
|
uint8_t need;
|
||||||
|
if ((b & 0xE0) == 0xC0) { cp = (uint32_t)(b & 0x1F); need = 1; }
|
||||||
|
else if ((b & 0xF0) == 0xE0) { cp = (uint32_t)(b & 0x0F); need = 2; }
|
||||||
|
else if ((b & 0xF8) == 0xF0) { cp = (uint32_t)(b & 0x07); need = 3; }
|
||||||
|
else { conv_put('?'); continue; } /* одиночный континюэйшн/битый лид */
|
||||||
|
|
||||||
|
uint8_t ok = 1;
|
||||||
|
for (uint8_t k = 0; k < need; k++) {
|
||||||
|
if (p >= n) { ok = 0; break; }
|
||||||
|
uint8_t cb = (uint8_t)fb(p);
|
||||||
|
if ((cb & 0xC0) != 0x80) { ok = 0; break; }
|
||||||
|
cp = (cp << 6) | (uint32_t)(cb & 0x3F);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (!ok || need == 3) { conv_put('?'); continue; } /* битое / 4-байтное */
|
||||||
|
conv_emit_cp(cp);
|
||||||
|
}
|
||||||
|
conv_flush();
|
||||||
|
utf_size = (uint32_t)conv_page * PAGE_SIZE + conv_off;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Выделяет страницы под UTF-8 набор и конвертирует туда исходник (читает
|
||||||
|
* оригинал — вызывать, когда живой буфер = исходные байты). НЕ перенаправляет
|
||||||
|
* активный буфер: это делает build_doc() при сборке набора DOC_UTF8.
|
||||||
|
* Конвертированный поток ≤ исходного (кириллица 2 байта → 1), хватает того же
|
||||||
|
* числа страниц. Возврат: 1 — успех, 0 — нет EMM. */
|
||||||
|
static uint8_t alloc_and_convert_utf8(void)
|
||||||
|
{
|
||||||
|
utf_pages = file_pages;
|
||||||
|
utf_blk = mem_alloc_pages(utf_pages);
|
||||||
|
if (utf_blk == 0) return 0;
|
||||||
|
for (uint8_t i = 0; i < utf_pages; i++)
|
||||||
|
utf_phys[i] = mem_get_page(utf_blk, i);
|
||||||
|
|
||||||
|
utf8_convert(); /* читает оригинал (active) → utf_phys */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Фаза 4-5 — cache-only draw path: рисует одну строку ИЗ КЭША, без
|
/* Фаза 4-5 — cache-only draw path: рисует одну строку ИЗ КЭША, без
|
||||||
* единого обращения к fb()/исходному файлу. Горизонтальный скролл для
|
* единого обращения к fb()/исходному файлу. Горизонтальный скролл для
|
||||||
* nowrap-строк (Фаза 5) — это просто смещение начала среза на vx*2
|
* nowrap-строк (Фаза 5) — это просто смещение начала среза на vx*2
|
||||||
@@ -1757,7 +2152,7 @@ static void draw_line_from_cache(uint16_t line_idx, uint8_t row)
|
|||||||
uint16_t remaining = (uint16_t)(rec.len - vx);
|
uint16_t remaining = (uint16_t)(rec.len - vx);
|
||||||
uint8_t w = (remaining > SCREEN_W) ? SCREEN_W : (uint8_t)remaining;
|
uint8_t w = (remaining > SCREEN_W) ? SCREEN_W : (uint8_t)remaining;
|
||||||
uint16_t off = (uint16_t)(rec.off + (uint16_t)vx * 2u);
|
uint16_t off = (uint16_t)(rec.off + (uint16_t)vx * 2u);
|
||||||
win_rest(row, 0, 1, w, rec.page, off);
|
win_rest_remap(row, w, rec.page, off);
|
||||||
if (remaining > SCREEN_W) {
|
if (remaining > SCREEN_W) {
|
||||||
wrchar(SCREEN_W - 1, row, '>', ATTR_TRUNC);
|
wrchar(SCREEN_W - 1, row, '>', ATTR_TRUNC);
|
||||||
}
|
}
|
||||||
@@ -1823,18 +2218,6 @@ static void render_status_numbers(void)
|
|||||||
gotoxy( 73, 0);
|
gotoxy( 73, 0);
|
||||||
dec8(pct);
|
dec8(pct);
|
||||||
cputs("%");
|
cputs("%");
|
||||||
|
|
||||||
if (g_loading) { /* DEBUG: время обработки (зона имени файла) */
|
|
||||||
datetime_t now;
|
|
||||||
getdatetime(&now);
|
|
||||||
int16_t el = (int16_t)now.minute*60 + now.second
|
|
||||||
- ((int16_t)g_load_t0.minute*60 + g_load_t0.second);
|
|
||||||
if (el < 0) el += 3600;
|
|
||||||
gotoxy(31, 0);
|
|
||||||
cputs("t=");
|
|
||||||
dec16((uint16_t)el);
|
|
||||||
cputs("s "); /* трейлинг-пробелы затирают прошлую ширину */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Полностью перерисовывает статус-бар (заголовок + числа). */
|
/* Полностью перерисовывает статус-бар (заголовок + числа). */
|
||||||
@@ -1845,6 +2228,7 @@ static void render_full_status(void)
|
|||||||
/* Размещение в шапке: "MDVIEW", затем слот спиннера и имя файла. */
|
/* Размещение в шапке: "MDVIEW", затем слот спиннера и имя файла. */
|
||||||
put_str_attr(1, 0, "MDVIEW", ATTR_BAR);
|
put_str_attr(1, 0, "MDVIEW", ATTR_BAR);
|
||||||
put_str_attr(10, 0, filename, ATTR_BAR);
|
put_str_attr(10, 0, filename, ATTR_BAR);
|
||||||
|
put_str_attr(37, 0, enc_name(g_encoding), ATTR_BAR); /* текущая кодировка */
|
||||||
render_status_numbers();
|
render_status_numbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1857,12 +2241,18 @@ static void render_updated_status(void)
|
|||||||
/* Отрисовывает нижнюю строку меню. */
|
/* Отрисовывает нижнюю строку меню. */
|
||||||
static void render_menu(void)
|
static void render_menu(void)
|
||||||
{
|
{
|
||||||
|
/* Строка меню разбита на 10 блоков по 8 колонок: метку Fn кладём в блок
|
||||||
|
* (n-1)*8, чтобы позиции соответствовали номерам клавиш (F1→0, F8→7,
|
||||||
|
* F10→9). Пока заполнены только F1/F8/F10. */
|
||||||
fill_row(MENU_ROW, ATTR_MENU_T);
|
fill_row(MENU_ROW, ATTR_MENU_T);
|
||||||
put_str_attr(1, MENU_ROW, "F1", ATTR_MENU_K);
|
put_str_attr(0 * 8 + 1, MENU_ROW, "F1", ATTR_MENU_K);
|
||||||
put_str_attr(4, MENU_ROW, "Help", ATTR_MENU_T);
|
put_str_attr(0 * 8 + 4, MENU_ROW, "Help", ATTR_MENU_T);
|
||||||
/* F2 зарезервирован: режимов wrap/unwrap больше нет. */
|
if (g_f8_enabled) { /* F8 показываем только когда переключение кодировки возможно */
|
||||||
put_str_attr(SCREEN_W - 10, MENU_ROW, "F10", ATTR_MENU_K);
|
put_str_attr(7 * 8 + 1, MENU_ROW, "F8", ATTR_MENU_K);
|
||||||
put_str_attr(SCREEN_W - 5, MENU_ROW, "Exit", ATTR_MENU_T);
|
put_str_attr(7 * 8 + 4, MENU_ROW, "Codepage", ATTR_MENU_T);
|
||||||
|
}
|
||||||
|
put_str_attr(9 * 8 + 0, MENU_ROW, "F10", ATTR_MENU_K);
|
||||||
|
put_str_attr(9 * 8 + 4, MENU_ROW, "Exit", ATTR_MENU_T);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================================================================
|
/* ==================================================================
|
||||||
@@ -2039,6 +2429,10 @@ static void show_help(void)
|
|||||||
help_line(r++, " - * + item 1. 2. Ordered list", ATTR_HELP_BG);
|
help_line(r++, " - * + item 1. 2. Ordered list", ATTR_HELP_BG);
|
||||||
help_line(r++, " |----|----| Tables", ATTR_HELP_BG);
|
help_line(r++, " |----|----| Tables", ATTR_HELP_BG);
|
||||||
help_line(r++, "", ATTR_HELP_BG);
|
help_line(r++, "", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " Encoding:", ATTR_HELP_HDR);
|
||||||
|
help_line(r++, " F8 Cycle CP866 / CP1251 / KOI8-R / UTF-8", ATTR_HELP_BG);
|
||||||
|
help_line(r++, " Auto-detected on open; F8 to override", ATTR_HELP_BG);
|
||||||
|
help_line(r++, "", ATTR_HELP_BG);
|
||||||
help_line(r++, " File size: up to 128 KB (EMM). Lines: up to 16384.", ATTR_HELP_BG);
|
help_line(r++, " File size: up to 128 KB (EMM). Lines: up to 16384.", ATTR_HELP_BG);
|
||||||
help_line(r++, "", ATTR_HELP_BG);
|
help_line(r++, "", ATTR_HELP_BG);
|
||||||
|
|
||||||
@@ -2073,6 +2467,16 @@ static void load_key(uint16_t k)
|
|||||||
case KEY_LEFT: scroll_h(-(int8_t)HPAN_STEP); render_status_numbers(); return;
|
case KEY_LEFT: scroll_h(-(int8_t)HPAN_STEP); render_status_numbers(); return;
|
||||||
case KEY_RIGHT: scroll_h(+(int8_t)HPAN_STEP); render_status_numbers(); return;
|
case KEY_RIGHT: scroll_h(+(int8_t)HPAN_STEP); render_status_numbers(); return;
|
||||||
case KEY_F1: show_help(); return;
|
case KEY_F1: show_help(); return;
|
||||||
|
case KEY_F8: /* во время сборки крутим ТОЛЬКО 8-бит и только если набор 8-битный
|
||||||
|
* (ремап на отрисовке; индексация не зависит от кодировки) */
|
||||||
|
if (g_f8_enabled && g_active_doc == DOC_8BIT) {
|
||||||
|
uint8_t nx = (g_encoding == ENC_CP866) ? ENC_CP1251 :
|
||||||
|
(g_encoding == ENC_CP1251) ? ENC_KOI8R : ENC_CP866;
|
||||||
|
set_encoding(nx); g_doc[DOC_8BIT].enc = nx;
|
||||||
|
draw_viewport_from_cache();
|
||||||
|
render_full_status();
|
||||||
|
}
|
||||||
|
return;
|
||||||
default: return;
|
default: return;
|
||||||
}
|
}
|
||||||
if (nt > maxtop) nt = maxtop;
|
if (nt > maxtop) nt = maxtop;
|
||||||
@@ -2086,8 +2490,8 @@ static void load_key(uint16_t k)
|
|||||||
/* Кооперативный «тик» прогрессивной загрузки — вызывается из index_lines(). */
|
/* Кооперативный «тик» прогрессивной загрузки — вызывается из index_lines(). */
|
||||||
static void progress_tick(void)
|
static void progress_tick(void)
|
||||||
{
|
{
|
||||||
|
/* Прогрессивная отрисовка строящегося (всегда показываемого) набора. */
|
||||||
uint8_t touched = 0;
|
uint8_t touched = 0;
|
||||||
|
|
||||||
if (!g_first_drawn && drawable_lines() >= VIEW_H) {
|
if (!g_first_drawn && drawable_lines() >= VIEW_H) {
|
||||||
draw_viewport_from_cache();
|
draw_viewport_from_cache();
|
||||||
render_status_numbers();
|
render_status_numbers();
|
||||||
@@ -2113,6 +2517,81 @@ static void progress_tick(void)
|
|||||||
* Точка входа
|
* Точка входа
|
||||||
* ================================================================== */
|
* ================================================================== */
|
||||||
|
|
||||||
|
/* Строит набор `slot` кооперативно (index_lines + progress_tick).
|
||||||
|
* visible=1 — первичный (показываемый): прогрессивная отрисовка первого экрана
|
||||||
|
* и скролл по готовым строкам. visible=0 — вторичный: index_lines строит его в
|
||||||
|
* живых глобалах, а progress_tick через свап показывает/листает уже готовый
|
||||||
|
* g_disp_doc. Возврат: 0 — успех, <0 — нехватка EMM (-5/-6/-7).
|
||||||
|
*
|
||||||
|
* UTF-8 конвертация делается ЛЕНИВО здесь, а не до первого экрана: при входе
|
||||||
|
* (для DOC_UTF8 ещё не сконвертированного) живой буфер обязан быть = оригинал
|
||||||
|
* (8-битные байты) — utf8_convert читает его через fb(). Это так и в случае
|
||||||
|
* первичного UTF-8 (сразу после load_file), и вторичного (после сборки
|
||||||
|
* первичного 8-битного, чей буфер = оригинал). */
|
||||||
|
static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible)
|
||||||
|
{
|
||||||
|
if (slot == DOC_UTF8) {
|
||||||
|
if (!utf_avail) { /* конвертируем сейчас (live = оригинал) */
|
||||||
|
if (!alloc_and_convert_utf8()) return -7;
|
||||||
|
utf_avail = 1;
|
||||||
|
}
|
||||||
|
file_blk = utf_blk; file_pages = utf_pages; file_size = utf_size;
|
||||||
|
memcpy(file_phys, utf_phys, MAX_PAGES);
|
||||||
|
} else {
|
||||||
|
file_blk = orig_file_blk; file_pages = orig_file_pages; file_size = orig_file_size;
|
||||||
|
memcpy(file_phys, orig_file_phys, MAX_PAGES);
|
||||||
|
}
|
||||||
|
cur_page = 0xFF;
|
||||||
|
|
||||||
|
int rc = alloc_set_storage();
|
||||||
|
if (rc < 0) return rc;
|
||||||
|
|
||||||
|
set_encoding(enc);
|
||||||
|
top_line = 0; viewport_x = 0;
|
||||||
|
g_active_doc = slot;
|
||||||
|
if (visible) { g_disp_doc = slot; g_first_drawn = 0; }
|
||||||
|
|
||||||
|
g_loading = 1;
|
||||||
|
g_last_prog = 0;
|
||||||
|
index_lines();
|
||||||
|
g_loading = 0;
|
||||||
|
|
||||||
|
g_doc[slot].built = 1;
|
||||||
|
doc_save_live(slot);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Переключает кодировку на `next` (вызывается из главного цикла по F8).
|
||||||
|
* 8-бит↔8-бит внутри одного набора — мгновенный ремап; переход в другой набор
|
||||||
|
* — мгновенный doc_switch, если он построен, иначе ЛЕНИВАЯ сборка по месту
|
||||||
|
* (со спиннером), результат кэшируется. */
|
||||||
|
static void switch_encoding(uint8_t next)
|
||||||
|
{
|
||||||
|
uint8_t target = (next == ENC_UTF8) ? DOC_UTF8 : DOC_8BIT;
|
||||||
|
|
||||||
|
if (target == g_active_doc) { /* тот же набор — только ремап */
|
||||||
|
set_encoding(next); g_doc[target].enc = next;
|
||||||
|
} else if (g_doc[target].built) { /* набор готов — мгновенный свап */
|
||||||
|
doc_switch(target); g_disp_doc = target;
|
||||||
|
set_encoding(next); g_doc[target].enc = next;
|
||||||
|
} else { /* строим набор лениво */
|
||||||
|
doc_save_live(g_active_doc); /* сохранить текущий (позиция и т.п.) */
|
||||||
|
spinner_show(1);
|
||||||
|
int rc = build_doc(target, next, 1); /* visible: рисует прогрессивно */
|
||||||
|
spinner_show(0);
|
||||||
|
if (rc < 0) { /* нет EMM — откат на текущий набор */
|
||||||
|
if (target == DOC_UTF8) g_utf_failed = 1;
|
||||||
|
doc_load_live(g_active_doc);
|
||||||
|
clamp_top(); draw_viewport_from_cache(); render_full_status();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_disp_doc = target; /* build_doc уже выставил active/enc */
|
||||||
|
}
|
||||||
|
clamp_top();
|
||||||
|
draw_viewport_from_cache();
|
||||||
|
render_full_status();
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
const char *path;
|
const char *path;
|
||||||
@@ -2172,15 +2651,44 @@ int main(int argc, char **argv)
|
|||||||
cputs("\n\r\n\r");
|
cputs("\n\r\n\r");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
/* Прогрессивная загрузка: index_lines() сама рисует первый экран и пускает
|
/* Scratch-страница под ремап-буфер строки (win_rest_remap). */
|
||||||
* скролл по готовым данным через progress_tick(). */
|
g_scratch_phys = mem_get_page(mem_alloc_pages(1), 0);
|
||||||
g_loading = 1;
|
|
||||||
g_first_drawn = 0;
|
/* Оба набора пока не построены (страховка к инициализатору g_doc). */
|
||||||
g_last_prog = 0;
|
g_doc[DOC_8BIT].built = 0;
|
||||||
getdatetime(&g_load_t0); /* DEBUG: старт обработки */
|
g_doc[DOC_UTF8].built = 0;
|
||||||
index_lines();
|
|
||||||
g_loading = 0;
|
/* Снимок исходного 8-битного буфера (его читает набор DOC_8BIT). */
|
||||||
|
orig_file_blk = file_blk; orig_file_pages = file_pages; orig_file_size = file_size;
|
||||||
|
memcpy(orig_file_phys, file_phys, MAX_PAGES);
|
||||||
|
|
||||||
|
/* Детекция кодировки по оригиналу. UTF-8 конвертацию НЕ делаем здесь —
|
||||||
|
* она дорогая (полный проход) и нужна только для UTF-8 набора, который
|
||||||
|
* строится через build_doc лениво. Так первый экран рисуется по индексации
|
||||||
|
* первичного набора без лишнего прохода. */
|
||||||
|
uint8_t det = detect_encoding();
|
||||||
|
|
||||||
|
/* Первичный (показываемый) набор — по детекции; второй строится ЛЕНИВО по
|
||||||
|
* первому F8 (switch_encoding). Во время сборки 8-битного первичного F8
|
||||||
|
* разрешён (ремап 8-бит, см. load_key) → показываем метку F8; для UTF-8
|
||||||
|
* первичного переключаться пока некуда → метку прячем. */
|
||||||
|
uint8_t prim_enc = det;
|
||||||
|
uint8_t prim_doc = (prim_enc == ENC_UTF8) ? DOC_UTF8 : DOC_8BIT;
|
||||||
|
g_f8_enabled = (prim_doc == DOC_8BIT);
|
||||||
|
render_menu();
|
||||||
|
|
||||||
|
rc = build_doc(prim_doc, prim_enc, 1); /* первичный — видимый */
|
||||||
|
if (rc < 0 && prim_doc == DOC_UTF8) { /* UTF-8 не вышел (нет EMM) → 8-бит */
|
||||||
|
prim_doc = DOC_8BIT; prim_enc = ENC_CP866;
|
||||||
|
rc = build_doc(DOC_8BIT, ENC_CP866, 1);
|
||||||
|
}
|
||||||
|
if (rc < 0) {
|
||||||
spinner_show(0);
|
spinner_show(0);
|
||||||
|
clrscr_attr(ATTR_RESET);
|
||||||
|
textattr(ATTR_RESET);
|
||||||
|
cputs("MDView: index/cache alloc failed\n\r\n\r");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
if (g_abort) goto exit_loop; /* F10/Esc во время загрузки — корректный выход */
|
if (g_abort) goto exit_loop; /* F10/Esc во время загрузки — корректный выход */
|
||||||
if (n_lines == 0) {
|
if (n_lines == 0) {
|
||||||
die("mdview: empty file");
|
die("mdview: empty file");
|
||||||
@@ -2188,7 +2696,12 @@ int main(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Финальная отрисовка: все строки готовы, top_line мог уехать при скролле. */
|
/* Первичный готов — гасим спиннер, включаем F8 (второй набор дособерётся
|
||||||
|
* лениво по первому переключению) и рисуем финальный статус с ПОЛНЫМ
|
||||||
|
* счётчиком строк. */
|
||||||
|
spinner_show(0);
|
||||||
|
g_f8_enabled = 1;
|
||||||
|
render_menu();
|
||||||
clamp_top();
|
clamp_top();
|
||||||
render_full_status();
|
render_full_status();
|
||||||
draw_viewport_from_cache();
|
draw_viewport_from_cache();
|
||||||
@@ -2204,6 +2717,15 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
switch (scan) {
|
switch (scan) {
|
||||||
case KEY_F1: show_help(); break;
|
case KEY_F1: show_help(); break;
|
||||||
|
case KEY_F8: { /* цикл CP866→CP1251→KOI8R→UTF8→CP866; нужный набор дособерётся лениво */
|
||||||
|
uint8_t next = (g_encoding == ENC_CP866) ? ENC_CP1251 :
|
||||||
|
(g_encoding == ENC_CP1251) ? ENC_KOI8R :
|
||||||
|
(g_encoding == ENC_KOI8R) ? ENC_UTF8 : ENC_CP866;
|
||||||
|
if (next == ENC_UTF8 && g_utf_failed) next = ENC_CP866; /* UTF-8 недоступен */
|
||||||
|
switch_encoding(next);
|
||||||
|
if (g_abort) goto exit_loop; /* Esc/F10 во время ленивой досборки */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
case KEY_F10: goto exit_loop;
|
case KEY_F10: goto exit_loop;
|
||||||
case KEY_UP: scroll_up(1); break;
|
case KEY_UP: scroll_up(1); break;
|
||||||
case KEY_DOWN: scroll_down(1); break;
|
case KEY_DOWN: scroll_down(1); break;
|
||||||
|
|||||||
Reference in New Issue
Block a user