/* * 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 #include #include #include #include "mdview2.h" #if WITH_RAW /* весь модуль — опционален (mdview_conf.h); при 0 пустой объектник */ static uint32_t raw_top = 0; /* байт-offset верхней строки экрана */ static uint16_t raw_hpan = 0; /* гориз. сдвиг (только 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 */ } /* WRAP: начало предыдущего ряда. */ // static uint32_t raw_prev_wrap(uint32_t off) // { // if (off == 0) return 0; // uint32_t ls = raw_line_start(off); // if (off > ls) /* тот же абзац: на 80 назад */ // return off - SCREEN_W; // /* off == начало строки: последний ряд предыдущей физической строки. // * Последнюю 80-границу ищем циклом (без 32-битного деления). */ // uint32_t pls = raw_line_start(off - 1); /* off-1 — это \n пред. строки */ // uint32_t last = pls; // while (last + SCREEN_W < (off - 1)) last += SCREEN_W; // return last; // } 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; // uint32_t p = off - 1; /* \n, завершивший пред. строку */ // if (p == 0) return 0; // p--; // while (p != 0 && fb(p) != '\n') p--; // return (fb(p) == '\n') ? p + 1 : 0; // } 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++; // } // for (; i < SCREEN_W; i++) raw_buf[i] = ' '; // if (raw_hpan > 0) raw_buf[0] = '<'; // if (len > (uint16_t)raw_hpan + SCREEN_W) raw_buf[SCREEN_W - 1] = '>'; // raw_flush(row); // return (e < file_size) ? e + 1 : e; // } 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); } static void raw_scroll_down1(void) { /* Скроллим вниз ТОЛЬКО если контент уходит за нижний край. Проходим VIEW_H * рядов от raw_top: если контент кончился раньше (экран не полон) — внизу * была бы пустая строка, не скроллим; если ровно по нижний ряд (off==EOF) — * ниже пусто, тоже не скроллим. Иначе off = ряд сразу под экраном. */ 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; /* контент ровно по экран, ниже пусто */ scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0); /* содержимое вверх */ raw_draw_one(off, (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1)); /* новый нижний ряд */ raw_top = raw_next(raw_top); } static void raw_scroll_up1(void) { if (raw_top == 0) return; uint32_t nt = raw_prev(raw_top); scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0); /* содержимое вниз */ (void)raw_draw_one(nt, VIEW_TOP_ROW); raw_top = nt; } /* ---- статус-бар (без подсчёта строк: процент по байтам + режим) ---- */ /* Процент по байтам без 32-битного деления: масштабируем offset'ы в единицы * по 1 КБ (>>10), дальше 16-битная арифметика (a*100 ≤ 12800). */ static uint8_t raw_pct(void) { uint16_t a = (uint16_t)(raw_top >> 10); uint16_t b = (uint16_t)(file_size >> 10); if (b == 0) return 0; /* файл < 1 КБ */ return (uint8_t)(a * 100u / b); } void raw_status_numbers(void) { uint8_t pct = raw_pct(); textattr(ATTR_BAR); gotoxy(47, 0); bios_writeattr_until(g_view == VIEW_RAW_WRAP ? "RAW wrap" : "RAW pan", 20, ATTR_BAR, 0); gotoxy(73, 0); dec8(pct); cputs("% "); } /* ---- посев позиции / пересев при смене активного буфера ----------- */ void raw_seed_from(uint16_t md_top_line) { uint32_t off = seg_off(md_top_line); if (off > file_size) off = 0; raw_top = raw_line_start(off); raw_hpan = 0; } /* Сброс в начало — при смене активного буфера (F8 в/из UTF-8: размеры/offset'ы * разные, старый raw_top невалиден). Без percent-математики. */ void raw_home(void) { raw_top = 0; 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; } /* ---- навигация (вызывается из главного цикла при g_view != VIEW_MD) - */ uint8_t raw_key(uint8_t scan) { uint32_t nt; switch (scan) { case KEY_DOWN: raw_scroll_down1(); raw_status_numbers(); return 1; case KEY_UP: raw_scroll_up1(); raw_status_numbers(); return 1; case KEY_PGDN: nt = raw_top; for (uint8_t k = 0; k < VIEW_H; k++) { uint32_t n2 = raw_next(nt); if (n2 >= file_size) break; nt = n2; } break; case KEY_PGUP: nt = raw_top; for (uint8_t k = 0; k < VIEW_H; k++) nt = raw_prev(nt); break; case KEY_HOME: nt = 0; raw_hpan = 0; break; case KEY_END: nt = file_size; /* отмотать VIEW_H рядов назад */ for (uint8_t k = 0; k < VIEW_H; k++) nt = raw_prev(nt); raw_hpan = 0; 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(); raw_status_numbers(); } return 1; case KEY_RIGHT: if (g_view == VIEW_RAW_HSCROLL) { if (raw_hpan < 248u) raw_hpan = (uint16_t)(raw_hpan + HPAN_STEP); raw_draw(); raw_status_numbers(); } return 1; default: return 0; /* не наша клавиша */ } raw_top = nt; raw_draw(); /* PgUp/PgDn/Home/End — полный экран */ raw_status_numbers(); return 1; } #endif /* WITH_RAW */