@@ -30,25 +30,7 @@
# include <sprinter.h>
# include <sprinter_mem.h>
# include <palette.h>
/* ---- Геометрия экрана -------------------------------------------- */
# 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 PAGE_BITS 14u
# define PAGE_SIZE (1u << PAGE_BITS) /* размер EMM-страницы: 16 КБ */
# define PAGE_MASK ((uint16_t)(PAGE_SIZE - 1u))
# define MAX_PAGES 8 /* 8 страниц × 16 КБ = 128 КБ */
# define MAX_FILE ((uint32_t)MAX_PAGES * PAGE_SIZE) /* максимальный размер файла: 131072 байт */
// #define MAX_LINES 2048
# define FILE_BUF ((char *)0xC000) /* окно W3, куда мапится текущая EMM-страница */
# include "mdview2.h" /* геометрия, атрибуты, extern для модуля RAW */
/* ---- Палитра атрибутов ------------------------------------------- */
@@ -71,33 +53,7 @@ static const uint8_t md_pallete[16 * 4] = {
0xFF , 0xFF , 0xFF , 0xFF
} ;
# 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) /* рамка таблиц; отдельный (не контентный) attr — чтобы ремап кодировок не трогал box-глифы */
# 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_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)
/* Атрибуты экрана/справки и геометрия — в mdview2.h (общие с модулем RAW). */
# define ATTR_HELP_HINT COLOR(COLOR_YELLOW, COLOR_BLACK)
/* Геометрия диалога справки (в символьных координатах 80×32). */
@@ -269,7 +225,7 @@ static uint8_t emph_to_attr(uint8_t emph, uint8_t base_attr);
static uint16_t n_lines = 0 ;
static uint16_t max_lines = 0 ; /* ёмкость индекса: index_pages * 2048 */
static uint16_t top_line = 0 ;
static uint32_t file_size = 0 ;
uint32_t file_size = 0 ;
static uint8_t file_blk = 0 ;
static uint8_t file_pages = 0 ;
static uint8_t file_phys [ MAX_PAGES ] = { 0 } ;
@@ -285,6 +241,11 @@ static char filename[64] = {0};
/* Прогрессивная загрузка: рисуем первый экран и пускаем скролл, пока
* index_lines() ещё досканирует файл (кооперативно, без прерываний). */
# if WITH_RAW
uint8_t g_view = VIEW_MD ; /* режим просмотра (общий с mdview2_raw.c) */
static uint8_t g_ready = 0 ; /* 1 когда первичный документ построен — F2 (RAW) рабочий */
static uint8_t g_raw_last = VIEW_RAW_WRAP ; /* последний под-режим RAW (возврат по F2) */
# endif
static uint8_t g_loading = 0 ; /* 1 во время index_lines() */
static uint8_t g_first_drawn = 0 ; /* первый экран уже нарисован */
static uint8_t g_f8_enabled = 0 ; /* можно ли сейчас переключать кодировку (метка F8 в меню + F8 в load_key) */
@@ -300,8 +261,8 @@ static void progress_tick(void); /* определена ниже (нужн
# 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 */
const uint8_t * g_remap = 0 ; /* активная таблица ремапа (CP1251/KOI8), 0 = без ремапа */
uint8_t g_scratch_phys = 0 ; /* EMM-страница под ремап-буфер строки для win_rest */
/* UTF-8 набор (Фаза 2): src декодируется в отдельный блок EMM-страниц как
* чистый CP866. После конвертации активный буфер файла (file_phys/file_size/
@@ -312,7 +273,16 @@ 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 набор удалось сконвертировать/построить */
static uint8_t utf_avail = 0 ; /* 1 = страницы UTF-8 выделены */
/* Инкрементальная конвертация UTF-8→CP866: идёт чанками, чередуясь с
* индексацией (index_lines докручивает конвертацию впереди позиции чтения),
* чтобы первый экран появлялся быстро, а не после полного прохода. */
static uint8_t g_utf_building = 0 ; /* 1 пока конвертация не дошла до конца исходника */
static uint32_t utf_src = 0 ; /* позиция чтения ОРИГИНАЛА (utf-8) конвертером */
static uint8_t cv_page = 0xFF ; /* orig-страница в W3 для cv_read (своя от cur_page) */
# define CONV_MARGIN 4096u /* на сколько байт держать конвертацию впереди индексатора */
static void utf_convert_more ( uint32_t target ) ; /* forward (нужна в index_lines) */
/* Снимок исходного (8-битного) файлового буфера — набор DOC_8BIT читает его
* как есть, набор DOC_UTF8 читает utf_phys. */
@@ -386,10 +356,11 @@ static void put_str_attr(uint8_t x, uint8_t y, const char *s, uint8_t attr)
/* Полностью очищает указанную строку экрана символами пробела с attr. */
static void fill_row ( uint8_t y , uint8_t attr )
void fill_row ( uint8_t y , uint8_t attr )
{
bios_set_place ( y , 0 ) ;
bios_fillcharattr ( ' ' , attr , SCREEN_W ) ;
// bios_set_place(y, 0);
// bios_fillcharattr(' ', attr, SCREEN_W);
bios_clearwin ( y , 0 , 1 , SCREEN_W , attr ) ;
}
/* Продвигает спиннер на один кадр.
@@ -439,7 +410,7 @@ static inline void map_page(uint8_t page)
* bank_read читает через IN — общий аппаратный регистр, без теневой RAM.
* NB: __naked обязателен (внутри собственный ret); IX/IY не трогаем
* (callee-saved), BC/DE/HL/AF — caller-saved, можно клобберить. */
static char fb ( uint32_t p ) __naked
char fb ( uint32_t p ) __naked
{
( void ) p ; /* p = HLDE; доступ только из asm */
__asm
@@ -497,7 +468,7 @@ static char fb(uint32_t p) __naked
* Когда Фаза 2 начала пакетировать много строк на одной странице с
* РАЗНЫМИ off, всё стало читать с начала страницы (т.е. контент первой
* записи) независимо от реальной строки — добавлен off, IX = 0xC000+off. */
static void win_rest ( uint8_t row , uint8_t col , uint8_t h , uint8_t w , uint8_t page , uint16_t off ) __naked
void win_rest ( uint8_t row , uint8_t col , uint8_t h , uint8_t w , uint8_t page , uint16_t off ) __naked
{
( void ) row ; ( void ) col ; ( void ) h ; ( void ) w ; ( void ) page ; ( void ) off ;
__asm
@@ -586,7 +557,7 @@ static void idx_put(uint16_t idx, const idx_rec_t *r)
bank_write ( index_phys [ page ] , off , r , INDEX_REC_SIZE ) ;
}
static uint32_t seg_off ( uint16_t idx )
uint32_t seg_off ( uint16_t idx )
{
idx_rec_t r ;
if ( idx > = n_lines ) return file_size ;
@@ -1011,6 +982,70 @@ static uint8_t is_table_raw(uint32_t p)
return 0 ;
}
/* Разбор одного inline-МАРКЕРА в позиции *pq (< bound): \X, [x], `, **, ~~,
* *, _, \t. Общий код для inline_scan() и scan_join_stream(). Возврат: 1 —
* маркер обработан (вызывающий делает continue), 0 — символ обычный (пробел/
* текст), его кладёт сам вызывающий (и проверяет перенос). Меняет
* *pq/*pls/*pseg/*pprev. attr берётся как emph_to_attr(line_style, base) —
* для join base=ATTR_TEXT, что тождественно styles_map[line_style]. */
static uint8_t inline_marker ( uint32_t * pq , uint32_t bound , uint8_t * pls ,
uint8_t * pseg , char * pprev , uint8_t base )
{
uint32_t q = * pq ;
uint8_t ls = * pls ;
char ch = fb ( q ) ;
if ( ls ! = INIT_STYLE_CODE & & ch = = ' \\ ' & & ( q + 1 ) < bound & & is_escapable ( fb ( q + 1 ) ) ) {
char nb = fb ( q + 1 ) ;
gc_put ( nb , emph_to_attr ( ls , base ) ) ;
* pseg + = 1 ; * pq = q + 2 ; * pprev = nb ; return 1 ;
}
if ( ls ! = INIT_STYLE_CODE & & ch = = ' [ ' & & ( q + 2 ) < bound & & fb ( q + 2 ) = = ' ] ' ) {
gc_put ( ' [ ' , ATTR_TEXT_BOLD ) ; gc_put ( fb ( q + 1 ) , ATTR_TEXT_BOLD ) ; gc_put ( ' ] ' , ATTR_TEXT_BOLD ) ;
* pseg + = 3 ; * pq = q + 3 ; * pprev = ' ] ' ; return 1 ;
}
if ( ch = = ' ` ' ) {
if ( ls = = INIT_STYLE_PLAIN ) { * pls = INIT_STYLE_CODE ; * pq = q + 1 ; return 1 ; }
if ( ls = = INIT_STYLE_CODE ) { * pls = INIT_STYLE_PLAIN ; * pq = q + 1 ; return 1 ; }
gc_put ( ' ` ' , emph_to_attr ( ls , base ) ) ; * pseg + = 1 ; * pq = q + 1 ; * pprev = ' ` ' ; return 1 ;
}
if ( ch = = ' * ' & & ( q + 1 ) < bound & & fb ( q + 1 ) = = ' * ' ) {
char nx = ( ( q + 2 ) < bound ) ? fb ( q + 2 ) : ' \n ' ;
if ( is_emph_flanked ( * pprev , nx ) ) {
if ( ls = = INIT_STYLE_PLAIN & & ws_or_eol ( * pprev ) ) { * pls = INIT_STYLE_BOLD ; * pq = q + 2 ; return 1 ; }
if ( ls = = INIT_STYLE_BOLD & & ws_or_eol_or_delim ( nx ) ) { * pls = INIT_STYLE_PLAIN ; * pq = q + 2 ; return 1 ; }
}
gc_put ( ' * ' , emph_to_attr ( ls , base ) ) ; gc_put ( ' * ' , emph_to_attr ( ls , base ) ) ;
* pseg + = 2 ; * pq = q + 2 ; * pprev = ' * ' ; return 1 ;
}
if ( ch = = ' ~ ' & & ( q + 1 ) < bound & & fb ( q + 1 ) = = ' ~ ' ) {
char nx = ( ( q + 2 ) < bound ) ? fb ( q + 2 ) : ' \n ' ;
if ( is_emph_flanked ( * pprev , nx ) ) {
if ( ls = = INIT_STYLE_PLAIN & & ws_or_eol ( * pprev ) ) { * pls = INIT_STYLE_STRIKE ; * pq = q + 2 ; return 1 ; }
if ( ls = = INIT_STYLE_STRIKE & & ws_or_eol_or_delim ( nx ) ) { * pls = INIT_STYLE_PLAIN ; * pq = q + 2 ; return 1 ; }
}
gc_put ( ' ~ ' , emph_to_attr ( ls , base ) ) ; gc_put ( ' ~ ' , emph_to_attr ( ls , base ) ) ;
* pseg + = 2 ; * pq = q + 2 ; * pprev = ' ~ ' ; return 1 ;
}
if ( ch = = ' * ' | | ch = = ' _ ' ) {
char nx = ( ( q + 1 ) < bound ) ? fb ( q + 1 ) : ' \n ' ;
if ( is_emph_flanked ( * pprev , nx ) ) {
uint8_t ns = ( ch = = ' * ' ) ? INIT_STYLE_ITALIC : INIT_STYLE_UNDER ;
if ( ls = = INIT_STYLE_PLAIN & & ws_or_eol ( * pprev ) ) { * pls = ns ; * pq = q + 1 ; return 1 ; }
if ( ls ! = INIT_STYLE_PLAIN & & ws_or_eol_or_delim ( nx ) ) { * pls = INIT_STYLE_PLAIN ; * pq = q + 1 ; return 1 ; }
}
gc_put ( ch , emph_to_attr ( ls , base ) ) ; * pseg + = 1 ; * pq = q + 1 ; * pprev = ch ; return 1 ;
}
if ( ch = = ' \t ' ) {
uint8_t old_sc = * pseg ;
uint8_t tgt_full = ( uint8_t ) ( ( old_sc & ( uint8_t ) ~ ( TAB_STOP - 1 ) ) + TAB_STOP ) ;
uint8_t tgt = tgt_full ; if ( tgt > SCREEN_W ) tgt = SCREEN_W ;
gc_fill ( ' ' , emph_to_attr ( ls , base ) , ( uint8_t ) ( tgt_full - old_sc ) ) ;
* pseg = tgt ; * pq = q + 1 ; * pprev = ' ' ; return 1 ;
}
return 0 ; /* пробел или обычный символ — обрабатывает вызывающий */
}
/* Общий сканер inline-форматирования и переносов.
* Идёт от q до q_end, учитывает стартовую колонку col и при необходимости
* эмитит continuation-сегменты. Возвращает итоговый line_style. */
@@ -1039,73 +1074,9 @@ static uint8_t inline_scan(uint32_t q, uint32_t q_end, uint8_t col,
continue ;
}
/* Экранированная пунктуация: \ * \ _ \` … → литерал (не внутри кода) . */
if ( line_style ! = INIT_STYLE_CODE & & ch = = ' \\ ' & & ( q + 1 ) < q_end & & is_escapable ( fb ( q + 1 ) ) ) {
char nb = fb ( q + 1 ) ;
gc_put ( nb , emph_to_attr ( line_style , base ) ) ;
seg_col + + ; q + = 2 ; prev_ch = nb ; continue ;
}
/* [x] — ровно один символ в скобках → болд (task-list checkbox; не внутри кода). */
if ( line_style ! = INIT_STYLE_CODE & & ch = = ' [ ' & & ( q + 2 ) < q_end & & fb ( q + 2 ) = = ' ] ' ) {
char mid = fb ( q + 1 ) ;
gc_put ( ' [ ' , ATTR_TEXT_BOLD ) ;
gc_put ( mid , ATTR_TEXT_BOLD ) ;
gc_put ( ' ] ' , ATTR_TEXT_BOLD ) ;
seg_col + = 3 ; q + = 3 ; prev_ch = ' ] ' ; continue ;
}
/* Inline-маркеры (\X [x] ` ** ~~ * _ \t) — общий разбор . */
if ( inline_marker ( & q , q_end , & line_style , & seg_col , & prev_ch , base ) ) continue ;
if ( ch = = ' ` ' ) {
if ( line_style = = INIT_STYLE_PLAIN ) { line_style = INIT_STYLE_CODE ; q + + ; continue ; }
if ( line_style = = INIT_STYLE_CODE ) { line_style = INIT_STYLE_PLAIN ; q + + ; continue ; }
gc_put ( ' ` ' , emph_to_attr ( line_style , base ) ) ;
seg_col + + ; q + + ; prev_ch = ' ` ' ; continue ;
}
if ( ch = = ' * ' & & ( q + 1 ) < q_end & & fb ( q + 1 ) = = ' * ' ) {
char next_ch = ( ( q + 2 ) < q_end ) ? fb ( q + 2 ) : ' \n ' ;
if ( is_emph_flanked ( prev_ch , next_ch ) ) {
if ( line_style = = INIT_STYLE_PLAIN & & ws_or_eol ( prev_ch ) ) {
line_style = INIT_STYLE_BOLD ; q + = 2 ; continue ;
} else if ( line_style = = INIT_STYLE_BOLD & & ws_or_eol_or_delim ( next_ch ) ) {
line_style = INIT_STYLE_PLAIN ; q + = 2 ; continue ;
}
}
{ gc_put ( ' * ' , emph_to_attr ( line_style , base ) ) ; gc_put ( ' * ' , emph_to_attr ( line_style , base ) ) ; }
seg_col + = 2 ; q + = 2 ; prev_ch = ' * ' ; continue ;
}
if ( ch = = ' ~ ' & & ( q + 1 ) < q_end & & fb ( q + 1 ) = = ' ~ ' ) {
char next_ch = ( ( q + 2 ) < q_end ) ? fb ( q + 2 ) : ' \n ' ;
if ( is_emph_flanked ( prev_ch , next_ch ) ) {
if ( line_style = = INIT_STYLE_PLAIN & & ws_or_eol ( prev_ch ) ) {
line_style = INIT_STYLE_STRIKE ; q + = 2 ; continue ;
} else if ( line_style = = INIT_STYLE_STRIKE & & ws_or_eol_or_delim ( next_ch ) ) {
line_style = INIT_STYLE_PLAIN ; q + = 2 ; continue ;
}
}
{ gc_put ( ' ~ ' , emph_to_attr ( line_style , base ) ) ; gc_put ( ' ~ ' , emph_to_attr ( line_style , base ) ) ; }
seg_col + = 2 ; q + = 2 ; prev_ch = ' ~ ' ; continue ;
}
if ( ch = = ' * ' | | ch = = ' _ ' ) {
char next_ch = ( ( q + 1 ) < q_end ) ? fb ( q + 1 ) : ' \n ' ;
if ( is_emph_flanked ( prev_ch , next_ch ) ) {
uint8_t new_style = ( ch = = ' * ' ) ? INIT_STYLE_ITALIC : INIT_STYLE_UNDER ;
if ( line_style = = INIT_STYLE_PLAIN & & ws_or_eol ( prev_ch ) ) {
line_style = new_style ; q + + ; continue ;
} else if ( line_style ! = INIT_STYLE_PLAIN & & ws_or_eol_or_delim ( next_ch ) ) {
/* как render: закрывает любой активный эмфазис */
line_style = INIT_STYLE_PLAIN ; q + + ; continue ;
}
}
gc_put ( ch , emph_to_attr ( line_style , base ) ) ;
seg_col + + ; q + + ; prev_ch = ch ; continue ;
}
if ( ch = = ' \t ' ) {
uint8_t old_sc = seg_col ;
uint8_t tgt_full = ( uint8_t ) ( ( seg_col & ( uint8_t ) ~ ( TAB_STOP - 1 ) ) + TAB_STOP ) ;
uint8_t tgt = tgt_full ;
if ( tgt > SCREEN_W ) tgt = SCREEN_W ;
gc_fill ( ' ' , emph_to_attr ( line_style , base ) , ( uint8_t ) ( tgt_full - old_sc ) ) ;
seg_col = tgt ; q + + ; prev_ch = ' ' ; continue ;
}
if ( ch = = ' ' ) {
gc_put ( ' ' , emph_to_attr ( line_style , base ) ) ;
last_space = q + 1 ;
@@ -1261,112 +1232,12 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
}
}
/* Экранированная пунктуация: \* \_ \` … → литерал (не внутри кода). */
if ( line_style ! = INIT_STYLE_CODE & & ch = = ' \\ ' & & ( q + 1 ) < file_size & & is_escapable ( fb ( q + 1 ) ) ) {
char nb = fb ( q + 1 ) ;
gc_put ( nb , styles_map [ line_style ] ) ;
seg_col + + ; q + = 2 ; prev_ch = nb ; continue ;
}
/* [x] — ровно один символ в скобках → болд (task-list checkbox; не внутри кода). */
if ( line_style ! = INIT_STYLE_CODE & & ch = = ' [ ' & & ( q + 2 ) < file_size & & fb ( q + 2 ) = = ' ] ' ) {
char mid = fb ( q + 1 ) ;
gc_put ( ' [ ' , ATTR_TEXT_BOLD ) ;
gc_put ( mid , ATTR_TEXT_BOLD ) ;
gc_put ( ' ] ' , ATTR_TEXT_BOLD ) ;
seg_col + = 3 ; q + = 3 ; prev_ch = ' ] ' ; continue ;
}
/* Inline-маркеры (\X [x] ` ** ~~ * _ \t) — общий разбор; при
* soft_break ch==' ' (синтетический пробел склейки) — маркеры
* пропускаем, его кладёт ветка пробела ниже. */
if ( ! soft_break & & inline_marker ( & q , file_size , & line_style , & seg_col , & prev_ch , ATTR_TEXT ) )
continue ;
if ( ch = = ' ` ' ) {
/* Как handle_inline_marker в render: backtick переключает code-режим
* ТОЛЬКО из PLAIN или CODE; если активен эмфазис (bold/italic/...),
* backtick — обычный литеральный символ (нет вложенного кода). */
if ( line_style = = INIT_STYLE_PLAIN ) {
line_style = INIT_STYLE_CODE ;
if ( ! soft_break ) q + + ;
continue ;
}
if ( line_style = = INIT_STYLE_CODE ) {
line_style = INIT_STYLE_PLAIN ;
if ( ! soft_break ) q + + ;
continue ;
}
gc_put ( ' ` ' , styles_map [ line_style ] ) ;
seg_col + + ;
if ( ! soft_break ) q + + ; else q + + ;
prev_ch = ' ` ' ;
continue ;
}
if ( ch = = ' * ' & & ( q + 1 ) < file_size & & fb ( q + 1 ) = = ' * ' ) {
char next_ch = ( ( q + 2 ) < file_size ) ? fb ( q + 2 ) : ' \n ' ;
if ( is_emph_flanked ( prev_ch , next_ch ) ) {
if ( line_style = = INIT_STYLE_PLAIN & & ws_or_eol ( prev_ch ) ) {
line_style = INIT_STYLE_BOLD ;
if ( ! soft_break ) q + = 2 ; else q + + ;
continue ;
} else if ( line_style = = INIT_STYLE_BOLD & & ws_or_eol_or_delim ( next_ch ) ) {
line_style = INIT_STYLE_PLAIN ;
if ( ! soft_break ) q + = 2 ; else q + + ;
continue ;
}
}
{ gc_put ( ' * ' , styles_map [ line_style ] ) ; gc_put ( ' * ' , styles_map [ line_style ] ) ; }
seg_col + = 2 ;
if ( ! soft_break ) q + = 2 ; else q + + ;
prev_ch = ' * ' ;
continue ;
}
if ( ch = = ' ~ ' & & ( q + 1 ) < file_size & & fb ( q + 1 ) = = ' ~ ' ) {
char next_ch = ( ( q + 2 ) < file_size ) ? fb ( q + 2 ) : ' \n ' ;
if ( is_emph_flanked ( prev_ch , next_ch ) ) {
if ( line_style = = INIT_STYLE_PLAIN & & ws_or_eol ( prev_ch ) ) {
line_style = INIT_STYLE_STRIKE ;
if ( ! soft_break ) q + = 2 ; else q + + ;
continue ;
} else if ( line_style = = INIT_STYLE_STRIKE & & ws_or_eol_or_delim ( next_ch ) ) {
line_style = INIT_STYLE_PLAIN ;
if ( ! soft_break ) q + = 2 ; else q + + ;
continue ;
}
}
{ gc_put ( ' ~ ' , styles_map [ line_style ] ) ; gc_put ( ' ~ ' , styles_map [ line_style ] ) ; }
seg_col + = 2 ;
if ( ! soft_break ) q + = 2 ; else q + + ;
prev_ch = ' ~ ' ;
continue ;
}
if ( ch = = ' * ' | | ch = = ' _ ' ) {
char next_ch = ( ( q + 1 ) < file_size ) ? fb ( q + 1 ) : ' \n ' ;
if ( is_emph_flanked ( prev_ch , next_ch ) ) {
uint8_t new_style = ( ch = = ' * ' ) ? INIT_STYLE_ITALIC : INIT_STYLE_UNDER ;
if ( line_style = = INIT_STYLE_PLAIN & & ws_or_eol ( prev_ch ) ) {
line_style = new_style ;
if ( ! soft_break ) q + + ; else q + + ;
continue ;
} else if ( line_style ! = INIT_STYLE_PLAIN & & ws_or_eol_or_delim ( next_ch ) ) {
/* как render: одиночный маркер закрывает ЛЮБОЙ активный эмфазис */
line_style = INIT_STYLE_PLAIN ;
if ( ! soft_break ) q + + ; else q + + ;
continue ;
}
}
gc_put ( ch , styles_map [ line_style ] ) ;
seg_col + + ;
if ( ! soft_break ) q + + ; else q + + ;
prev_ch = ch ;
continue ;
}
if ( ch = = ' \t ' ) {
uint8_t old_sc = seg_col ;
uint8_t tgt_full = ( uint8_t ) ( ( seg_col & ( uint8_t ) ~ ( TAB_STOP - 1 ) ) + TAB_STOP ) ;
uint8_t tgt = tgt_full ;
if ( tgt > SCREEN_W ) tgt = SCREEN_W ;
/* render заполняет до НЕкапнутого tgt_full (по cc); для plain cc==seg_col */
gc_fill ( ' ' , styles_map [ line_style ] , ( uint8_t ) ( tgt_full - old_sc ) ) ;
seg_col = tgt ;
if ( ! soft_break ) q + + ; else q + + ;
prev_ch = ' ' ;
continue ;
}
if ( ch = = ' ' ) {
gc_put ( ' ' , styles_map [ line_style ] ) ;
if ( soft_break ) {
@@ -1384,7 +1255,7 @@ static uint32_t scan_join_stream(uint32_t q, uint32_t para_start, uint8_t col,
} else {
gc_put ( ch , styles_map [ line_style ] ) ;
seg_col + + ;
if ( ! soft_break ) q + + ; else q + + ;
q + + ; /* soft_break здесь всегда 0 (ch!=' ') */
prev_ch = ch ;
}
if ( seg_col > = SCREEN_W ) {
@@ -1533,6 +1404,12 @@ static void index_lines(void)
uint32_t p = 0 ;
while ( p < file_size & & n_lines < max_lines & & ! g_abort ) {
/* UTF-8: держим конвертацию впереди позиции чтения (включая look-ahead
* склейки/классификации ~ один абзац). file_size при этом = доступная
* сконвертированная часть и растёт по ходу. */
if ( g_utf_building & & ( uint32_t ) ( p + CONV_MARGIN ) > file_size )
utf_convert_more ( ( uint32_t ) ( p + CONV_MARGIN ) ) ;
if ( g_loading )
progress_tick ( ) ; /* первый экран + статус + скролл по готовым данным */
else if ( ( n_lines & 15 ) = = 0 )
@@ -1813,43 +1690,29 @@ 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 ,
/* Таблицы ремапа СТАРШИХ байтов [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 [ 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 ,
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-битных). */
@@ -1910,9 +1773,14 @@ static void doc_switch(uint8_t 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 ; }
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 ;
}
/* Автоопределение кодировки дешёвым сканом байтов (до построения индекса).
* BOM → UTF8; иначе валидность UTF-8 (структура multibyte); иначе 8-бит по
@@ -1931,9 +1799,9 @@ static uint8_t detect_encoding(void)
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 ( 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 ; }
@@ -1979,7 +1847,7 @@ static void win_rest_remap(uint8_t row, uint8_t w, uint8_t page, uint16_t off)
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 ] ;
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 ) ;
@@ -2027,64 +1895,67 @@ static void conv_put(uint8_t b)
if ( conv_n = = ( uint16_t ) sizeof ( conv_buf ) ) conv_flush ( ) ;
}
/* К одпойнт → CP866-глиф(ы). Кириллица по таблице; ходовые символы (тире,
* кавычки, буллет, стрелки, галочки, box-drawing) — подстановкой; BOM
* проглатывается; прочее → '?'. */
/* П одстановки одиночных не-кириллических символов 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-глиф(ы). Кириллица по таблице; спецслучаи (…, BOM) явно;
* остальные ходовые символы по таблице utf_sym[]; прочее → '?'. */
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 ;
}
if ( cp = = 0x2026 ) { conv_put ( ' . ' ) ; conv_put ( ' . ' ) ; conv_put ( ' . ' ) ; return ; } /* … → "..." */
if ( cp = = 0xFEFF ) return ; /* BOM/ZWNBSP — выкинуть */
for ( uint8_t i = 0 ; i < UTF_SYM_N ; i + + )
if ( utf_sym [ i ] . utf8 = = ( uint16_t ) cp ) { conv_put ( utf_sym [ i ] . cp866 ) ; return ; }
conv_put ( ' ? ' ) ;
}
/* Декодирует активный (исходный UTF-8) буфер в utf_phys как CP866.
* Битые/обрезанные последовательности → '?'; 4-байтные (эмодзи и т.п.) → '?'. */
static void utf8_convert ( void )
/* Последовательное чтение байта ОРИГИНАЛА (utf-8) конвертером. Оригинал лежит
* в orig_file_phys[]; мапим его страницу в W3 напрямую, со своей кэш-переменной
* cv_page (отдельной от cur_page индексатора, т.к. оба используют W3 и
* чередуются). На границе с fb()-чтением utf-буфера кэши взаимно сбрасываются. */
static uint8_t cv_read ( uint32_t s )
{
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 + + ) ;
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. */
static 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 ;
@@ -2092,21 +1963,23 @@ static void utf8_convert(void)
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 ; } /* одиночный континюэйшн/битый лид */
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 ( 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 ) ;
p + + ;
utf_src + + ;
}
if ( ! ok | | need = = 3 ) { conv_put ( ' ? ' ) ; continue ; } /* битое / 4-байтное */
if ( ! ok | | need = = 3 ) { conv_put ( ' ? ' ) ; continue ; }
conv_emit_cp ( cp ) ;
}
conv_flush ( ) ;
utf_size = ( uint32_t ) conv_page * PAGE_SIZE + conv_off ;
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 набор и конвертирует туда исходник (читает
@@ -2114,15 +1987,16 @@ static void utf8_convert(void)
* активный буфер: это делает build_doc() при сборке набора DOC_UTF8.
* Конвертированный поток ≤ исходного (кириллица 2 байта → 1), хватает того же
* числа страниц. Возврат: 1 — успех, 0 — нет EMM. */
static uint8_t alloc_and_convert_utf8 ( void )
/* Выделяет страницы под UTF-8 набор (конвертированный ≤ оригинала по размеру).
* Сама конвертация — инкрементальная, через utf_convert_more(). Возврат: 1 —
* успех, 0 — нет EMM. */
static uint8_t utf_alloc ( void )
{
utf_pages = file_pages ;
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 ) ;
utf8_convert ( ) ; /* читает оригинал (active) → utf_phys */
return 1 ;
}
@@ -2241,18 +2115,30 @@ static void render_updated_status(void)
/* Отрисовывает нижнюю строку меню. */
static void render_menu ( void )
{
/* Строка меню разбита на 10 блоков по 8 колонок: метку Fn кладём в блок
* (n-1)*8, чтобы позиции соответствовали номерам клавиш (F1→0, F8→7,
* F10→9). Пока заполнены только F1/F8/F10. */
/* Блок i = колонка i*8 (8 шириной): 2 поз. НОМЕР клавиши без 'F' (стиль
* ATTR_MENU_K, ведущий «чёрный» пробел кроме F10) + 6 поз. ТЕКСТ-функция
* (ATTR_MENU_T) сразу за номером. Номера рисуем для ВСЕХ 10 клавиш; текст —
* только у задействованных и доступных сейчас. */
fill_row ( MENU_ROW , ATTR_MENU_T ) ;
put_str_attr ( 0 * 8 + 1 , MENU_ROW , " F1 " , ATTR_MENU_K ) ;
put_str_attr ( 0 * 8 + 4 , MENU_ROW , " Help " , ATTR_MENU_T ) ;
if ( g_f8_enabled ) { /* F8 показываем только когда переключение кодировки возможно */
put_str_attr ( 7 * 8 + 1 , MENU_ROW , " F8 " , ATTR_MENU_K ) ;
put_str_attr ( 7 * 8 + 4 , MENU_ROW , " Codepage " , ATTR_MENU_T ) ;
char num [ 3 ] ; 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 + 0 , MENU_ROW , " F10 " , ATTR_MENU_K ) ;
put_str_attr ( 9 * 8 + 4 , MENU_ROW , " Exit " , ATTR_MENU_T ) ;
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 ( g_view ! = VIEW_MD ) /* Wrap/Unwrap — только в RAW */
put_str_attr ( 2 * 8 + 2 , MENU_ROW ,
( g_view = = VIEW_RAW_WRAP ) ? " UnWrap " : " Wrap " , 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 ) ;
}
/* ==================================================================
@@ -2490,6 +2376,22 @@ static void load_key(uint16_t k)
/* Кооперативный «тик» прогрессивной загрузки — вызывается из index_lines(). */
static void progress_tick ( void )
{
# if WITH_RAW
/* В RAW-режиме буфер документа готов сразу после конвертации (до индекса),
* поэтому первый экран RAW можно показать немедленно — рисуем raw_draw, а
* не MD-вид (иначе мелькал бы MD). Индекс при этом строится дальше (нужен
* для MD-вида). */
if ( g_view ! = VIEW_MD ) {
if ( ! g_first_drawn ) {
raw_draw ( ) ;
raw_status_numbers ( ) ;
g_first_drawn = 1 ;
cur_page = 0xFF ; /* raw_draw сбил W3-маппинг, на который опирается fb() */
}
spinner_tick ( ) ;
return ;
}
# endif
/* Прогрессивная отрисовка строящегося (всегда показываемого) набора. */
uint8_t touched = 0 ;
if ( ! g_first_drawn & & drawable_lines ( ) > = VIEW_H ) {
@@ -2523,20 +2425,30 @@ static void progress_tick(void)
* живых глобалах, а progress_tick через свап показывает/листает уже готовый
* g_disp_doc. Возврат: 0 — успех, <0 — нехватка EMM (-5/-6/-7).
*
* UTF-8 конвертация делается ЛЕНИВО здесь, а не до первого экрана: при входе
* (для DOC_UTF8 ещё не сконвертированного) живой буфер обязан быть = оригинал
* (8-битные байты) — utf8_convert чит ает е го через fb(). Это так и в случ ае
* первичного UTF-8 (сразу после load_file), и вторичного (после сборки
* первичного 8-битного, чей буфер = оригинал ). */
* UTF-8 набор конвертируется ИНКРЕМЕНТАЛЬНО: выделяем страницы, переключаем
* живой буфер на utf и запускаем конвертацию первого чанка; дальше index_lines
* докру чив ает е ё впереди позиции чтения (g_utf_building). Конвертер чит ает
* оригинал из orig_file_phys[] через cv_read (не через fb, который теперь
* указывает на utf ). */
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 ;
if ( ! utf_avail ) { /* первый вход: выделить страницы */
if ( ! utf_alloc ( ) ) return - 7 ;
utf_avail = 1 ;
conv_n = 0 ; conv_page = 0 ; conv_off = 0 ;
utf_src = 0 ;
g_utf_building = 1 ;
}
file_blk = utf_blk ; file_pages = utf_pages ; file_size = utf_size ;
file_blk = utf_blk ; file_pages = utf_pages ;
memcpy ( file_phys , utf_phys , MAX_PAGES ) ;
if ( g_utf_building ) {
file_size = 0 ;
cur_page = 0xFF ;
utf_convert_more ( CONV_MARGIN ) ; /* первый чанк — для первого экрана */
} else {
file_size = utf_size ;
}
} else {
file_blk = orig_file_blk ; file_pages = orig_file_pages ; file_size = orig_file_size ;
memcpy ( file_phys , orig_file_phys , MAX_PAGES ) ;
@@ -2556,6 +2468,10 @@ static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible)
index_lines ( ) ;
g_loading = 0 ;
/* Зафиксировать конвертацию (на случай обрыва индекса по max_lines до конца
* исходника) — иначе g_utf_building остался бы 1 и сломал бы следующую сборку. */
if ( g_utf_building ) { utf_size = file_size ; g_utf_building = 0 ; }
g_doc [ slot ] . built = 1 ;
doc_save_live ( slot ) ;
return 0 ;
@@ -2565,6 +2481,10 @@ static int build_doc(uint8_t slot, uint8_t enc, uint8_t visible)
* 8-бит↔8-бит внутри одного набора — мгновенный ремап; переход в другой набор
* — мгновенный doc_switch, если он построен, иначе ЛЕНИВАЯ сборка по месту
* (со спиннером), результат кэшируется. */
/* Меняет кодировку: тот же набор — ремап; другой готовый — doc_switch;
* непостроенный — ЛЕНИВАЯ ТИХАЯ сборка (спиннер, без отрисовки документа).
* НЕ перерисовывает документ/статус — это делает вызывающий в своём режиме
* (MD: draw_viewport_from_cache; RAW: raw_draw), чтобы не мелькал чужой вид. */
static void switch_encoding ( uint8_t next )
{
uint8_t target = ( next = = ENC_UTF8 ) ? DOC_UTF8 : DOC_8BIT ;
@@ -2576,20 +2496,24 @@ static void switch_encoding(uint8_t next)
set_encoding ( next ) ; g_doc [ target ] . enc = next ;
} else { /* строим набор лениво */
doc_save_live ( g_active_doc ) ; /* сохранить текущий (позиция и т.п.) */
# if WITH_RAW
g_ready = 0 ; render_menu ( ) ; /* идёт индексация — прячем F2 */
# endif
spinner_show ( 1 ) ;
int rc = build_doc ( target , next , 1 ) ; /* visible: рисует прогрессивно */
int rc = build_doc ( target , next , 1 ) ; /* visible: прогрессивный показ первого экрана
* (progress_tick рисует по текущему виду — MD/RAW) */
spinner_show ( 0 ) ;
# if WITH_RAW
g_ready = 1 ; render_menu ( ) ;
# endif
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 ( ) ;
clamp_top ( ) ; /* привести top_line к новому набору (для MD) */
}
int main ( int argc , char * * argv )
@@ -2701,6 +2625,9 @@ int main(int argc, char **argv)
* счётчиком строк. */
spinner_show ( 0 ) ;
g_f8_enabled = 1 ;
# if WITH_RAW
g_ready = 1 ; /* документ построен — теперь доступен F2 (RAW) */
# endif
render_menu ( ) ;
clamp_top ( ) ;
render_full_status ( ) ;
@@ -2715,6 +2642,65 @@ int main(int argc, char **argv)
if ( ascii = = 0x1B ) break ; /* клавиша Esc */
continue ;
}
# if WITH_RAW
/* F2 — тумблер RAW ↔ MD (рабочий только после постройки документа). */
if ( scan = = KEY_F2 ) {
if ( ! g_ready ) continue ;
if ( g_view = = VIEW_MD ) {
g_view = g_raw_last ; /* → RAW (последний под-режим) */
raw_seed_from ( top_line ) ; /* позиция из MD */
raw_screen_init ( ) ;
render_full_status ( ) ; render_menu ( ) ;
raw_status_numbers ( ) ; raw_draw ( ) ;
} else {
g_raw_last = g_view ; /* запомнить под-режим RAW */
g_view = VIEW_MD ;
clamp_top ( ) ;
render_full_status ( ) ; render_menu ( ) ;
draw_viewport_from_cache ( ) ;
}
continue ;
}
/* RAW-режим: F3 Wrap/Unwrap, своя навигация; F8 — кодировка; F1/F10. */
if ( g_view ! = VIEW_MD ) {
if ( scan = = KEY_F10 ) goto exit_loop ;
if ( scan = = KEY_F1 ) {
show_help ( ) ;
raw_screen_init ( ) ;
render_full_status ( ) ; render_menu ( ) ;
raw_status_numbers ( ) ; raw_draw ( ) ;
continue ;
}
if ( scan = = KEY_F3 ) { /* Wrap ↔ Unwrap */
g_view = ( g_view = = VIEW_RAW_WRAP ) ? VIEW_RAW_HSCROLL : VIEW_RAW_WRAP ;
g_raw_last = g_view ;
raw_renorm ( ) ;
raw_screen_init ( ) ;
render_menu ( ) ;
raw_status_numbers ( ) ; raw_draw ( ) ;
continue ;
}
if ( scan = = KEY_F8 ) {
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 ;
uint8_t target = ( next = = ENC_UTF8 ) ? DOC_UTF8 : DOC_8BIT ;
/* другой буфер (в/из UTF-8) → в начало ДО сборки, чтобы прогрессивный
* raw_draw в progress_tick показывал верх нового набора. */
if ( target ! = g_active_doc ) raw_home ( ) ;
switch_encoding ( next ) ; /* меняет набор/ремап */
if ( g_abort ) goto exit_loop ;
render_full_status ( ) ; raw_status_numbers ( ) ; raw_draw ( ) ;
continue ;
}
raw_key ( scan ) ;
continue ;
}
# endif /* WITH_RAW */
switch ( scan ) {
case KEY_F1 : show_help ( ) ; break ;
case KEY_F8 : { /* цикл CP866→CP1251→KOI8R→UTF8→CP866; нужный набор дособерётся лениво */
@@ -2724,6 +2710,8 @@ int main(int argc, char **argv)
if ( next = = ENC_UTF8 & & g_utf_failed ) next = ENC_CP866 ; /* UTF-8 недоступен */
switch_encoding ( next ) ;
if ( g_abort ) goto exit_loop ; /* Esc/F10 во время ленивой досборки */
render_full_status ( ) ; /* перерисовка — в MD-режиме */
draw_viewport_from_cache ( ) ;
continue ;
}
case KEY_F10 : goto exit_loop ;