Подготовить автономную сборку и запуск перед разделением проектов
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Sprinter C Compiler contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
|
||||
Vendored third-party components are under their own licenses:
|
||||
|
||||
third_party/sdcc/ — SDCC 4.5 under GPL v2 with linking exception.
|
||||
See third_party/sdcc/COPYING.txt.
|
||||
third_party/solid-c/ — Original Sprinter Solid C, used only as a reference
|
||||
for compatibility. Licence not specified upstream;
|
||||
treat as reference material, do not redistribute
|
||||
original binaries.
|
||||
mame/v306/ — MAME (https://www.mamedev.org), GPL v2+.
|
||||
mame/v306/IMG/*.img *.chd *.iso — Sprinter ROM / DSS / sample disk images
|
||||
from Peters Plus. Redistribution policy: see
|
||||
Peters Plus documentation.
|
||||
|
||||
Documentation in docs/converted/, docs/reference/, docs/samples/, and
|
||||
docs/memory management/ contains material originally published by Peters Plus
|
||||
team (Иван Мак, Дмитрий Паринов and others). Used here for cross-compilation
|
||||
reference; original copyrights apply.
|
||||
@@ -0,0 +1,8 @@
|
||||
# MDVIEW2 fixture для теста запуска дочернего приложения
|
||||
|
||||
Снимок исходников `examples/mdview2` из Sprinter-SDCC commit `5323b16`
|
||||
(2026-09-15). Лицензия MIT находится в `LICENSE`. Fixture специально
|
||||
зафиксирован: тест Commander должен проверять запуск полноэкранного viewer,
|
||||
PageDown/raw-режим и восстановление окружения после возврата, независимо от
|
||||
последующих изменений проекта Examples. Обновлять снимок только вместе с
|
||||
повторной проверкой сценария `mame_p5_viewer.lua` и эталонных кадров.
|
||||
@@ -0,0 +1,153 @@
|
||||
# MDView v1.0 (b3) — просмотрщик Markdown для Sprinter
|
||||
|
||||
**MDView** — программа просмотра документов Markdown для компьютера
|
||||
**Sprinter-2000** (Z80, ОС ESTEX). Файл загружается в расширенную память (EMM)
|
||||
и один раз «прогоняется» через парсер: готовые к показу строки (пары
|
||||
символ+атрибут) складываются в **рендер-кэш** в EMM, после чего прокрутка в
|
||||
любую сторону — это просто копирование готовых строк на экран, без повторного
|
||||
парсинга. Даже на файлах в сотни килобайт листание остаётся мгновенным.
|
||||
|
||||
Текстовый режим 80×32, цветное оформление элементов разметки, три режима
|
||||
просмотра (MD / RAW / HEX) и четыре кодировки с автоопределением.
|
||||
|
||||
---
|
||||
|
||||
## Возможности
|
||||
|
||||
* **Markdown-рендеринг** с цветовым оформлением:
|
||||
* заголовки `#`…`######` (H5/H6 отображаются как H4);
|
||||
* **жирный** (`**текст**`), *курсив* (`*текст*`), подчёркнутый (`_текст_`),
|
||||
~~зачёркнутый~~ (`~~текст~~`), `встроенный код` (`` `текст` ``);
|
||||
* экранирование `\*`, `\_`, `` \` `` и любой ASCII-пунктуации;
|
||||
* чекбоксы `[x]` / `[ ]` в списках;
|
||||
* ненумерованные (`-`, `*`, `+`) и нумерованные (`1.`, `1)`) списки
|
||||
с базовой вложенностью по отступам;
|
||||
* цитаты `>` (склейка многострочных, маркер │ на переносах);
|
||||
* fenced-блоки кода ` ``` ` (без переносов, горизонтальный скролл);
|
||||
* таблицы `| … | … |` — рисуются псевдографической рамкой, ширины колонок
|
||||
вычисляются по содержимому (до 16 колонок);
|
||||
* горизонтальные разделители `---` / `***` / `___`;
|
||||
* жёсткие переносы (два пробела или `\` в конце строки);
|
||||
* мягкая склейка абзацев с переносом по словам под ширину экрана.
|
||||
* **Кодировки: CP866, CP1251, KOI8-R, UTF-8.**
|
||||
* автоопределение при открытии (BOM → UTF-8; валидность multibyte-структуры;
|
||||
частотный анализ ходовых русских букв для 8-битных);
|
||||
* переключение по кругу клавишей **F8** в любой момент;
|
||||
* 8-битные кодировки отличаются только перекодировкой глифов на отрисовке —
|
||||
переключение мгновенно;
|
||||
* UTF-8 декодируется в CP866 в **отдельный набор** (файл + индекс + кэш);
|
||||
второй набор готовится **в фоне**, пока вы читаете документ, — обычно
|
||||
к первому нажатию F8 он уже построен и переключение мгновенно,
|
||||
с сохранением позиции. Если фон не успел, F8 докручивает начатую
|
||||
сборку (со спиннером), а не начинает её заново.
|
||||
* **Три режима просмотра:**
|
||||
* **MD** — форматированный Markdown (по умолчанию);
|
||||
* **RAW** (**F2**) — исходный текст без разметки: перенос строк кратно 80
|
||||
(**F3** — режим панорамы с горизонтальным скроллом);
|
||||
* **HEX** (**F4**) — дамп *оригинального* файла:
|
||||
`0x012340 │ 16 байт hex │ 16 печатных символов`. Печатная колонка
|
||||
интерпретируется текущей кодировкой; для UTF-8 глиф ставится на позиции
|
||||
лид-байта, continuation-байты показываются точкой.
|
||||
* **Единая позиция** при любых переключениях: MD ↔ RAW ↔ HEX и смена
|
||||
кодировки сохраняют текущее место в документе (между наборами разного
|
||||
размера — пропорционально, с точностью до строки).
|
||||
* **Прогрессивная загрузка**: первый экран показывается сразу, индексация
|
||||
продолжается в фоне; по готовой части документа уже можно листать,
|
||||
**Esc**/**F10** прерывают загрузку.
|
||||
* **Фоновая работа незаметна**: второй набор кодировки строится только в
|
||||
паузах между клавишами (после нажатия выдерживается пауза), поэтому
|
||||
скролл — в том числе с автоповтором — не теряет плавности.
|
||||
|
||||
---
|
||||
|
||||
## Запуск
|
||||
|
||||
```
|
||||
MDVIEW2.EXE <файл.md>
|
||||
```
|
||||
|
||||
Без аргумента открывается `README.MD` из текущего каталога.
|
||||
|
||||
## Клавиши
|
||||
|
||||
| Клавиша | Действие |
|
||||
|--------------|-------------------------------------------------------------|
|
||||
| ↑ / ↓ | прокрутка на одну строку |
|
||||
| PgUp / PgDn | прокрутка на экран (30 строк) |
|
||||
| Home / End | в начало / в конец документа |
|
||||
| ← / → | горизонтальный сдвиг: код/таблицы в MD, панорама в RAW |
|
||||
| F1 | справка |
|
||||
| F2 | RAW-режим ↔ MD |
|
||||
| F3 | в RAW: перенос строк ↔ панорама |
|
||||
| F4 | HEX-режим ↔ прежний вид |
|
||||
| F8 | кодировка: CP866 → CP1251 → KOI8-R → UTF-8 → … |
|
||||
| Esc / F10 | выход (во время загрузки — прервать её) |
|
||||
|
||||
Статус-бар (верхняя строка): имя файла, кодировка, диапазон видимых строк
|
||||
и процент прокрутки. Нижняя строка — меню доступных F-клавиш.
|
||||
|
||||
---
|
||||
|
||||
## Ограничения
|
||||
|
||||
| Параметр | Значение |
|
||||
|---------------------------------|-------------------------------------------|
|
||||
| Размер файла | до 256 КБ (больший — обрезается с предупреждением) |
|
||||
| Логических строк (после переносов) | до 18 432 |
|
||||
| Длина строки в рендер-кэше | 255 ячеек (RAW/HEX ограничения не имеют) |
|
||||
| Колонок в таблице | до 16 |
|
||||
| Шаг табуляции | 4 |
|
||||
|
||||
При исчерпании любого лимита документ завершается строкой-сообщением
|
||||
с указанием причины обрыва; всё, что вошло, доступно для просмотра.
|
||||
|
||||
## Требования
|
||||
|
||||
* Sprinter-2000 с ОС ESTEX;
|
||||
* расширенная память (EMM): в худшем случае (файл 256 КБ + оба набора
|
||||
кодировок) — до ~150 страниц по 16 КБ (~2,4 МБ). Для типичных файлов
|
||||
в десятки килобайт достаточно нескольких десятков страниц.
|
||||
|
||||
Код, данные, стек и куча программы занимают окна W1+W2 (32 КБ, режим
|
||||
памяти `small`); окно W3 используется только для доступа к EMM-страницам.
|
||||
|
||||
---
|
||||
|
||||
## Сборка
|
||||
|
||||
Требуется тулчейн этого репозитория (обёртка `sprinter-cc` над SDCC 4.5).
|
||||
Из каталога `examples/mdview2`:
|
||||
|
||||
```
|
||||
make # собрать mdview2.exe
|
||||
make floppy # собрать и упаковать дискету для MAME (mc.img)
|
||||
make run # floppy + автоматический прогон в MAME (скриншоты)
|
||||
```
|
||||
|
||||
На дискету кладутся: `MDVIEW2.EXE` и три документа, каждый в своей кодировке
|
||||
(заодно покрывают все пути автодетекта): `README.MD` — этот файл, как есть
|
||||
(UTF-8); `DEMO.MD` — демонстрация всех элементов разметки (CP1251);
|
||||
`CHANGES.MD` — история версий (CP866).
|
||||
|
||||
## Структура исходников
|
||||
|
||||
| Файл | Назначение |
|
||||
|------------------|--------------------------------------------------------------|
|
||||
| `mdview2.c` | ядро: EMM-аллокации, рендер-кэш, наборы кодировок, загрузка файла, главный цикл |
|
||||
| `mdview2_index.c`| парсер/индексатор Markdown — единственный проход по файлу |
|
||||
| `mdview2_md.c` | MD-вид: отрисовка из кэша, прокрутка |
|
||||
| `mdview2_raw.c` | RAW-вид (F2/F3) |
|
||||
| `mdview2_hex.c` | HEX-вид (F4) |
|
||||
| `mdview2_enc.c` | кодировки: детект, ремап-таблицы, конвертер UTF-8 → CP866 |
|
||||
| `mdview2_table.c`| отрисовка таблиц |
|
||||
| `mdview2_status.c`| статус-бар, меню, спиннер |
|
||||
| `mdview2_help.c` | справка (F1) |
|
||||
| `mdview2_conf.h` | конфигурация: `WITH_RAW` / `WITH_HEX` (модули отключаемы) |
|
||||
| `mdview2.h` | общие константы, атрибуты, межмодульный API |
|
||||
|
||||
Подробности архитектуры — в `docs/mdview2-plan.md`.
|
||||
|
||||
## Лицензия и авторы
|
||||
|
||||
© 2026 Петров А.Г. Часть проекта Sprinter C Compiler
|
||||
(см. LICENSE в корне репозитория).
|
||||
+1338
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
* mdview2.h — общие определения и интерфейс между модулями mdview2.
|
||||
*
|
||||
* Монолит mdview2.c расщеплён на ядро + модули: index (парсер), md/raw (виды),
|
||||
* enc (кодировки), table, help. Здесь — разделяемые константы экрана/атрибутов,
|
||||
* словари флагов/стилей, тип cache_rec_t, extern-объявления разделяемого
|
||||
* состояния ядра и API каждого модуля (сгруппированы по секциям ниже).
|
||||
*/
|
||||
#ifndef MDVIEW2_H
|
||||
#define MDVIEW2_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <palette.h> /* COLOR(), COLOR_* */
|
||||
#include "mdview2_conf.h" /* WITH_RAW / WITH_HEX — выбор опциональных модулей */
|
||||
|
||||
/* ---- Геометрия экрана -------------------------------------------- */
|
||||
#define SCREEN_W 80
|
||||
#define SCREEN_H 32
|
||||
#define VIEW_TOP_ROW 1
|
||||
#define VIEW_H 30 /* видимая область: строки 1..30 включительно */
|
||||
#define MENU_ROW 31
|
||||
#define TAB_STOP 4
|
||||
#define HPAN_STEP 8u /* шаг горизонтального сдвига (←/→) */
|
||||
|
||||
/* ---- Геометрия статус-бара (строка 0): фиксированные поля и разделители ---- */
|
||||
#define SPINNER_COL 8 /* слот спиннера загрузки */
|
||||
#define DIV1_X 45 /* разделитель │ перед числами диапазона строк */
|
||||
#define DIV2_X 71 /* разделитель │ перед процентом */
|
||||
/* Числа/метка пишутся с DIVn_X+2; область между разделителями — [DIV1_X+1 .. DIV2_X-1]. */
|
||||
|
||||
/* ---- Параметры файла и памяти ------------------------------------ */
|
||||
#define PAGE_BITS 14u
|
||||
#define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */
|
||||
#define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u))
|
||||
#define MAX_PAGES 16 /* 16 страниц × 16 КБ = 256 КБ */
|
||||
#define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE)
|
||||
#define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */
|
||||
|
||||
/* ---- Палитра атрибутов ------------------------------------------- */
|
||||
#define ATTR_RESET COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
||||
#define ATTR_TEXT COLOR(COLOR_LIGHTGRAY, COLOR_BLUE)
|
||||
#define ATTR_TEXT_TITLE1 COLOR(COLOR_YELLOW, COLOR_BLUE)
|
||||
#define ATTR_TEXT_TITLE2 COLOR(COLOR_LIGHTBLUE, COLOR_BLUE)
|
||||
#define ATTR_TEXT_TITLE3 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||
#define ATTR_TEXT_TITLE4 COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||
#define ATTR_TEXT_BOLD COLOR(COLOR_LIGHTRED, COLOR_BLUE)
|
||||
#define ATTR_TEXT_ITALIC COLOR(COLOR_LIGHTGREEN, COLOR_BLUE)
|
||||
#define ATTR_TEXT_UNDERSORE COLOR(COLOR_LIGHTMAGENTA, COLOR_BLUE)
|
||||
#define ATTR_TEXT_CODE COLOR(COLOR_WHITE, COLOR_BLUE)
|
||||
#define ATTR_TEXT_STRIKE COLOR(COLOR_DARKGRAY, COLOR_BLUE)
|
||||
#define ATTR_LIST_MARKER COLOR(COLOR_LIGHTCYAN, COLOR_BLUE)
|
||||
#define ATTR_QUOTE_MARKER COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||
#define ATTR_HR COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||
#define ATTR_BOX COLOR(COLOR_CYAN, COLOR_BLUE)
|
||||
#define ATTR_TRUNC COLOR(COLOR_YELLOW, COLOR_BLUE)
|
||||
#define ATTR_BAR COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
||||
#define ATTR_BAR_SPINNER COLOR(COLOR_WHITE, COLOR_LIGHTCYAN)
|
||||
#define ATTR_MENU_T COLOR(COLOR_BLACK, COLOR_LIGHTCYAN)
|
||||
#define ATTR_MENU_K COLOR(COLOR_YELLOW, COLOR_BLACK)
|
||||
#define ATTR_WARN COLOR(COLOR_YELLOW, COLOR_RED) /* строка-обрыв (исчерпан кэш/лимит) */
|
||||
|
||||
/* ---- Атрибуты диалога справки ------------------------------------ */
|
||||
#define ATTR_HELP_BG COLOR(COLOR_LIGHTGRAY, COLOR_BLACK)
|
||||
#define ATTR_HELP_BDR COLOR(COLOR_WHITE, COLOR_BLACK)
|
||||
#define ATTR_HELP_TIT COLOR(COLOR_YELLOW, COLOR_BLACK)
|
||||
#define ATTR_HELP_HDR COLOR(COLOR_WHITE, COLOR_BLACK)
|
||||
|
||||
/* ---- Режимы просмотра -------------------------------------------- */
|
||||
#define VIEW_MD 0 /* markdown с форматированием (по умолчанию) */
|
||||
#define VIEW_RAW_WRAP 1 /* RAW: длинные строки переносятся кратно 80 */
|
||||
#define VIEW_RAW_HSCROLL 2 /* RAW: длинные строки в одну, гориз. скролл */
|
||||
#define VIEW_HEX 3 /* HEX-дамп оригинального файла */
|
||||
#define VIEW_IS_RAW(v) ((v) == VIEW_RAW_WRAP || (v) == VIEW_RAW_HSCROLL)
|
||||
|
||||
/* ---- Кодировки --------------------------------------------------- */
|
||||
/* 8-битные (CP866/CP1251/KOI8R) различаются только ремапом глифов [128-255]
|
||||
* на отрисовке; UTF-8 — отдельный декодированный набор. */
|
||||
#define ENC_CP866 0
|
||||
#define ENC_CP1251 1
|
||||
#define ENC_KOI8R 2
|
||||
#define ENC_UTF8 3
|
||||
#define ENC_UNSUPPORTED -1 /* UTF16 / UTF32 */
|
||||
#define CONV_MARGIN 4096u /* на сколько байт держать UTF-конвертацию впереди индексатора */
|
||||
|
||||
/* ---- Флаги сегмента индекса (IF_*) — общие для индексатора/кэша/вью ---- */
|
||||
#define IF_CONT 0x01u /* сегмент является продолжением перенесённой строки */
|
||||
#define IF_NOWRAP 0x02u /* строка не переносится (кодовый блок / HR / таблица) */
|
||||
#define IF_BLANK 0x04u /* визуально пустая строка */
|
||||
#define IF_CODE 0x08u /* тело fenced code-блока (verbatim-режим) */
|
||||
#define IF_HSCROLL 0x10u /* блок горизонтально скроллируется ЦЕЛИКОМ (код/таблица),
|
||||
* включая строки короче 80 — двигаются как единый блок.
|
||||
* НЕ ставится на HR и границы fence (им нечего прятать). */
|
||||
#define IF_TRUNC_MSG 0x20u /* строка-обрыв: рисуется ВЖИВУЮ фикс. текстом (без контент-кэша);
|
||||
* cache_rec.reserved несёт код причины (TRUNC_*). */
|
||||
|
||||
/* ---- Причина обрыва индексации (cache_rec.reserved при IF_TRUNC_MSG) ---- */
|
||||
#define TRUNC_CONTENT 1 /* исчерпан контент-кэш (MAX_CACHE_CONTENT_PAGES) */
|
||||
#define TRUNC_LINES 2 /* исчерпана ёмкость индекса (max_lines) */
|
||||
#define TRUNC_FILE 3 /* файл больше MAX_FILE — прочитаны первые 256 КБ */
|
||||
|
||||
/* ---- Словарь сегментов/стилей (общий для индексатора и таблиц) ---- */
|
||||
/* Тип сегмента-продолжения, передаваемый в emit_seg()/inline_scan(). */
|
||||
#define CK_PLAIN 0
|
||||
#define CK_QUOTE 1
|
||||
#define CK_LIST 2
|
||||
#define CK_OTHER 3
|
||||
|
||||
/* Начальный inline-стиль сегмента (INIT_STYLE_*). */
|
||||
#define INIT_STYLE_PLAIN 0x0
|
||||
#define INIT_STYLE_BOLD 0x1
|
||||
#define INIT_STYLE_ITALIC 0x2
|
||||
#define INIT_STYLE_UNDER 0x3
|
||||
#define INIT_STYLE_CODE 0x4
|
||||
#define INIT_STYLE_STRIKE 0x5
|
||||
|
||||
/* ---- Таблицы: CP866 light box-drawing (общий для индексатора и модуля) ---- */
|
||||
#define TBL_MAX_COLS 16
|
||||
#define TBL_ATTR ATTR_BOX
|
||||
#define TBL_H 0xC4 /* ─ */
|
||||
#define TBL_V 0xB3 /* │ */
|
||||
#define TBL_TL 0xDA /* ┌ */
|
||||
#define TBL_TM 0xC2 /* ┬ */
|
||||
#define TBL_TR 0xBF /* ┐ */
|
||||
#define TBL_ML 0xC3 /* ├ */
|
||||
#define TBL_MM 0xC5 /* ┼ */
|
||||
#define TBL_MR 0xB4 /* ┤ */
|
||||
#define TBL_BL 0xC0 /* └ */
|
||||
#define TBL_BM 0xC1 /* ┴ */
|
||||
#define TBL_BR 0xD9 /* ┘ */
|
||||
|
||||
/* ================================================================== *
|
||||
* Разделяемые символы mdview2.c, используемые модулем RAW.
|
||||
* ================================================================== */
|
||||
extern uint8_t g_view; /* активный режим просмотра (VIEW_*) */
|
||||
extern uint32_t file_size; /* размер активного буфера документа */
|
||||
extern const uint8_t *g_remap; /* таблица ремапа [128-255] (0 = нет) */
|
||||
extern uint8_t g_scratch_phys; /* scratch EMM-страница для win_rest */
|
||||
|
||||
char fb(uint32_t p); /* байт активного буфера (W3-маппинг) */
|
||||
void win_rest(uint8_t row, uint8_t col, uint8_t h, uint8_t w, uint8_t page, uint16_t off);
|
||||
void fill_row(uint8_t y, uint8_t attr);
|
||||
uint8_t pct16(uint16_t num, uint16_t den); /* num*100/den (0..100), 16-бит, без __divulong */
|
||||
uint32_t seg_off(uint16_t idx); /* offset исходника для логической строки idx */
|
||||
uint16_t line_at_off(uint32_t off); /* обратно: последняя строка с seg_off <= off */
|
||||
|
||||
/* ================================================================== *
|
||||
* API модуля RAW (mdview2_raw.c).
|
||||
* ================================================================== */
|
||||
void raw_seed_from(uint16_t md_top_line); /* вход в RAW: позиция по top_line */
|
||||
uint32_t raw_pos(void); /* текущий байт-offset верха экрана RAW */
|
||||
void raw_reanchor(uint32_t off); /* поставить RAW на строку с байтом off */
|
||||
void raw_draw(void); /* перерисовать область документа в RAW */
|
||||
uint8_t raw_pct(void); /* % прокрутки по байтам (для render_raw_status_numbers) */
|
||||
uint8_t raw_key(uint8_t scan); /* навигация RAW; 1 = обработано */
|
||||
void raw_screen_init(void); /* очистка экрана с нужным аттрибутом */
|
||||
void raw_renorm(void); /* выровнять позицию при смене под-режима (F3) */
|
||||
|
||||
/* ================================================================== *
|
||||
* API модуля HEX (mdview2_hex.c) — дамп ОРИГИНАЛЬНОГО файла (F4).
|
||||
* Позиция hex_pos/hex_reanchor — в байтах ОРИГИНАЛА (orig_file_*);
|
||||
* конвертацию в/из активного буфера делает ядро (map_off).
|
||||
* ================================================================== */
|
||||
uint32_t hex_pos(void); /* offset верхнего ряда (кратен 16) */
|
||||
void hex_reanchor(uint32_t orig_off); /* поставить на ряд с байтом orig_off */
|
||||
void hex_draw(void); /* перерисовать область документа */
|
||||
uint8_t hex_key(uint8_t scan); /* навигация HEX; 1 = обработано */
|
||||
void hex_screen_init(void); /* очистка экрана */
|
||||
|
||||
/* ================================================================== *
|
||||
* Запись директории рендер-кэша (одна на видимую строку, РОВНО 8 байт —
|
||||
* cache_dir_get/put адресуют сдвигом idx<<3). Контент — len пар (char,attr).
|
||||
* ================================================================== */
|
||||
typedef struct cache_rec_s {
|
||||
uint8_t page; /* EMM-страница рендер-кэша (физический номер) */
|
||||
uint16_t off; /* смещение в странице, 0..16383 (байты, не ячейки) */
|
||||
uint8_t len; /* длина контента в ЯЧЕЙКАХ (char,attr пар), 0..255; 0 = пустая/HR */
|
||||
uint8_t flags; /* копия IF_NOWRAP/IF_CODE/IF_BLANK на момент рендера */
|
||||
uint8_t reserved; /* не используется */
|
||||
uint8_t pad[2]; /* явный резерв, добивка до 8 байт */
|
||||
} cache_rec_t;
|
||||
|
||||
void cache_dir_get(uint16_t idx, cache_rec_t *r); /* читатель директории кэша (ядро) */
|
||||
void put_str_attr(uint8_t x, uint8_t y, const char *s, uint8_t attr); /* печать строки с атрибутом */
|
||||
|
||||
/* ================================================================== *
|
||||
* Состояние навигации/представления (определено в mdview2.c).
|
||||
* ================================================================== */
|
||||
extern uint16_t n_lines; /* всего строк в индексе текущего набора */
|
||||
extern uint16_t top_line; /* верхняя видимая строка */
|
||||
extern uint8_t viewport_x; /* горизонтальный сдвиг (nowrap-строки) */
|
||||
extern char filename[]; /* имя файла для статус-бара */
|
||||
extern uint8_t g_loading; /* 1 во время index_lines() */
|
||||
extern uint8_t g_ready; /* 1 когда первичный документ построен (F2 RAW) */
|
||||
extern uint8_t g_f8_enabled; /* можно ли сейчас переключать кодировку */
|
||||
|
||||
/* ================================================================== *
|
||||
* Статус-бар (строка 0), меню (строка 31), спиннер — mdview2_status.c.
|
||||
* Атомарные части: prerender (фикс. хром, один раз) / encoding (поле кодировки)
|
||||
* / numbers (диапазон строк + %) / menu / spinner — обновляются по отдельности.
|
||||
* ================================================================== */
|
||||
void prerender_status(void); /* фикс. часть: фон, разделители, MDVIEW, имя файла (1 раз) */
|
||||
void status_encoding(void); /* только поле кодировки (col 37) */
|
||||
void render_md_status_numbers(void); /* числа MD: диапазон строк + % */
|
||||
void render_raw_status(void); /* RAW mode: метка режима */
|
||||
void render_hex_status(void); /* HEX mode: метка режима */
|
||||
void render_full_status(void); /* encoding + numbers (MD) */
|
||||
void render_menu(void); /* строка меню (по смене режима/готовности) */
|
||||
uint16_t drawable_lines(void); /* число строк, готовых к показу */
|
||||
void spinner_tick(void); /* кадр спиннера загрузки */
|
||||
void spinner_show(uint8_t on); /* вкл/выкл спиннер */
|
||||
void render_percent_progress(uint8_t pct); /* показать процентный прогресс просмотра */
|
||||
|
||||
/* ================================================================== *
|
||||
* Отрисовка области MD-документа и прокрутка (mdview2_md.c).
|
||||
* ================================================================== */
|
||||
void draw_viewport_from_cache(void); /* перерисовать область документа из кэша */
|
||||
void clamp_top(void); /* привести top_line к диапазону */
|
||||
void md_scroll_up(uint16_t n);
|
||||
void md_scroll_down(uint16_t n);
|
||||
void md_scroll_horizon(int8_t delta); /* горизонтальный сдвиг nowrap-блоков */
|
||||
uint8_t md_key(uint8_t scan); /* навигация MD (после загрузки); 1 = обработано */
|
||||
|
||||
/* ================================================================== *
|
||||
* API модуля справки (mdview2_help.c).
|
||||
* ================================================================== */
|
||||
void show_help(void); /* модальный диалог F1 */
|
||||
|
||||
/* ================================================================== *
|
||||
* Парсер/индексатор (mdview2_index.c) и разделяемое с ним состояние.
|
||||
* Хранилище индекса (index_blk/pages/phys, n_lines/max_lines) — в ядре
|
||||
* (аллокация/doc-slot), индексатор пишет в него; cache_*-писатели и
|
||||
* progress_tick ядро экспортирует индексатору (spinner_tick — из status).
|
||||
* ================================================================== */
|
||||
#define INDEX_RECS_PER_PAGE 2048u /* 16384 / 8; запись всегда в одной странице */
|
||||
#define MAX_CACHE_LINE_LEN 255u /* кап длины контента строки в ячейках (буфер g_cells) */
|
||||
|
||||
extern uint16_t max_lines; /* ёмкость индекса: index_pages * 2048 */
|
||||
extern uint8_t index_blk, index_pages; /* дескриптор EMM-блока индекса */
|
||||
extern uint8_t index_phys[]; /* физ. страницы индекса */
|
||||
extern uint8_t index_truncated; /* индекс упёрся в ёмкость */
|
||||
extern uint8_t g_abort; /* F10/Esc во время загрузки → прервать индексацию */
|
||||
extern uint8_t g_file_clamped; /* файл был обрезан до MAX_FILE при загрузке (>256 КБ) */
|
||||
|
||||
uint8_t cache_reserve(uint16_t nbytes, uint8_t *out_page, uint16_t *out_off);
|
||||
void cache_commit(uint8_t page, uint16_t off, const void *buf, uint16_t len);
|
||||
void cache_dir_put(uint16_t idx, const cache_rec_t *r);
|
||||
void progress_tick(void); /* кооперативный шаг loading-loop (рисует/листает) */
|
||||
|
||||
/* Резюмируемая индексация (для фоновой сборки второго набора): begin сбрасывает
|
||||
* проход, step выполняет до budget итераций (блоков исходника) и возвращает 1,
|
||||
* когда индекс финализирован. Межшаговое состояние — статики модуля index;
|
||||
* оно не входит в docset_t (см. bg_step ядра). */
|
||||
void index_begin(void);
|
||||
uint8_t index_step(uint8_t budget);
|
||||
extern uint8_t g_bg_building; /* 1 = фоновый шаг индексации: не трогать экран (спиннер) */
|
||||
|
||||
/* ================================================================== *
|
||||
* Буфер ячеек рендера (mdview2.c) — общий с модулем таблиц.
|
||||
* Таблицы эмитят свои строки теми же примитивами, что и индексатор.
|
||||
* ================================================================== */
|
||||
extern uint8_t g_ncells; /* ячеек в текущем сегменте */
|
||||
void gc_put(char ch, uint8_t attr); /* добавить ячейку в g_cells */
|
||||
void gc_fill(char ch, uint8_t attr, uint8_t n); /* n одинаковых ячеек */
|
||||
uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
|
||||
uint8_t ckind, uint8_t line_style, uint8_t base_attr,
|
||||
uint8_t nowrap);
|
||||
|
||||
/* ================================================================== *
|
||||
* API модуля таблиц (mdview2_table.c) — вызывается из index_lines().
|
||||
* ================================================================== */
|
||||
uint32_t row_end(uint32_t p); /* offset завершающего '\n' (или file_size) */
|
||||
uint32_t table_first_cell(uint32_t row_start); /* контент первой ячейки строки */
|
||||
uint8_t table_next_cell(uint32_t *pp, uint32_t lineend, uint32_t *cs, uint32_t *ce);
|
||||
uint8_t table_is_sep_row(uint32_t row_start, uint32_t lineend);
|
||||
void table_border(const uint8_t *widths, uint8_t ncols, char left, char mid, char right);
|
||||
void table_data_row(uint32_t row_start, uint32_t lineend, const uint8_t *widths, uint8_t ncols);
|
||||
|
||||
/* ================================================================== *
|
||||
* Кодировки и UTF-8 конвертация (mdview2_enc.c).
|
||||
* Состояние кодека владеется модулем; build_doc()/index_lines() ядра
|
||||
* оркестрируют его (привязка UTF-буфера к живому file_*, сборка наборов).
|
||||
* ================================================================== */
|
||||
extern uint8_t g_encoding; /* активная кодировка (ENC_*) */
|
||||
extern uint8_t g_utf_building; /* 1 пока UTF-конвертация не дошла до конца исходника */
|
||||
extern uint8_t utf_avail; /* 1 = страницы UTF-8 выделены */
|
||||
extern uint8_t utf_blk, utf_pages; /* дескриптор EMM-буфера UTF-набора */
|
||||
extern uint8_t utf_phys[]; /* физ. страницы UTF-буфера */
|
||||
extern uint32_t utf_size; /* размер сконвертированного UTF-потока */
|
||||
|
||||
/* Ядро-owned состояние, читаемое кодеком (определено в mdview2.c). */
|
||||
extern uint8_t cur_page; /* текущая страница файла в W3 (инвалидация маппинга) */
|
||||
extern uint8_t orig_file_phys[]; /* физ. страницы снимка исходного файла */
|
||||
extern uint8_t orig_file_pages;
|
||||
extern uint32_t orig_file_size;
|
||||
|
||||
void set_encoding(uint8_t enc); /* активная кодировка + таблица ремапа */
|
||||
const char *enc_name(uint8_t enc); /* короткое имя для статус-бара */
|
||||
uint8_t detect_encoding(void); /* автоопределение по сэмплу байт */
|
||||
void win_rest_remap(uint8_t row, uint8_t w, uint8_t page, uint16_t off); /* вывод среза с ремапом */
|
||||
void utf_convert_more(uint32_t target); /* инкрементальная конвертация до target байт */
|
||||
uint8_t utf_alloc(void); /* выделить EMM-страницы под UTF-набор; 0 = нет EMM */
|
||||
uint8_t utf_cp_glyph(uint32_t cp); /* кодпойнт → один CP866-глиф ('?' без аналога) */
|
||||
|
||||
#endif /* MDVIEW2_H */
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* mdview2_conf.h — конфигурация сборки mdview2: какие опциональные модули
|
||||
* просмотра включать. 1 = включить (код входит в бинарь), 0 = исключить
|
||||
* полностью (нулевой расход кода/памяти).
|
||||
*
|
||||
* Модули обёрнуты в #if WITH_xxx целиком, поэтому файлы можно держать в
|
||||
* сборке всегда (при 0 они компилируются в пустой объектник) — менять
|
||||
* нужно только эти define.
|
||||
*/
|
||||
#ifndef MDVIEW2_CONF_H
|
||||
#define MDVIEW2_CONF_H
|
||||
|
||||
#define WITH_RAW 1 /* RAW-просмотр исходника (F2): mdview2_raw.c */
|
||||
#define WITH_HEX 1 /* HEX-дамп оригинального файла (F4): mdview2_hex.c */
|
||||
|
||||
#endif /* MDVIEW2_CONF_H */
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* mdview2_enc.c — кодировки и конвертация UTF-8 → CP866.
|
||||
*
|
||||
* Две связанные подсистемы:
|
||||
* 1. 8-битные кодировки (CP866/CP1251/KOI8-R) — различаются только ремапом
|
||||
* старших байтов [128-255] на ОТРИСОВКЕ (g_remap, win_rest_remap).
|
||||
* Автоопределение — detect_encoding по частоте ходовых букв.
|
||||
* 2. UTF-8 — отдельный набор: исходник инкрементально декодируется в
|
||||
* utf_phys[] как чистый CP866 (utf_convert_more), после чего весь
|
||||
* конвейер индексации/кэша работает по нему без ремапа.
|
||||
*
|
||||
* Владеет состоянием кодека (conv-буфер, позиция конвертации, UTF-страницы);
|
||||
* описание разделяемых символов и API — в mdview2.h. Оркестрацией (привязка
|
||||
* UTF-буфера к живому file_*, сборка наборов) занимается build_doc() ядра.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <conio.h> /* COLOR()/COLOR_* для ATTR_* в attr_is_content */
|
||||
#include <sprinter.h> /* sprinter_page_w3 */
|
||||
#include <sprinter_mem.h> /* bank_read/bank_write/mem_alloc_pages/mem_get_page */
|
||||
#include "mdview2.h"
|
||||
|
||||
/* ---- Состояние кодировки/UTF (владелец — этот модуль) ------------- */
|
||||
uint8_t g_encoding = ENC_CP866; /* активная кодировка */
|
||||
|
||||
uint8_t utf_blk;
|
||||
uint8_t utf_pages;
|
||||
uint8_t utf_phys[MAX_PAGES];
|
||||
uint32_t utf_size;
|
||||
uint8_t utf_avail; /* 1 = страницы UTF-8 выделены */
|
||||
uint8_t g_utf_building; /* 1 пока конвертация не дошла до конца исходника */
|
||||
|
||||
/* Приватное состояние конвертера (наружу не торчит — сброс через
|
||||
* utf_conv_reset(), чтение оригинала через cv_read). */
|
||||
static uint32_t utf_src; /* позиция чтения ОРИГИНАЛА (utf-8) */
|
||||
static uint8_t cv_page = 0xFF; /* orig-страница в W3 для cv_read (своя от cur_page) */
|
||||
|
||||
/* Таблицы ремапа СТАРШИХ байтов [128-255] в CP866. Индексируются (ch-0x80):
|
||||
* младшие 128 (ASCII) — identity, в ремапе не участвуют (win_rest_remap
|
||||
* трогает только ch>=0x80), поэтому в таблицах их нет. Кириллица + ходовая
|
||||
* пунктуация; неизвестное → '?' (0x3F). Сгенерированы Python codecs. */
|
||||
static const uint8_t cp1251_to_866[128] = {
|
||||
/* 80 */ 0x3F, 0x3F, 0x27, 0x3F, 0x22, 0x2E, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3C, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||
/* 90 */ 0x3F, 0x27, 0x27, 0x22, 0x22, 0x07, 0x2D, 0x2D, 0x3F, 0x3F, 0x3F, 0x3E, 0x3F, 0x3F, 0x3F, 0x3F,
|
||||
/* A0 */ 0xFF, 0xF6, 0xF7, 0x3F, 0xFD, 0x3F, 0x3F, 0x3F, 0xF0, 0x63, 0xF2, 0x3C, 0x3F, 0x3F, 0x72, 0xF4,
|
||||
/* B0 */ 0xF8, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xFA, 0xF1, 0xFC, 0xF3, 0x3E, 0x3F, 0x3F, 0x3F, 0xF5,
|
||||
/* C0 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||
/* D0 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
||||
/* E0 */ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
||||
/* F0 */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
||||
};
|
||||
static const uint8_t koi8r_to_866[128] = {
|
||||
/* 80 */ 0xC4, 0xB3, 0xDA, 0xBF, 0xC0, 0xD9, 0xC3, 0xB4, 0xC2, 0xC1, 0xC5, 0xDF, 0xDC, 0xDB, 0xDD, 0xDE,
|
||||
/* 90 */ 0xB0, 0xB1, 0xB2, 0x3F, 0xFE, 0xF9, 0xFB, 0x3F, 0x3F, 0x3F, 0xFF, 0x3F, 0xF8, 0x3F, 0xFA, 0x3F,
|
||||
/* A0 */ 0xCD, 0xBA, 0xD5, 0xF1, 0xD6, 0xC9, 0xB8, 0xB7, 0xBB, 0xD4, 0xD3, 0xC8, 0xBE, 0xBD, 0xBC, 0xC6,
|
||||
/* B0 */ 0xC7, 0xCC, 0xB5, 0xF0, 0xB6, 0xB9, 0xD1, 0xD2, 0xCB, 0xCF, 0xD0, 0xCA, 0xD8, 0xD7, 0xCE, 0x63,
|
||||
/* C0 */ 0xEE, 0xA0, 0xA1, 0xE6, 0xA4, 0xA5, 0xE4, 0xA3, 0xE5, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE,
|
||||
/* D0 */ 0xAF, 0xEF, 0xE0, 0xE1, 0xE2, 0xE3, 0xA6, 0xA2, 0xEC, 0xEB, 0xA7, 0xE8, 0xED, 0xE9, 0xE7, 0xEA,
|
||||
/* E0 */ 0x9E, 0x80, 0x81, 0x96, 0x84, 0x85, 0x94, 0x83, 0x95, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||
/* F0 */ 0x8F, 0x9F, 0x90, 0x91, 0x92, 0x93, 0x86, 0x82, 0x9C, 0x9B, 0x87, 0x98, 0x9D, 0x99, 0x97, 0x9A,
|
||||
};
|
||||
|
||||
/* Устанавливает активную кодировку и таблицу ремапа (для 8-битных). */
|
||||
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;
|
||||
}
|
||||
|
||||
/* Самые ходовые строчные русские буквы (о е а и н т с р в л) — их байты
|
||||
* различают 8-битные кодировки по частоте. */
|
||||
// static const uint8_t common866 [10] = {0xAE,0xA5,0xA0,0xA8,0xAD,0xE2,0xE1,0xE0,0xA2,0xAB};
|
||||
// static const uint8_t common1251[10] = {0xEE,0xE5,0xE0,0xE8,0xED,0xF2,0xF1,0xF0,0xE2,0xEB};
|
||||
// static const uint8_t commonkoi [10] = {0xCF,0xC5,0xC1,0xC9,0xCE,0xD4,0xD3,0xD2,0xD7,0xCC};
|
||||
// static uint8_t in_set10(const uint8_t *s, uint8_t b)
|
||||
// {
|
||||
// for (uint8_t i = 0; i < 10; i++) if (s[i] == b) return 1;
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
/* Проверяем только пять самых популярных символов (о е а и н) */
|
||||
static const uint8_t common866 [5] = {0xAE,0xA5,0xA0,0xA8,0xAD};
|
||||
static const uint8_t common1251[5] = {0xEE,0xE5,0xE0,0xE8,0xED};
|
||||
static const uint8_t commonkoi [5] = {0xCF,0xC5,0xC1,0xC9,0xCE};
|
||||
static uint8_t in_set10(const uint8_t *s, uint8_t b)
|
||||
{
|
||||
for (uint8_t i = 0; i < 5; i++) if (s[i] == b) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Автоопределение кодировки дешёвым сканом байтов (до построения индекса).
|
||||
* BOM → UTF8; иначе валидность UTF-8 (структура multibyte); иначе 8-бит по
|
||||
* частоте ходовых букв; фолбэк CP866. */
|
||||
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 > 1024u) n = 1024u; /* сэмпл: первый 1 КБ — детекции хватает */
|
||||
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 (in_set10(common866, b)) s866++;
|
||||
if (in_set10(common1251, b)) s1251++;
|
||||
if (in_set10(commonkoi, 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;
|
||||
}
|
||||
|
||||
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. */
|
||||
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 - 0x80]; /* таблицы хранят только старшие 128 */
|
||||
}
|
||||
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; /* байт в буфере, ещё не сброшено */
|
||||
static uint8_t conv_page; /* индекс текущей страницы в utf_phys */
|
||||
static uint16_t conv_off; /* уже сброшенное смещение в этой странице */
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/* Подстановки одиночных не-кириллических символов UTF-8 → один CP866-байт.
|
||||
* Таблица вместо switch (экономия кода); линейный поиск дёшев — вызывается
|
||||
* лишь при конвертации на редких символьных кодпойнтах. Чтобы добавить новый
|
||||
* символ — достаточно дописать одну строку {кодпойнт, байт-CP866}.
|
||||
* (Символы с НЕ-1:1 заменой, напр. … -> "...", обрабатываются отдельно ниже.) */
|
||||
typedef struct { uint16_t utf8; uint8_t cp866; } utf_sym_t;
|
||||
static const utf_sym_t utf_sym[] = {
|
||||
{0x00A0, 0x20}, {0x00AB, 0x3C}, {0x00BB, 0x3E}, /* nbsp « » → space < > */
|
||||
{0x00B0, 0xF8}, {0x00B7, 0xFA}, /* ° · */
|
||||
{0x2013, 0x2D}, {0x2014, 0x2D}, /* – — → '-' */
|
||||
{0x2018, 0x27}, {0x2019, 0x27}, /* ‘ ’ → '\'' */
|
||||
{0x201C, 0x22}, {0x201D, 0x22}, /* “ ” → '"' */
|
||||
{0x2022, 0xF9}, {0x2116, 0xFC}, /* • → ∙ № */
|
||||
{0x2190, 0x1B}, {0x2191, 0x18}, {0x2192, 0x1A}, {0x2193, 0x19}, /* ← ↑ → ↓ */
|
||||
{0x2500, 0xC4}, {0x2502, 0xB3}, {0x250C, 0xDA}, {0x2510, 0xBF}, /* ─ │ ┌ ┐ */
|
||||
{0x2514, 0xC0}, {0x2518, 0xD9}, {0x251C, 0xC3}, {0x2524, 0xB4}, /* └ ┘ ├ ┤ */
|
||||
{0x252C, 0xC2}, {0x2534, 0xC1}, {0x253C, 0xC5}, /* ┬ ┴ ┼ */
|
||||
{0x2580, 0xDF}, {0x2584, 0xDC}, {0x2588, 0xDB}, /* ▀ ▄ █ */
|
||||
{0x2591, 0xB0}, {0x2592, 0xB1}, {0x2593, 0xB2}, {0x25A0, 0xFE}, /* ░ ▒ ▓ ■ */
|
||||
{0x2713, 0xFB}, {0x2714, 0xFB}, /* ✓ ✔ → галка */
|
||||
{0x2715, 0x78}, {0x2717, 0x78}, {0x2718, 0x78}, /* ✕ ✗ ✘ → 'x' */
|
||||
};
|
||||
#define UTF_SYM_N (uint8_t)(sizeof(utf_sym) / sizeof(utf_sym[0]))
|
||||
|
||||
/* Кодпойнт → ОДИН CP866-глиф: кириллица по таблице, ходовые символы по
|
||||
* utf_sym[], прочее → '?'. Общая для конвертера и HEX-printable (F4). */
|
||||
uint8_t utf_cp_glyph(uint32_t cp)
|
||||
{
|
||||
if (cp < 0x80) return (uint8_t)cp;
|
||||
if (cp >= 0x0400 && cp <= 0x045F) return utf_cyr_to_866[cp - 0x0400];
|
||||
for (uint8_t i = 0; i < UTF_SYM_N; i++)
|
||||
if (utf_sym[i].utf8 == (uint16_t)cp) return utf_sym[i].cp866;
|
||||
return '?';
|
||||
}
|
||||
|
||||
/* Кодпойнт → CP866-глиф(ы) в поток конвертера. Спецслучаи с НЕ-1:1 заменой
|
||||
* (… → "...", BOM → ничего) здесь; остальное через utf_cp_glyph(). */
|
||||
static void conv_emit_cp(uint32_t cp)
|
||||
{
|
||||
if (cp == 0x2026) { conv_put('.'); conv_put('.'); conv_put('.'); return; } /* … → "..." */
|
||||
if (cp == 0xFEFF) return; /* BOM/ZWNBSP — выкинуть */
|
||||
conv_put(utf_cp_glyph(cp));
|
||||
}
|
||||
|
||||
/* Последовательное чтение байта ОРИГИНАЛА (utf-8) конвертером. Оригинал лежит
|
||||
* в orig_file_phys[]; мапим его страницу в W3 напрямую, со своей кэш-переменной
|
||||
* cv_page (отдельной от cur_page индексатора, т.к. оба используют W3 и
|
||||
* чередуются). На границе с fb()-чтением utf-буфера кэши взаимно сбрасываются. */
|
||||
static uint8_t cv_read(uint32_t s)
|
||||
{
|
||||
uint8_t pg = (uint8_t)(s >> PAGE_BITS);
|
||||
if (pg != cv_page) { sprinter_page_w3(orig_file_phys[pg]); cv_page = pg; }
|
||||
return *((volatile uint8_t *)(0xC000u + (uint16_t)(s & PAGE_MASK)));
|
||||
}
|
||||
|
||||
/* Возобновляемая конвертация: дописывает utf_phys из оригинала, пока
|
||||
* сконвертированных (flushed) байт меньше target и не достигнут конец
|
||||
* исходника. По выходу file_size = доступная (сконвертированная) часть; при
|
||||
* достижении конца фиксирует utf_size и снимает g_utf_building. */
|
||||
void utf_convert_more(uint32_t target)
|
||||
{
|
||||
cv_page = 0xFF; /* W3 был на utf (fb индексатора) — пере-смаппим orig */
|
||||
uint32_t n = orig_file_size;
|
||||
while (utf_src < n &&
|
||||
(uint32_t)((uint32_t)conv_page * PAGE_SIZE + conv_off) < target) {
|
||||
uint8_t b = (uint8_t)cv_read(utf_src++);
|
||||
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 (utf_src >= n) { ok = 0; break; }
|
||||
uint8_t cb = (uint8_t)cv_read(utf_src);
|
||||
if ((cb & 0xC0) != 0x80) { ok = 0; break; }
|
||||
cp = (cp << 6) | (uint32_t)(cb & 0x3F);
|
||||
utf_src++;
|
||||
}
|
||||
if (!ok || need == 3) { conv_put('?'); continue; }
|
||||
conv_emit_cp(cp);
|
||||
}
|
||||
conv_flush();
|
||||
file_size = (uint32_t)conv_page * PAGE_SIZE + conv_off; /* доступно индексатору */
|
||||
if (utf_src >= n) { utf_size = file_size; g_utf_building = 0; }
|
||||
cur_page = 0xFF; /* W3 был на orig — fb индексатора пере-смаппит utf */
|
||||
}
|
||||
|
||||
/* Выделяет страницы под UTF-8 набор (конвертированный ≤ оригинала по размеру)
|
||||
* и сбрасывает приватное состояние конвертера в начало (conv-буфер, utf_src,
|
||||
* g_utf_building). Сама конвертация — инкрементальная, через utf_convert_more().
|
||||
* Возврат: 1 — успех, 0 — нет EMM. */
|
||||
uint8_t utf_alloc(void)
|
||||
{
|
||||
utf_pages = orig_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);
|
||||
conv_n = 0; conv_page = 0; conv_off = 0; /* converter reset (вызывается один раз) */
|
||||
utf_src = 0;
|
||||
g_utf_building = 1;
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* mdview2_help.c — диалог справки (F1).
|
||||
*
|
||||
* Модальное окно поверх документа: рамка с заголовком, список горячих
|
||||
* клавиш и поддерживаемых markdown-элементов. Блокирует до нажатия любой
|
||||
* клавиши, после чего восстанавливает статус-бар, область документа и меню.
|
||||
*
|
||||
* Зависимости от ядра (mdview2.c): render_full_status / draw_viewport_from_cache
|
||||
* / render_menu для восстановления экрана. Текст справки — в CP866.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <conio.h>
|
||||
#include <bios/text.h>
|
||||
#include "mdview2.h"
|
||||
|
||||
/* Геометрия диалога справки (в символьных координатах 80×32). */
|
||||
#define HELP_X 8u /* левая граница рамки */
|
||||
#define HELP_Y 3u /* верхняя граница рамки */
|
||||
#define HELP_W 64u /* ширина рамки (включая │) */
|
||||
#define HELP_H 26u /* высота рамки (включая ─) */
|
||||
|
||||
/* Заполняет одну внутреннюю строку диалога (r=0 — первая строка за рамкой).
|
||||
* Строка s в кодировке CP866; остаток до края дополняется пробелами. */
|
||||
static void help_line(uint8_t r, const char *s, uint8_t attr)
|
||||
{
|
||||
uint8_t x = HELP_X + 1u;
|
||||
uint8_t y = HELP_Y + 1u + r;
|
||||
uint8_t len = (uint8_t)strlen(s);
|
||||
|
||||
if (len > HELP_W - 2u)
|
||||
len = HELP_W - 2u;
|
||||
|
||||
bios_set_place(y, x);
|
||||
if (len > 0)
|
||||
bios_writeattr(s, len, attr);
|
||||
/* place уже продвинут bios_writeattr на len колонок (verified). */
|
||||
if (len < HELP_W - 2u) {
|
||||
bios_fillcharattr(' ', ATTR_HELP_BG, (uint8_t)(HELP_W - 2u - len));
|
||||
}
|
||||
}
|
||||
|
||||
void show_help(void)
|
||||
{
|
||||
/* " Помощь " в CP866 (8 байт) */
|
||||
static const char title[] = " \x8F\xAE\xAC\xAE\xE9\xEC ";
|
||||
uint8_t tlen = 8u;
|
||||
uint8_t lft = (uint8_t)((HELP_W - 2u - tlen) / 2u); /* = 27 */
|
||||
uint8_t rgt = (uint8_t)(HELP_W - 2u - tlen - lft);
|
||||
/* Верхняя граница рамки с заголовком по центру.
|
||||
* place продвигается каждым BIOS-вызовом (verified), поэтому
|
||||
* достаточно одной установки места на всю строку рамки. */
|
||||
wrchar(HELP_X, HELP_Y, 0xDA, ATTR_HELP_BDR); /* ┌ */
|
||||
bios_set_place(HELP_Y, (uint8_t)(HELP_X + 1u));
|
||||
if (lft > 0) bios_fillcharattr(0xC4, ATTR_HELP_BDR, lft);
|
||||
if (tlen > 0) bios_writeattr(title, tlen, ATTR_HELP_TIT);
|
||||
if (rgt > 0) bios_fillcharattr(0xC4, ATTR_HELP_BDR, rgt);
|
||||
wrchar(HELP_X + HELP_W - 1u, HELP_Y, 0xBF, ATTR_HELP_BDR); /* ┐ */
|
||||
|
||||
/* Боковые границы (левый и правый │ для каждой строки тела) */
|
||||
for (uint8_t r = 1u; r < HELP_H - 1u; r++) {
|
||||
wrchar(HELP_X, HELP_Y + r, 0xB3, ATTR_HELP_BDR); /* │ */
|
||||
wrchar(HELP_X + HELP_W - 1u, HELP_Y + r, 0xB3, ATTR_HELP_BDR); /* │ */
|
||||
}
|
||||
|
||||
/* Нижняя граница рамки */
|
||||
wrchar(HELP_X, HELP_Y + HELP_H - 1u, 0xC0, ATTR_HELP_BDR); /* └ */
|
||||
bios_set_place((uint8_t)(HELP_Y + HELP_H - 1u), (uint8_t)(HELP_X + 1u));
|
||||
bios_fillcharattr(0xC4, ATTR_HELP_BDR, (uint8_t)(HELP_W - 2u));
|
||||
wrchar(HELP_X + HELP_W - 1u, HELP_Y + HELP_H - 1u, 0xD9, ATTR_HELP_BDR); /* ┘ */
|
||||
|
||||
/* Содержимое (20 внутренних строк) */
|
||||
uint8_t r = 0;
|
||||
help_line(r++, "", ATTR_HELP_BG);
|
||||
help_line(r++, " MDView v1.0 (b3) -- Markdown Viewer for Sprinter", ATTR_HELP_HDR);
|
||||
help_line(r++, " (c) 2026 \x8F\xA5\xE2\xE0\xAE\xA2 \x80\x2E\x83\x2E",
|
||||
ATTR_HELP_BG);
|
||||
help_line(r++, "", ATTR_HELP_BG);
|
||||
help_line(r++, " Navigation:", ATTR_HELP_HDR);
|
||||
help_line(r++, " \x18 \x19 Scroll one line up / down", ATTR_HELP_BG);
|
||||
help_line(r++, " PgUp PgDn Scroll one page up / down", ATTR_HELP_BG);
|
||||
help_line(r++, " Home End Jump to beginning / end of document", ATTR_HELP_BG);
|
||||
help_line(r++, " \x1B \x1A Horizontal pan (code blocks/tables/unwrap)", ATTR_HELP_BG);
|
||||
help_line(r++, " Esc F10 Exit", ATTR_HELP_BG);
|
||||
help_line(r++, "", ATTR_HELP_BG);
|
||||
help_line(r++, " Markdown elements:", ATTR_HELP_HDR);
|
||||
help_line(r++, " # ## ### Headings H1-H6, **bold**, *italic*,", ATTR_HELP_BG);
|
||||
help_line(r++, " `code`, ``` code ```, ~~strike~~, > quote", ATTR_HELP_BG);
|
||||
help_line(r++, " - * + 1. 2. Ordered list, |----|----| Tables", ATTR_HELP_BG);
|
||||
help_line(r++, "", ATTR_HELP_BG);
|
||||
help_line(r++, " Encoding and View modes:", ATTR_HELP_HDR);
|
||||
help_line(r++, " F2 RAW Mode", ATTR_HELP_BG);
|
||||
help_line(r++, " F3 Wrap/Unwrap Mode for RAW View", ATTR_HELP_BG);
|
||||
help_line(r++, " F4 HEX Mode (dump of original file)", ATTR_HELP_BG);
|
||||
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 256 KB (EMM). Lines: up to 18432.", ATTR_HELP_BG);
|
||||
|
||||
(void)getkey();
|
||||
|
||||
render_full_status();
|
||||
draw_viewport_from_cache();
|
||||
render_menu();
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* mdview2_hex.c — HEX-дамп ОРИГИНАЛЬНОГО файла (F4).
|
||||
*
|
||||
* Показывает исходные байты файла (orig_file_phys), а не активный буфер:
|
||||
* hex-дамп декодированного UTF-набора был бы искажением. Printable-колонка
|
||||
* интерпретируется ТЕКУЩЕЙ кодировкой: CP866 как есть, CP1251/KOI8 через
|
||||
* g_remap, UTF-8 — глиф на позиции лид-байта (continuation-байты → '.').
|
||||
*
|
||||
* Формат ряда (79 колонок, один атрибут ATTR_TEXT — как RAW):
|
||||
* 0x012340 │ XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX │ 16 символов
|
||||
*
|
||||
* Ряд выровнен на 16 байт и никогда не пересекает границу EMM-страницы
|
||||
* (16 делит 16384 нацело) — один bank_read на ряд, fb()/W3-маппинг не нужны.
|
||||
* Позиция (hex_top) живёт в байтах ОРИГИНАЛА; конвертацию в/из байт-позиции
|
||||
* активного буфера делает ядро (view_pos/view_reanchor через map_off).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <conio.h>
|
||||
#include <bios/text.h>
|
||||
#include <sprinter_mem.h> /* bank_read (чтение оригинала по страницам EMM) */
|
||||
#include "mdview2.h"
|
||||
|
||||
#if WITH_HEX /* весь модуль опционален (mdview2_conf.h); при 0 пустой объектник */
|
||||
|
||||
#define HEX_BPR 16u /* байт на ряд */
|
||||
#define HEX_SPAN ((uint32_t)(VIEW_H - 1) * HEX_BPR)
|
||||
|
||||
/* Геометрия ряда (колонки). */
|
||||
#define HX_OFF 1u /* "0x012340" */
|
||||
#define HX_SEP1 10u /* │ */
|
||||
#define HX_DUMP 12u /* пары hex-цифр */
|
||||
#define HX_SEP2 61u /* │ */
|
||||
#define HX_PRINT 63u /* printable */
|
||||
|
||||
static uint32_t hex_top; /* offset верхнего ряда (кратен 16) */
|
||||
|
||||
uint32_t hex_pos(void) { return hex_top; }
|
||||
|
||||
/* Максимальный hex_top: последний ряд файла на нижней строке экрана. */
|
||||
static uint32_t hex_max_top(void)
|
||||
{
|
||||
uint32_t last = (orig_file_size == 0) ? 0
|
||||
: ((orig_file_size - 1u) & ~(uint32_t)(HEX_BPR - 1u));
|
||||
return (last > HEX_SPAN) ? (last - HEX_SPAN) : 0;
|
||||
}
|
||||
|
||||
void hex_reanchor(uint32_t orig_off)
|
||||
{
|
||||
uint32_t mt = hex_max_top();
|
||||
orig_off &= ~(uint32_t)(HEX_BPR - 1u);
|
||||
hex_top = (orig_off > mt) ? mt : orig_off;
|
||||
}
|
||||
|
||||
void hex_screen_init(void)
|
||||
{
|
||||
bios_clearwin(VIEW_TOP_ROW, 0, VIEW_H, SCREEN_W, ATTR_TEXT);
|
||||
}
|
||||
|
||||
/* ---- отрисовка ----------------------------------------------------- */
|
||||
|
||||
static uint8_t hexdig(uint8_t v)
|
||||
{
|
||||
v &= 15u;
|
||||
return (uint8_t)((v < 10u) ? ('0' + v) : ('A' - 10 + v));
|
||||
}
|
||||
|
||||
/* Один байт оригинала (для UTF-lookahead за границей ряда — редкий путь). */
|
||||
static uint8_t fbo(uint32_t off)
|
||||
{
|
||||
uint8_t b;
|
||||
bank_read(orig_file_phys[off >> PAGE_BITS], (uint16_t)(off & PAGE_MASK), &b, 1);
|
||||
return b;
|
||||
}
|
||||
|
||||
/* Printable-глиф для n байт ряда b[] (off — offset b[0] в файле) → out[]. */
|
||||
static void hex_print_cells(const uint8_t *b, uint32_t off, uint8_t n, uint8_t *out)
|
||||
{
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
uint8_t c = b[i];
|
||||
uint8_t g;
|
||||
if (c < 0x20u) {
|
||||
g = '.';
|
||||
} else if (c < 0x80u || g_encoding != ENC_UTF8) {
|
||||
g = (uint8_t)((c >= 0x80u && g_remap) ? g_remap[c - 0x80u] : c);
|
||||
} else if ((c & 0xC0u) == 0x80u) {
|
||||
g = '.'; /* continuation-байт */
|
||||
} else {
|
||||
/* Лид-байт: собрать кодпойнт (хвост может выйти за ряд → fbo).
|
||||
* 4-байтовые (за пределами CP866) и битые последовательности → '.'. */
|
||||
uint32_t cp;
|
||||
uint8_t need, ok = 1;
|
||||
if ((c & 0xE0u) == 0xC0u) { cp = (uint32_t)(c & 0x1Fu); need = 1; }
|
||||
else if ((c & 0xF0u) == 0xE0u) { cp = (uint32_t)(c & 0x0Fu); need = 2; }
|
||||
else { out[i] = '.'; continue; }
|
||||
for (uint8_t k = 1; k <= need; k++) {
|
||||
uint32_t p = off + i + k;
|
||||
if (p >= orig_file_size) { ok = 0; break; }
|
||||
uint8_t cb = (uint8_t)((i + k < n) ? b[i + k] : fbo(p));
|
||||
if ((cb & 0xC0u) != 0x80u) { ok = 0; break; }
|
||||
cp = (cp << 6) | (uint32_t)(cb & 0x3Fu);
|
||||
}
|
||||
g = ok ? utf_cp_glyph(cp) : '.';
|
||||
if (g < 0x20u) g = '.'; /* overlong мог дать управляющий */
|
||||
}
|
||||
out[i] = g;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t hex_line[SCREEN_W]; /* собранный ряд (только символы, единый attr) */
|
||||
|
||||
static void hex_draw_row(uint32_t off, uint8_t row)
|
||||
{
|
||||
uint8_t buf[HEX_BPR];
|
||||
uint8_t n = 0;
|
||||
|
||||
memset(hex_line, ' ', SCREEN_W);
|
||||
if (off < orig_file_size) {
|
||||
uint32_t rem = orig_file_size - off;
|
||||
n = (rem >= HEX_BPR) ? (uint8_t)HEX_BPR : (uint8_t)rem;
|
||||
bank_read(orig_file_phys[off >> PAGE_BITS], (uint16_t)(off & PAGE_MASK), buf, n);
|
||||
}
|
||||
if (n) { /* ряды за EOF — пустые */
|
||||
hex_line[HX_OFF] = '0';
|
||||
hex_line[HX_OFF + 1] = 'x';
|
||||
hex_line[HX_OFF + 2] = hexdig((uint8_t)(off >> 20));
|
||||
hex_line[HX_OFF + 3] = hexdig((uint8_t)(off >> 16));
|
||||
hex_line[HX_OFF + 4] = hexdig((uint8_t)(off >> 12));
|
||||
hex_line[HX_OFF + 5] = hexdig((uint8_t)(off >> 8));
|
||||
hex_line[HX_OFF + 6] = hexdig((uint8_t)(off >> 4));
|
||||
hex_line[HX_OFF + 7] = hexdig((uint8_t)off);
|
||||
hex_line[HX_SEP1] = TBL_V;
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
uint8_t x = (uint8_t)(HX_DUMP + i * 3u + ((i >= 8u) ? 1u : 0u));
|
||||
hex_line[x] = hexdig((uint8_t)(buf[i] >> 4));
|
||||
hex_line[x + 1] = hexdig(buf[i]);
|
||||
}
|
||||
hex_line[HX_SEP2] = TBL_V;
|
||||
hex_print_cells(buf, off, n, hex_line + HX_PRINT);
|
||||
}
|
||||
bios_set_place(row, 0);
|
||||
bios_write_until((const char *)hex_line, SCREEN_W, 0);
|
||||
}
|
||||
|
||||
void hex_draw(void)
|
||||
{
|
||||
uint32_t off = hex_top;
|
||||
for (uint8_t r = 0; r < VIEW_H; r++) {
|
||||
hex_draw_row(off, (uint8_t)(VIEW_TOP_ROW + r));
|
||||
off += HEX_BPR;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- статус-бар: % по байтам (как calc_raw_pct) --------------------- */
|
||||
|
||||
static uint8_t calc_hex_pct(void)
|
||||
{
|
||||
return pct16((uint16_t)(hex_top >> 8), (uint16_t)(orig_file_size >> 8));
|
||||
}
|
||||
|
||||
/* ---- навигация (вызывается из главного цикла при g_view == VIEW_HEX) */
|
||||
|
||||
uint8_t hex_key(uint8_t scan)
|
||||
{
|
||||
uint32_t mt = hex_max_top();
|
||||
switch (scan) {
|
||||
/* Одна строка — аппаратный скролл окна + подрисовка одного ряда
|
||||
* (как в MD/RAW), без перерисовки всего экрана. */
|
||||
case KEY_DOWN:
|
||||
if (hex_top < mt) {
|
||||
hex_top += HEX_BPR;
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0); /* содержимое вверх */
|
||||
hex_draw_row(hex_top + HEX_SPAN, (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
|
||||
}
|
||||
break;
|
||||
case KEY_UP:
|
||||
if (hex_top) {
|
||||
hex_top -= HEX_BPR;
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0); /* содержимое вниз */
|
||||
hex_draw_row(hex_top, VIEW_TOP_ROW);
|
||||
}
|
||||
break;
|
||||
case KEY_PGDN: {
|
||||
uint32_t nt = hex_top + (uint32_t)VIEW_H * HEX_BPR;
|
||||
if (nt > mt) nt = mt;
|
||||
if (nt != hex_top) { hex_top = nt; hex_draw(); }
|
||||
break;
|
||||
}
|
||||
case KEY_PGUP: {
|
||||
uint32_t d = (uint32_t)VIEW_H * HEX_BPR;
|
||||
uint32_t nt = (hex_top >= d) ? hex_top - d : 0;
|
||||
if (nt != hex_top) { hex_top = nt; hex_draw(); }
|
||||
break;
|
||||
}
|
||||
case KEY_HOME:
|
||||
if (hex_top) { hex_top = 0; hex_draw(); }
|
||||
break;
|
||||
case KEY_END:
|
||||
if (hex_top != mt) { hex_top = mt; hex_draw(); }
|
||||
break;
|
||||
default:
|
||||
return 0; /* не наша клавиша */
|
||||
}
|
||||
render_percent_progress(calc_hex_pct());
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* WITH_HEX */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* mdview2_md.c — представление MD-документа (peer к mdview2_raw.c): отрисовка
|
||||
* области из рендер-кэша + прокрутка (верт./гориз.) + навигация md_key().
|
||||
* Статус-бар, меню и спиннер — в mdview2_status.c (общие для MD/RAW).
|
||||
*
|
||||
* Работает поверх готового рендер-кэша (cache_dir_get → cache_rec_t) и
|
||||
* состояния навигации (top_line/viewport_x/n_lines, см. mdview2.h). Никакого
|
||||
* парсинга/обращения к исходному файлу: всё рисуется из (char,attr)-ячеек кэша
|
||||
* через win_rest_remap (ремап 8-битных кодировок — в mdview2_enc.c).
|
||||
*
|
||||
* Пара видов: mdview2_md.c (MD, из кэша) ↔ mdview2_raw.c (RAW, из fb()).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <conio.h> /* wrchar/scroll/COLOR */
|
||||
#include <bios/text.h> /* bios_set_place/bios_fillcharattr */
|
||||
#include "mdview2.h"
|
||||
|
||||
/* Фаза 4-5 — cache-only draw path: рисует одну строку ИЗ КЭША, без
|
||||
* единого обращения к fb()/исходному файлу. Горизонтальный скролл для
|
||||
* nowrap-строк (Фаза 5) — это просто смещение начала среза на vx*2
|
||||
* байт ВНУТРИ ТОГО ЖЕ кэш-буфера (cache_reserve гарантирует, что вся
|
||||
* строка лежит в одной странице, так что off+vx*2 не пересекает
|
||||
* границу страницы); не nowrap-строки игнорируют viewport_x, как и
|
||||
* раньше в живом render_line() (там это называлось effective_vx). */
|
||||
static void draw_line_from_cache(uint16_t line_idx, uint8_t row)
|
||||
{
|
||||
cache_rec_t rec;
|
||||
cache_dir_get(line_idx, &rec);
|
||||
|
||||
/* Строка-обрыв (исчерпан контент-кэш / лимит строк): фикс. текст вживую,
|
||||
* без обращения к контент-кэшу (см. index_lines финализацию). */
|
||||
if (rec.flags & IF_TRUNC_MSG) {
|
||||
const char *msg;
|
||||
switch (rec.reserved) {
|
||||
case TRUNC_LINES: msg = "*** Line limit reached - document truncated ***"; break;
|
||||
case TRUNC_FILE: msg = "*** File too large - truncated at 256 KB ***"; break;
|
||||
default: msg = "*** Content cache exhausted - document truncated ***"; break;
|
||||
}
|
||||
bios_set_place(row, 0);
|
||||
bios_fillcharattr(' ', ATTR_WARN, SCREEN_W);
|
||||
put_str_attr(2, row, msg, ATTR_WARN);
|
||||
return;
|
||||
}
|
||||
|
||||
bios_set_place(row, 0);
|
||||
bios_fillcharattr(' ', ATTR_TEXT, SCREEN_W);
|
||||
|
||||
/* Горизонтальный сдвиг применяется ко ВСЕМ строкам hscroll-блока (код,
|
||||
* таблицы) — по ТИПУ, а не по длине: блок двигается целиком, включая
|
||||
* строки короче 80 (короткая строка, ушедшая за левый край, просто
|
||||
* показывает маркер '<' на пустом месте). HR и границы fence не имеют
|
||||
* IF_HSCROLL и не сдвигаются. */
|
||||
uint8_t vx = (rec.flags & IF_HSCROLL) ? viewport_x : 0;
|
||||
|
||||
if (rec.len > vx) {
|
||||
uint16_t remaining = (uint16_t)(rec.len - vx);
|
||||
uint8_t w = (remaining > SCREEN_W) ? SCREEN_W : (uint8_t)remaining;
|
||||
uint16_t off = (uint16_t)(rec.off + (uint16_t)vx * 2u);
|
||||
win_rest_remap(row, w, rec.page, off);
|
||||
if (remaining > SCREEN_W) {
|
||||
wrchar(SCREEN_W - 1, row, '>', ATTR_TRUNC);
|
||||
}
|
||||
}
|
||||
if (vx > 0) {
|
||||
wrchar(0, row, '<', ATTR_TRUNC);
|
||||
}
|
||||
}
|
||||
|
||||
/* Перерисовывает все VIEW_H строк окна из кэша — основной путь
|
||||
* перерисовки видимой области после загрузки файла (Фаза 3-4). */
|
||||
void draw_viewport_from_cache(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||||
draw_line_from_cache((uint16_t)(top_line + i), (uint8_t)(VIEW_TOP_ROW + i));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ==================================================================
|
||||
* Прокрутка
|
||||
* ================================================================== */
|
||||
|
||||
/* Ограничивает top_line допустимым диапазоном с учётом высоты окна. */
|
||||
void clamp_top(void)
|
||||
{
|
||||
if (n_lines <= VIEW_H) {
|
||||
top_line = 0;
|
||||
} else if (top_line > n_lines - VIEW_H) {
|
||||
top_line = (uint16_t)(n_lines - VIEW_H);
|
||||
}
|
||||
}
|
||||
|
||||
/* Прокрутка вверх на n строк с частичной перерисовкой при n == 1. */
|
||||
void md_scroll_up(uint16_t n)
|
||||
{
|
||||
uint16_t new_top_line = (top_line >= n) ? (uint16_t)(top_line - n) : 0;
|
||||
if( new_top_line != top_line) {
|
||||
top_line = new_top_line;
|
||||
if (n == 1) {
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0);
|
||||
draw_line_from_cache((uint16_t)(top_line), (uint8_t)(VIEW_TOP_ROW));
|
||||
} else {
|
||||
draw_viewport_from_cache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Прокрутка вниз на n строк с частичной перерисовкой при n == 1. */
|
||||
void md_scroll_down(uint16_t n)
|
||||
{
|
||||
uint16_t new_top_line = (top_line + n < n_lines - VIEW_H) ?
|
||||
(uint16_t)(top_line + n) : n_lines - VIEW_H;
|
||||
if( new_top_line != top_line) {
|
||||
top_line = new_top_line;
|
||||
if (n == 1) {
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0);
|
||||
draw_line_from_cache((uint16_t)(top_line + VIEW_H - 1), (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
|
||||
} else {
|
||||
clamp_top();
|
||||
draw_viewport_from_cache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Горизонтальный сдвиг (только если в окне есть nowrap-строки).
|
||||
* Максимум сдвига ограничен самой широкой nowrap-строкой на экране.
|
||||
* Фаза 5: ширина берётся прямо из директории кэша (rec.len — уже
|
||||
* посчитанная реальная длина в ячейках после рендера), без единого
|
||||
* обращения к fb()/seg_off()/исходному файлу. */
|
||||
void md_scroll_horizon(int8_t delta)
|
||||
{
|
||||
uint16_t maxw = 0;
|
||||
for (uint8_t i = 0; i < VIEW_H; i++) {
|
||||
uint16_t li = (uint16_t)(top_line + i);
|
||||
if (li >= n_lines) break;
|
||||
cache_rec_t rec;
|
||||
cache_dir_get(li, &rec);
|
||||
if (!(rec.flags & IF_HSCROLL)) continue; /* только код/таблицы */
|
||||
if (rec.len > maxw) maxw = rec.len; /* самая широкая строка блока */
|
||||
}
|
||||
if (maxw == 0) return; /* нет строк шире экрана */
|
||||
|
||||
/* Максимальный сдвиг = ширина за пределами экрана, в границах uint8. */
|
||||
uint16_t over = (maxw > SCREEN_W) ? (uint16_t)(maxw - SCREEN_W) : 0;
|
||||
if (over > 248u) over = 248u;
|
||||
uint8_t max_vx = (uint8_t)over;
|
||||
|
||||
int16_t nx = (int16_t)viewport_x + delta;
|
||||
if (nx < 0) nx = 0;
|
||||
if (nx > (int16_t)max_vx) nx = max_vx;
|
||||
|
||||
/* ПОРЯДОК ВАЖЕН: записываем viewport_x ДО сравнения, а сравниваем уже
|
||||
* сохранённую копию old_vx с new_vx. Иначе SDCC генерирует для
|
||||
* `if (new_vx != viewport_x) viewport_x = new_vx;` такой код:
|
||||
* ld a, new_vx ; sub a,(viewport_x) ; jr Z,.. ; ld (viewport_x),a
|
||||
* — т.е. для записи переиспользует регистр A, испорченный вычитанием в
|
||||
* сравнении, и кладёт в viewport_x не new_vx, а (new_vx - old_vx).
|
||||
* (asm-дамп 2026-06-24 подтвердил; даёт 10-8=2, 10-2=8 — ровно
|
||||
* наблюдавшийся "прыжок" скролла.) Записав viewport_x первой, мы убираем
|
||||
* портящий sub с пути записи; сравнение ниже только решает, перерисовывать
|
||||
* ли экран. */
|
||||
uint8_t new_vx = (uint8_t)nx;
|
||||
uint8_t old_vx = viewport_x;
|
||||
viewport_x = new_vx;
|
||||
if (new_vx != old_vx)
|
||||
draw_viewport_from_cache();
|
||||
}
|
||||
|
||||
/* Процент прокрутки (0..100) относительно доступного диапазона. */
|
||||
static uint8_t calc_md_pct(void)
|
||||
{
|
||||
uint16_t total = drawable_lines();
|
||||
if (total <= VIEW_H) return 100;
|
||||
return pct16(top_line, (uint16_t)(total - VIEW_H));
|
||||
}
|
||||
|
||||
/* ---- навигация MD после загрузки (peer к raw_key) ---------------- */
|
||||
|
||||
/* Обрабатывает навигационную клавишу в MD-режиме (вызывается из главного
|
||||
* цикла). Сама перерисовывает область/статус. Возврат: 1 — обработано,
|
||||
* 0 — не наша клавиша (F1/F8/F10 разбирает main). */
|
||||
uint8_t md_key(uint8_t scan)
|
||||
{
|
||||
switch (scan) {
|
||||
case KEY_UP:
|
||||
md_scroll_up(1);
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
md_scroll_down(1);
|
||||
break;
|
||||
case KEY_LEFT:
|
||||
md_scroll_horizon(-(int8_t)HPAN_STEP);
|
||||
break;
|
||||
case KEY_RIGHT:
|
||||
md_scroll_horizon(+(int8_t)HPAN_STEP);
|
||||
break;
|
||||
case KEY_PGUP:
|
||||
md_scroll_up(VIEW_H);
|
||||
break;
|
||||
case KEY_PGDN:
|
||||
md_scroll_down(VIEW_H);
|
||||
break;
|
||||
case KEY_HOME:
|
||||
if(top_line != 0 || viewport_x != 0 ) {
|
||||
top_line = 0;
|
||||
viewport_x = 0;
|
||||
draw_viewport_from_cache();
|
||||
}
|
||||
break;
|
||||
case KEY_END:
|
||||
uint16_t new_top_line = (n_lines > VIEW_H) ? (uint16_t)(n_lines - VIEW_H) : 0;
|
||||
if(top_line != new_top_line || viewport_x != 0 ) {
|
||||
top_line = new_top_line;
|
||||
viewport_x = 0;
|
||||
draw_viewport_from_cache();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0; /* не наша клавиша */
|
||||
}
|
||||
|
||||
render_percent_progress(calc_md_pct());
|
||||
render_md_status_numbers();
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* mdview2_raw.c — RAW-просмотр исходного текста (без markdown-форматирования).
|
||||
*
|
||||
* Работает по АКТИВНОМУ буферу документа через fb() (8-бит = исходные байты,
|
||||
* UTF-8 = декодированный в CP866 буфер), ремап ≥0x80 (CP1251/KOI8) — на
|
||||
* отрисовке. Индекс/кэш markdown не используются: позиция — байт-offset,
|
||||
* 1 байт = 1 ячейка, \t показываем пробелом. Два режима:
|
||||
* VIEW_RAW_WRAP — длинные строки переносятся кратно 80 (рвём слова);
|
||||
* VIEW_RAW_HSCROLL — строка в один ряд, горизонтальный скролл.
|
||||
*
|
||||
* Память: 0 доп. EMM; near-буфер строки + переиспользование g_scratch_phys.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <bios/text.h>
|
||||
#include "mdview2.h"
|
||||
|
||||
#if WITH_RAW /* весь модуль — опционален (mdview_conf.h); при 0 пустой объектник */
|
||||
|
||||
static uint32_t raw_top; /* байт-offset верхней строки экрана */
|
||||
static uint16_t raw_hpan; /* гориз. сдвиг (только HSCROLL) */
|
||||
|
||||
/* ---- примитивы навигации по физическим строкам / переносам -------- */
|
||||
|
||||
static uint32_t raw_line_start(uint32_t off)
|
||||
{
|
||||
while (off && fb(off - 1) != '\n') off--;
|
||||
return off;
|
||||
}
|
||||
|
||||
/* WRAP: начало следующего ряда от off (\n завершает строку; иначе +80). */
|
||||
static uint32_t raw_next_wrap(uint32_t off)
|
||||
{
|
||||
uint32_t e = off, lim = off + SCREEN_W;
|
||||
while (e < file_size) {
|
||||
if (fb(e) == '\n') return e + 1;
|
||||
e++;
|
||||
if (e >= lim) { /* прошли 80 без \n */
|
||||
if (e < file_size && fb(e) == '\n') return e + 1; /* ровно 80 + \n */
|
||||
return e; /* длинная строка → продолжение */
|
||||
}
|
||||
}
|
||||
return e; /* EOF */
|
||||
}
|
||||
|
||||
static uint32_t raw_prev_wrap(uint32_t off)
|
||||
{
|
||||
if (off == 0) return 0;
|
||||
uint32_t pls = raw_line_start(off - 1); /* off-1 — это \n пред. строки */
|
||||
while (pls + SCREEN_W < (off - 1)) pls += SCREEN_W;
|
||||
return pls;
|
||||
}
|
||||
|
||||
/* HSCROLL: начало следующей/предыдущей физической строки. */
|
||||
static uint32_t raw_next_line(uint32_t off)
|
||||
{
|
||||
while (off < file_size && fb(off) != '\n') off++;
|
||||
return (off < file_size) ? off + 1 : off;
|
||||
}
|
||||
|
||||
static uint32_t raw_prev_line(uint32_t off)
|
||||
{
|
||||
if (off == 0) return 0;
|
||||
return raw_line_start(off - 1);
|
||||
}
|
||||
|
||||
/* ---- отрисовка ряда (общий near-буфер → g_scratch_phys → win_rest) - */
|
||||
|
||||
static uint8_t raw_buf[SCREEN_W + 1]; /* только символы (атрибут единый, задаётся при выводе) */
|
||||
|
||||
/* В RAW атрибут единый (ATTR_TEXT): буфер — только 80 символов, без пар
|
||||
* (char,attr). Кладём один байт-глиф (\t→пробел, ремап ≥0x80). */
|
||||
static void raw_cell(uint8_t i, uint8_t ch)
|
||||
{
|
||||
if (ch >= 0x80 && g_remap) ch = g_remap[ch - 0x80];
|
||||
raw_buf[i] = ch;
|
||||
}
|
||||
/* Вывод готового 80-символьного буфера в строку экрана одним BIOS-вызовом
|
||||
* (char-буфер + единый attr — без WINREST/scratch-страницы). */
|
||||
static void raw_flush(uint8_t row)
|
||||
{
|
||||
bios_set_place(row, 0);
|
||||
// bios_writeattr((const char *)raw_buf, SCREEN_W, ATTR_TEXT);
|
||||
// bios_writeattr_until((const char *)raw_buf, SCREEN_W, ATTR_TEXT, 0);
|
||||
bios_write_until((const char *)raw_buf, SCREEN_W, 0);
|
||||
}
|
||||
|
||||
static uint32_t raw_draw_wrap_row(uint32_t off, uint8_t row)
|
||||
{
|
||||
uint8_t i = 0;
|
||||
uint32_t e = off, lim = off + SCREEN_W;
|
||||
while (i < SCREEN_W && e < file_size) {
|
||||
char c = fb(e);
|
||||
if (c == '\n') {
|
||||
raw_cell(i, 0);
|
||||
break;
|
||||
}
|
||||
raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c));
|
||||
i++; e++;
|
||||
}
|
||||
raw_flush(row);
|
||||
return raw_next_wrap(off);
|
||||
}
|
||||
|
||||
static uint32_t raw_draw_hscroll_row(uint32_t off, uint8_t row)
|
||||
{
|
||||
uint32_t e = off;
|
||||
while (e < file_size && fb(e) != '\n') e++; /* e = конец строки */
|
||||
uint16_t len = e - off;
|
||||
|
||||
uint8_t i = 0;
|
||||
uint32_t p = off + raw_hpan;
|
||||
while (i < SCREEN_W && p < e) {
|
||||
char c = fb(p);
|
||||
raw_cell(i, (uint8_t)(c == '\t' ? ' ' : c));
|
||||
i++; p++;
|
||||
}
|
||||
// raw_buf[i] = 0;
|
||||
|
||||
if (raw_hpan > 0)
|
||||
raw_buf[0] = '<';
|
||||
if (len > (uint16_t)raw_hpan + SCREEN_W)
|
||||
raw_buf[SCREEN_W - 1] = '>';
|
||||
if (i < SCREEN_W)
|
||||
raw_buf[i] = 0;
|
||||
raw_flush(row);
|
||||
return (e < file_size) ? e + 1 : e;
|
||||
}
|
||||
|
||||
/* Рисует один ряд от off (по режиму) и возвращает offset следующего ряда. */
|
||||
static uint32_t raw_draw_one(uint32_t off, uint8_t row)
|
||||
{
|
||||
if (off >= file_size) { fill_row(row, ATTR_TEXT); return off; }
|
||||
return (g_view == VIEW_RAW_WRAP) ? raw_draw_wrap_row(off, row)
|
||||
: raw_draw_hscroll_row(off, row);
|
||||
}
|
||||
|
||||
void raw_draw(void)
|
||||
{
|
||||
uint32_t off = raw_top;
|
||||
for (uint8_t r = 0; r < VIEW_H; r++)
|
||||
off = raw_draw_one(off, (uint8_t)(VIEW_TOP_ROW + r));
|
||||
}
|
||||
|
||||
/* Прокрутка на одну строку: аппаратный scroll окна документа + отрисовка
|
||||
* ОДНОЙ новой строки (как в MD-режиме). Смещение нижнего ряда вычисляется
|
||||
* проходом VIEW_H рядов от raw_top — без отдельного инкрементального счётчика,
|
||||
* который рассинхронизировался, когда контент кончается в середине экрана
|
||||
* (короче окна после wrap→unwrap у конца файла). */
|
||||
static uint32_t raw_next(uint32_t off)
|
||||
{
|
||||
return (g_view == VIEW_RAW_WRAP) ? raw_next_wrap(off) : raw_next_line(off);
|
||||
}
|
||||
static uint32_t raw_prev(uint32_t off)
|
||||
{
|
||||
return (g_view == VIEW_RAW_WRAP) ? raw_prev_wrap(off) : raw_prev_line(off);
|
||||
}
|
||||
|
||||
/* ---- статус-бар: % по байтам (отрисовка — render_raw_status_numbers
|
||||
* в mdview2_status.c, читает позицию через этот raw_pct) ---- */
|
||||
|
||||
/* Процент по байтам без 32-битного деления: масштабируем offset'ы (>>8 →
|
||||
* ≤1024), дальше 16-битный pct16 (без __divulong). raw_top приватен модулю,
|
||||
* поэтому % считаем здесь и отдаём готовым числом в статус-модуль. */
|
||||
uint8_t calc_raw_pct(void)
|
||||
{
|
||||
uint16_t a = (uint16_t)(raw_top >> 8);
|
||||
uint16_t b = (uint16_t)(file_size >> 8);
|
||||
return pct16(a, b); /* den==0 (файл < 1 КБ) → pct16 вернёт 0 */
|
||||
}
|
||||
|
||||
/* ---- посев позиции / пересев при смене активного буфера ----------- */
|
||||
|
||||
uint32_t raw_pos(void)
|
||||
{
|
||||
return raw_top;
|
||||
}
|
||||
|
||||
/* Поставить RAW на начало строки, содержащей байт off (позиционирование
|
||||
* при F2 MD→RAW и F8-переносе позиции между наборами). */
|
||||
void raw_reanchor(uint32_t off)
|
||||
{
|
||||
if (off > file_size) off = file_size;
|
||||
raw_top = raw_line_start(off);
|
||||
raw_hpan = 0;
|
||||
}
|
||||
|
||||
void raw_seed_from(uint16_t md_top_line)
|
||||
{
|
||||
raw_reanchor(seg_off(md_top_line));
|
||||
}
|
||||
|
||||
/* Сброс в начало (клавиша Home). */
|
||||
static void raw_home(void)
|
||||
{
|
||||
raw_top = 0;
|
||||
raw_hpan = 0;
|
||||
}
|
||||
|
||||
void raw_end(void)
|
||||
{
|
||||
raw_top = file_size; /* отмотать VIEW_H рядов назад */
|
||||
for (uint8_t k = 0; k < VIEW_H; k++)
|
||||
raw_top = raw_prev(raw_top);
|
||||
raw_hpan = 0;
|
||||
}
|
||||
|
||||
void raw_screen_init(void) {
|
||||
bios_clearwin(1, 0, 30, 80, ATTR_TEXT);
|
||||
}
|
||||
|
||||
/* Нормализация позиции при смене под-режима RAW (F3 Wrap/Unwrap): выровнять
|
||||
* raw_top на начало физической строки и снять горизонтальный сдвиг. */
|
||||
void raw_renorm(void)
|
||||
{
|
||||
raw_top = raw_line_start(raw_top);
|
||||
raw_hpan = 0;
|
||||
}
|
||||
|
||||
void raw_scroll_down(uint8_t delta) {
|
||||
uint32_t off = raw_top;
|
||||
for (uint8_t r = 0; r < VIEW_H; r++) {
|
||||
if (off >= file_size)
|
||||
return; /* экран не заполнен контентом */
|
||||
off = raw_next(off);
|
||||
}
|
||||
if (off >= file_size)
|
||||
return; /* контент ровно по экран, ниже пусто */
|
||||
|
||||
if(delta ==1) {
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0); /* содержимое вверх */
|
||||
off = raw_draw_one(off, (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1)); /* новый нижний ряд */
|
||||
raw_top = raw_next(raw_top);
|
||||
} else {
|
||||
for (uint8_t k = 0; k < delta; k++) {
|
||||
uint32_t n2 = raw_next(raw_top);
|
||||
if (n2 >= file_size)
|
||||
break;
|
||||
raw_top = n2;
|
||||
}
|
||||
raw_draw(); /* PgUp/PgDn/Home/End — полный экран */
|
||||
}
|
||||
}
|
||||
|
||||
void raw_scroll_up(uint8_t delta) {
|
||||
if (raw_top == 0)
|
||||
return;
|
||||
|
||||
if (delta == 1) {
|
||||
raw_top = raw_prev(raw_top);
|
||||
scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0); /* содержимое вниз */
|
||||
(void)raw_draw_one(raw_top, VIEW_TOP_ROW);
|
||||
} else {
|
||||
for (uint8_t k = 0; k < delta; k++)
|
||||
raw_top = raw_prev(raw_top);
|
||||
raw_draw(); /* PgUp/PgDn/Home/End — полный экран */
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- навигация (вызывается из главного цикла при g_view != VIEW_MD) - */
|
||||
|
||||
uint8_t raw_key(uint8_t scan)
|
||||
{
|
||||
switch (scan) {
|
||||
case KEY_DOWN:
|
||||
raw_scroll_down(1);
|
||||
break;
|
||||
case KEY_UP:
|
||||
raw_scroll_up(1);
|
||||
break;
|
||||
case KEY_PGDN:
|
||||
raw_scroll_down(VIEW_H);
|
||||
break;
|
||||
case KEY_PGUP:
|
||||
raw_scroll_up(VIEW_H);
|
||||
break;
|
||||
case KEY_HOME:
|
||||
raw_home();
|
||||
raw_draw();
|
||||
break;
|
||||
case KEY_END:
|
||||
raw_end();
|
||||
raw_draw();
|
||||
break;
|
||||
case KEY_LEFT:
|
||||
if (g_view == VIEW_RAW_HSCROLL && raw_hpan) {
|
||||
raw_hpan = (raw_hpan >= HPAN_STEP) ? (uint16_t)(raw_hpan - HPAN_STEP) : 0;
|
||||
raw_draw();
|
||||
}
|
||||
break;
|
||||
case KEY_RIGHT:
|
||||
if (g_view == VIEW_RAW_HSCROLL && raw_hpan < 248u) {
|
||||
raw_hpan = (uint16_t)(raw_hpan + HPAN_STEP);
|
||||
raw_draw();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0; /* не наша клавиша */
|
||||
}
|
||||
|
||||
// render_raw_status_numbers();
|
||||
render_percent_progress(calc_raw_pct());
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* WITH_RAW */
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* mdview2_status.c — статус-бар (строка 0), нижнее меню (строка 31) и спиннер.
|
||||
*
|
||||
* Хром бара АТОМИЗИРОВАН — обновляется по частям, без перерисовки всего бара:
|
||||
* prerender_status() — фон + разделители │ + "MDVIEW" + имя файла (1 раз);
|
||||
* status_encoding() — только поле кодировки (col 37), по F8;
|
||||
* render_md_status_numbers() — числа MD: диапазон строк + % (при скролле);
|
||||
* render_raw_status_numbers()— числа RAW: метка режима + % по байтам;
|
||||
* render_full_status() — encoding + numbers (MD), при смене набора/режима;
|
||||
* render_menu() — строка меню, по смене режима/готовности;
|
||||
* spinner_tick/show — индикатор занятости во время загрузки.
|
||||
* Оба *_status_numbers() самоочищаются по геометрии разделителей (DIV1_X/DIV2_X
|
||||
* в mdview2.h) — фикс. хром не перерисовывается. RAW-позицию даёт raw_pct()
|
||||
* из mdview2_raw.c (raw_top там приватен).
|
||||
*
|
||||
* Раскладка строки 0:
|
||||
* 0 1..6 8 10.. 37..43 45 46..70 71 72..79
|
||||
* . MDVIEW spinner filename encod. │ L a-b / total │ pct%
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h> /* dec8/dec16, cputs */
|
||||
#include <conio.h> /* textattr/gotoxy/wrchar/COLOR */
|
||||
#include <limits.h>
|
||||
#include <bios/text.h> /* bios_fillcharattr/bios_write_until */
|
||||
#include "mdview2.h"
|
||||
|
||||
/* ---- Спиннер (слот SPINNER_COL в баре) --------------------------- */
|
||||
static const char spinner_chars[4] = { '|', '/', '-', '\\' };
|
||||
static uint8_t spinner_phase = 0;
|
||||
static uint8_t spinner_active = 0;
|
||||
|
||||
/* Продвигает спиннер на один кадр (если включён). */
|
||||
void spinner_tick(void)
|
||||
{
|
||||
if (!spinner_active)
|
||||
return;
|
||||
wrchar(SPINNER_COL, 0, spinner_chars[spinner_phase & 3], ATTR_BAR_SPINNER);
|
||||
spinner_phase++;
|
||||
}
|
||||
|
||||
/* Вкл/выкл спиннер; при выключении гасит его позицию. */
|
||||
void spinner_show(uint8_t on)
|
||||
{
|
||||
spinner_active = on;
|
||||
if (!on) wrchar(SPINNER_COL, 0, ' ', ATTR_BAR);
|
||||
}
|
||||
|
||||
/* ---- Вычисления для чисел статус-бара ----------------------------- */
|
||||
|
||||
/* Число строк, готовых к показу. Во время загрузки последняя эмитированная
|
||||
* строка ещё в g_cells (не зафлашена), поэтому доступны [0..n_lines-2]. */
|
||||
uint16_t drawable_lines(void)
|
||||
{
|
||||
if (g_loading)
|
||||
return (n_lines >= 1) ? (uint16_t)(n_lines - 1) : 0;
|
||||
return n_lines;
|
||||
}
|
||||
|
||||
|
||||
/* ---- Атомарные части статус-бара ---------------------------------- */
|
||||
|
||||
/* Фиксированный хром бара — рисуется ОДИН раз при старте: фон, два
|
||||
* разделителя │, метка "MDVIEW" и имя файла (они не меняются за сессию). */
|
||||
void prerender_status(void)
|
||||
{
|
||||
fill_row(0, ATTR_BAR);
|
||||
wrchar(DIV1_X, 0, 0xB3, ATTR_BAR); /* │ */
|
||||
wrchar(DIV2_X, 0, 0xB3, ATTR_BAR); /* │ */
|
||||
put_str_attr(1, 0, "MDVIEW", ATTR_BAR);
|
||||
put_str_attr(10, 0, filename, ATTR_BAR);
|
||||
}
|
||||
|
||||
/* Поле текущей кодировки (col 37, 7 ячеек). Меняется только по F8. */
|
||||
void status_encoding(void)
|
||||
{
|
||||
textattr(ATTR_BAR);
|
||||
gotoxy(DIV1_X - 10, 0);
|
||||
bios_write_until(enc_name(g_encoding), 8, 0);
|
||||
}
|
||||
|
||||
static uint16_t local_total = UINT_MAX;
|
||||
static uint16_t local_last = UINT_MAX;
|
||||
static uint8_t local_loading = UCHAR_MAX;
|
||||
static uint8_t local_pct = UCHAR_MAX;
|
||||
|
||||
/* Числовая часть (MD): "L a-b / total" между разделителями + "pct%" справа.
|
||||
* Перезаписывает ТОЛЬКО области между/после разделителей, сами │ и фикс.
|
||||
* хром не трогает. */
|
||||
void render_md_status_numbers(void)
|
||||
{
|
||||
uint16_t total = drawable_lines();
|
||||
uint16_t last = top_line + VIEW_H;
|
||||
if (last > total)
|
||||
last = total;
|
||||
|
||||
if(local_total != total || local_last != last || local_loading != g_loading) {
|
||||
local_total = total; local_last = last; local_loading = g_loading;
|
||||
textattr(ATTR_BAR);
|
||||
gotoxy(DIV1_X + 2, 0);
|
||||
bios_fillcharattr(' ', ATTR_BAR, DIV2_X - DIV1_X - 2); /* очистить [DIV1_X+1 .. DIV2_X-1] */
|
||||
gotoxy(DIV1_X + 2, 0);
|
||||
cputs("L ");
|
||||
dec16(top_line + 1);
|
||||
cputs("-");
|
||||
dec16(last);
|
||||
cputs(" / ");
|
||||
dec16(total);
|
||||
if (g_loading) cputs("..."); /* ещё грузится */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Полное обновление переменной части (MD): кодировка + числа. */
|
||||
void render_full_status(void)
|
||||
{
|
||||
local_loading = UCHAR_MAX;
|
||||
status_encoding();
|
||||
render_md_status_numbers();
|
||||
}
|
||||
|
||||
void render_percent_progress(uint8_t pct) {
|
||||
if (local_pct != pct) {
|
||||
local_pct = pct;
|
||||
gotoxy(DIV2_X + 2, 0);
|
||||
bios_fillcharattr(' ', ATTR_BAR, SCREEN_W - DIV2_X - 2); /* очистить [DIV2_X+1 .. конец] */
|
||||
gotoxy(DIV2_X + 2, 0);
|
||||
dec8(pct);
|
||||
cputs("%");
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_RAW
|
||||
/* RAW-вариант числовой части бара (peer к render_md_status_numbers): метка
|
||||
* режима между разделителями + процент справа. Сам очищает свои области (та же
|
||||
* геометрия DIVn_X), атомарен — не требует общего fill всего бара. Позиция
|
||||
* берётся готовым числом из raw_pct() (raw_top приватен mdview2_raw.c). */
|
||||
void render_raw_status(void)
|
||||
{
|
||||
// uint8_t pct = raw_pct();
|
||||
textattr(ATTR_BAR);
|
||||
gotoxy(DIV1_X + 1, 0);
|
||||
bios_writeattr_until(g_view == VIEW_RAW_WRAP ? " RAW wrap" : " RAW pan",
|
||||
DIV2_X - DIV1_X - 1, ATTR_BAR, 0); /* метка + пад до DIV2_X-1 */
|
||||
}
|
||||
#endif /* WITH_RAW */
|
||||
|
||||
#if WITH_HEX
|
||||
/* HEX-вариант числовой части бара (peer к render_raw_status). */
|
||||
void render_hex_status(void)
|
||||
{
|
||||
textattr(ATTR_BAR);
|
||||
gotoxy(DIV1_X + 1, 0);
|
||||
bios_writeattr_until(" HEX", DIV2_X - DIV1_X - 1, ATTR_BAR, 0);
|
||||
}
|
||||
#endif /* WITH_HEX */
|
||||
|
||||
/* ---- Нижнее меню (строка 31) -------------------------------------- */
|
||||
|
||||
void render_menu(void)
|
||||
{
|
||||
/* Блок i = колонка i*8 (8 шириной): 2 поз. НОМЕР клавиши без 'F' (стиль
|
||||
* ATTR_MENU_K, ведущий «чёрный» пробел кроме F10) + 6 поз. ТЕКСТ-функция
|
||||
* (ATTR_MENU_T) сразу за номером. Номера рисуем для ВСЕХ 10 клавиш; текст —
|
||||
* только у задействованных и доступных сейчас. */
|
||||
char num[3];
|
||||
|
||||
fill_row(MENU_ROW, ATTR_MENU_T);
|
||||
|
||||
num[2] = 0;
|
||||
for (uint8_t i = 0; i < 9; i++) { /* F1..F9: ' 1'..' 9' */
|
||||
num[0] = ' '; num[1] = (char)('1' + i);
|
||||
put_str_attr((uint8_t)(i * 8), MENU_ROW, num, ATTR_MENU_K);
|
||||
}
|
||||
put_str_attr(9 * 8, MENU_ROW, "10", ATTR_MENU_K); /* F10 */
|
||||
|
||||
put_str_attr(0 * 8 + 2, MENU_ROW, "Help", ATTR_MENU_T);
|
||||
#if WITH_RAW
|
||||
if (g_ready) /* RAW доступен после постройки документа */
|
||||
put_str_attr(1 * 8 + 2, MENU_ROW, (g_view == VIEW_MD) ? "RAW" : "MD", ATTR_MENU_T);
|
||||
if (VIEW_IS_RAW(g_view)) /* Wrap/Unwrap — только в RAW */
|
||||
put_str_attr(2 * 8 + 2, MENU_ROW,
|
||||
(g_view == VIEW_RAW_WRAP) ? "UnWrap" : "Wrap", ATTR_MENU_T);
|
||||
#endif
|
||||
#if WITH_HEX
|
||||
if (g_ready) /* HEX доступен после постройки документа */
|
||||
put_str_attr(3 * 8 + 2, MENU_ROW, (g_view == VIEW_HEX) ? "Back" : "Hex", ATTR_MENU_T);
|
||||
#endif
|
||||
if (g_f8_enabled) /* смена кодировки сейчас возможна */
|
||||
put_str_attr(7 * 8 + 2, MENU_ROW, "CodePg", ATTR_MENU_T);
|
||||
put_str_attr(9 * 8 + 2, MENU_ROW, "Exit", ATTR_MENU_T);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* mdview2_table.c — выровненная отрисовка markdown-таблиц.
|
||||
*
|
||||
* Разбор |-разделённых ячеек и эмиссия рамок/строк в общий буфер ячеек
|
||||
* рендера (gc_put/gc_fill, см. mdview2.h). Вызывается из index_lines()
|
||||
* ядра при обнаружении таблицы. Геометрия рамок (TBL_n), словарь стилей
|
||||
* (CK_n, INIT_STYLE_n) и cell-buffer API объявлены в mdview2.h.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <conio.h> /* COLOR() / COLOR_* для атрибутов TBL_ATTR/ATTR_TEXT */
|
||||
#include "mdview2.h"
|
||||
|
||||
uint32_t row_end(uint32_t p)
|
||||
{
|
||||
while (p < file_size && fb(p) != '\n') p++;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Начало контента первой ячейки строки (после ведущих пробелов и '|'). */
|
||||
uint32_t table_first_cell(uint32_t row_start)
|
||||
{
|
||||
uint32_t p = row_start;
|
||||
while (p < file_size && fb(p) == ' ') p++;
|
||||
if (p < file_size && fb(p) == '|') p++;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Следующая ячейка: [*cs,*ce) — обрезанный диапазон; *pp продвигается за '|'.
|
||||
* 0 — ячеек больше нет (включая хвостовую пустоту после последнего '|'). */
|
||||
uint8_t table_next_cell(uint32_t *pp, uint32_t lineend, uint32_t *cs, uint32_t *ce)
|
||||
{
|
||||
uint32_t p = *pp;
|
||||
if (p >= lineend) return 0;
|
||||
uint32_t a = p;
|
||||
while (p < lineend && fb(p) != '|') p++;
|
||||
uint32_t b = p;
|
||||
uint8_t had_pipe = (uint8_t)(p < lineend && fb(p) == '|');
|
||||
if (had_pipe) p++;
|
||||
*pp = p;
|
||||
while (a < b && fb(a) == ' ') a++;
|
||||
while (b > a && fb(b - 1) == ' ') b--;
|
||||
if (!had_pipe && a == b) return 0;
|
||||
*cs = a; *ce = b;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Строка-разделитель (|---|:-:|): каждая ячейка непустая и только '-'/':'. */
|
||||
uint8_t table_is_sep_row(uint32_t row_start, uint32_t lineend)
|
||||
{
|
||||
uint32_t p = table_first_cell(row_start);
|
||||
uint32_t cs, ce;
|
||||
uint8_t n = 0;
|
||||
while (table_next_cell(&p, lineend, &cs, &ce)) {
|
||||
if (cs >= ce) return 0;
|
||||
for (uint32_t q = cs; q < ce; q++) { char c = fb(q); if (c != '-' && c != ':') return 0; }
|
||||
n++;
|
||||
}
|
||||
return (uint8_t)(n > 0);
|
||||
}
|
||||
|
||||
/* Горизонтальная рамка в g_cells: left + (H×(w+2) + mid|right) по колонкам. */
|
||||
void table_border(const uint8_t *widths, uint8_t ncols, char left, char mid, char right)
|
||||
{
|
||||
gc_put(left, TBL_ATTR);
|
||||
for (uint8_t c = 0; c < ncols; c++) {
|
||||
gc_fill(TBL_H, TBL_ATTR, (uint8_t)(widths[c] + 2));
|
||||
gc_put((c == (uint8_t)(ncols - 1)) ? right : mid, TBL_ATTR);
|
||||
}
|
||||
}
|
||||
|
||||
/* Строка данных в g_cells: │ <ячейка, добитая до widths[c]> │ … */
|
||||
void table_data_row(uint32_t row_start, uint32_t lineend, const uint8_t *widths, uint8_t ncols)
|
||||
{
|
||||
uint32_t p = table_first_cell(row_start);
|
||||
uint32_t cs, ce;
|
||||
gc_put(TBL_V, TBL_ATTR);
|
||||
for (uint8_t c = 0; c < ncols; c++) {
|
||||
uint8_t got = table_next_cell(&p, lineend, &cs, &ce);
|
||||
gc_put(' ', TBL_ATTR);
|
||||
uint8_t rendered = 0;
|
||||
if (got && ce > cs) {
|
||||
uint8_t before = g_ncells;
|
||||
(void)inline_scan(cs, ce, 0, CK_OTHER, INIT_STYLE_PLAIN, ATTR_TEXT, 1);
|
||||
rendered = (uint8_t)(g_ncells - before);
|
||||
}
|
||||
if (rendered < widths[c]) gc_fill(' ', ATTR_TEXT, (uint8_t)(widths[c] - rendered));
|
||||
gc_put(' ', TBL_ATTR);
|
||||
gc_put(TBL_V, TBL_ATTR);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user