mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-08 23:59:02 -05:00
Add kernel unicode-to-font mappings dynamically for line-drawing characters (#794)
Part 1 of a two-part revolution in Linux console graphics. Map all the line-drawing Unicode characters to similar glyphs. This means all our nice corners freely translate into rigid corners etc in the console, rather than hateful default characters (usually black diamonds). The demo and all widgets now look correct when drawing lines and boxes. Next, we'll add the actual glyphs for the block-drawing characters, and we'll have the finest graphics ever seen on a text-mode Linux console. #201
This commit is contained in:
parent
62f6896715
commit
50ce76bad8
Binary file not shown.
Before Width: | Height: | Size: 408 KiB After Width: | Height: | Size: 146 KiB |
Binary file not shown.
Before Width: | Height: | Size: 980 KiB After Width: | Height: | Size: 239 KiB |
Binary file not shown.
Before Width: | Height: | Size: 570 KiB After Width: | Height: | Size: 204 KiB |
Binary file not shown.
@ -804,7 +804,7 @@ nc_err_e ncvisual_blit(struct ncvisual* ncv, int rows, int cols,
|
||||
|
||||
void nclog(const char* fmt, ...);
|
||||
|
||||
bool is_linux_console(const notcurses* nc);
|
||||
bool is_linux_console(const notcurses* nc, unsigned no_font_changes);
|
||||
|
||||
// get a file descriptor for the controlling tty device, -1 on error
|
||||
int get_controlling_tty(void);
|
||||
@ -830,6 +830,11 @@ int get_controlling_tty(void);
|
||||
if((nc)->loglevel >= NCLOGLEVEL_DEBUG){ \
|
||||
nclog("%s:%d:" fmt, __func__, __LINE__, ##__VA_ARGS__); } }while(0);
|
||||
|
||||
#define logtrace(nc, fmt, ...) do{ \
|
||||
if((nc)->loglevel >= NCLOGLEVEL_TRACE){ \
|
||||
nclog("%s:%d:" fmt, __func__, __LINE__, ##__VA_ARGS__); } }while(0);
|
||||
|
||||
|
||||
// Convert a notcurses log level to some multimedia library equivalent.
|
||||
int ffmpeg_log_level(ncloglevel_e level);
|
||||
|
||||
|
279
src/lib/linux.c
279
src/lib/linux.c
@ -4,19 +4,294 @@
|
||||
#include <linux/kd.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
static unsigned char*
|
||||
get_glyph(struct consolefontdesc* cfd, unsigned idx){
|
||||
if(idx >= cfd->charcount){
|
||||
return NULL;
|
||||
}
|
||||
return (unsigned char*)cfd->chardata + 32 * idx;
|
||||
}
|
||||
|
||||
static int // insert U+2580 (upper half block)
|
||||
shim_upper_half_block(struct consolefontdesc* cfd, unsigned idx){
|
||||
unsigned char* glyph = get_glyph(cfd, idx);
|
||||
if(glyph == NULL){
|
||||
return -1;
|
||||
}
|
||||
unsigned r;
|
||||
for(r = 0 ; r < cfd->charheight / 2 ; ++r, ++glyph){
|
||||
*glyph = 0xff;
|
||||
}
|
||||
while(r < cfd->charheight){
|
||||
*glyph = 0;
|
||||
++glyph;
|
||||
++r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int // insert U+2584 (lower half block)
|
||||
shim_lower_half_block(struct consolefontdesc* cfd, unsigned idx){
|
||||
unsigned char* glyph = get_glyph(cfd, idx);
|
||||
if(glyph == NULL){
|
||||
return -1;
|
||||
}
|
||||
unsigned r;
|
||||
for(r = 0 ; r < cfd->charheight / 2 ; ++r, ++glyph){
|
||||
*glyph = 0;
|
||||
}
|
||||
while(r < cfd->charheight){
|
||||
*glyph = 0xff;
|
||||
++glyph;
|
||||
++r;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int // insert U+258c (left half block)
|
||||
shim_left_half_block(struct consolefontdesc* cfd, unsigned idx){
|
||||
unsigned char* glyph = get_glyph(cfd, idx);
|
||||
if(glyph == NULL){
|
||||
return -1;
|
||||
}
|
||||
for(unsigned r = 0 ; r < cfd->charheight ; ++r, ++glyph){
|
||||
*glyph = 0xf0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int // insert U+2590 (right half block)
|
||||
shim_right_half_block(struct consolefontdesc* cfd, unsigned idx){
|
||||
unsigned char* glyph = get_glyph(cfd, idx);
|
||||
if(glyph == NULL){
|
||||
return -1;
|
||||
}
|
||||
for(unsigned r = 0 ; r < cfd->charheight ; ++r, ++glyph){
|
||||
*glyph = 0x0f;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int // insert U+2598 (quadrant upper left)
|
||||
shim_upper_left_quad(struct consolefontdesc* cfd, unsigned idx){
|
||||
unsigned char* glyph = get_glyph(cfd, idx);
|
||||
if(glyph == NULL){
|
||||
return -1;
|
||||
}
|
||||
for(unsigned r = cfd->charheight / 2 ; r < cfd->charheight ; ++r, ++glyph){
|
||||
*glyph = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int // insert U+259D (quadrant upper right)
|
||||
shim_upper_right_quad(struct consolefontdesc* cfd, unsigned idx){
|
||||
unsigned char* glyph = get_glyph(cfd, idx);
|
||||
if(glyph == NULL){
|
||||
return -1;
|
||||
}
|
||||
for(unsigned r = 0 ; r < cfd->charheight / 2 ; ++r, ++glyph){
|
||||
*glyph = 0x0f;
|
||||
}
|
||||
for(unsigned r = cfd->charheight / 2 ; r < cfd->charheight ; ++r, ++glyph){
|
||||
*glyph = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int // insert U+2598 (quadrant lower left)
|
||||
shim_lower_left_quad(struct consolefontdesc* cfd, unsigned idx){
|
||||
unsigned char* glyph = get_glyph(cfd, idx);
|
||||
if(glyph == NULL){
|
||||
return -1;
|
||||
}
|
||||
for(unsigned r = 0 ; r < cfd->charheight / 2 ; ++r, ++glyph){
|
||||
*glyph = 0;
|
||||
}
|
||||
for(unsigned r = cfd->charheight / 2 ; r < cfd->charheight ; ++r, ++glyph){
|
||||
*glyph = 0xf0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int // insert U+2597 (quadrant lower right)
|
||||
shim_lower_right_quad(struct consolefontdesc* cfd, unsigned idx){
|
||||
unsigned char* glyph = get_glyph(cfd, idx);
|
||||
if(glyph == NULL){
|
||||
return -1;
|
||||
}
|
||||
for(unsigned r = 0 ; r < cfd->charheight / 2 ; ++r, ++glyph){
|
||||
*glyph = 0;
|
||||
}
|
||||
for(unsigned r = cfd->charheight / 2 ; r < cfd->charheight ; ++r, ++glyph){
|
||||
*glyph = 0x0f;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
program_line_drawing_chars(const notcurses* nc, struct unimapdesc* map){
|
||||
struct simset {
|
||||
wchar_t* ws;
|
||||
} sets[] = {
|
||||
{
|
||||
.ws = L"/╱",
|
||||
}, {
|
||||
.ws = L"\\╲",
|
||||
}, {
|
||||
.ws = L"X╳",
|
||||
}, {
|
||||
.ws = L"└┕┖┗╘╙╚╰",
|
||||
}, {
|
||||
.ws = L"┘┙┚┛╛╜╝╯",
|
||||
}, {
|
||||
.ws = L"┌┍┎┏╒╓╔╭",
|
||||
}, {
|
||||
.ws = L"┐┑┒┓╕╖╗╮",
|
||||
}, {
|
||||
.ws = L"─━┄┅┈┉╌╍═╼╾",
|
||||
}, {
|
||||
.ws = L"│┃┆┇┊┋╎╏║╽╿",
|
||||
}, {
|
||||
.ws = L"├┝┞┟┠┡┢┣╞╟╠",
|
||||
}, {
|
||||
.ws = L"┤┥┦┧┨┩┪┫╡╢╣",
|
||||
}, {
|
||||
.ws = L"┬┭┮┯┰┱┲┳╤╥╦",
|
||||
}, {
|
||||
.ws = L"┴┵┶┷┸┹┺┻╧╨╩",
|
||||
}, {
|
||||
.ws = L"┼┽┾┿╀╁╂╃╄╅╆╇╈╉╊╋╪╫╬",
|
||||
},
|
||||
};
|
||||
int toadd = 0;
|
||||
for(size_t sidx = 0 ; sidx < sizeof(sets) / sizeof(*sets) ; ++sidx){
|
||||
int fontidx = -1;
|
||||
struct simset* s = &sets[sidx];
|
||||
bool found[wcslen(s->ws)];
|
||||
memset(found, 0, sizeof(found));
|
||||
for(unsigned idx = 0 ; idx < map->entry_ct ; ++idx){
|
||||
for(size_t widx = 0 ; widx < wcslen(s->ws) ; ++widx){
|
||||
if(map->entries[idx].unicode == s->ws[widx]){
|
||||
logtrace(nc, "Found desired character U+%04x -> %03u\n",
|
||||
map->entries[idx].unicode, map->entries[idx].fontpos);
|
||||
found[widx] = true;
|
||||
if(fontidx == -1){
|
||||
fontidx = map->entries[idx].fontpos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(fontidx > -1){
|
||||
for(size_t widx = 0 ; widx < wcslen(s->ws) ; ++widx){
|
||||
if(!found[widx]){
|
||||
logdebug(nc, "Adding mapping U+%04x -> %03u\n", s->ws[widx], fontidx);
|
||||
struct unipair* tmp = realloc(map->entries, sizeof(*map->entries) * (map->entry_ct + 1));
|
||||
if(tmp == NULL){
|
||||
return -1;
|
||||
}
|
||||
map->entries = tmp;
|
||||
map->entries[map->entry_ct].unicode = s->ws[widx];
|
||||
map->entries[map->entry_ct].fontpos = fontidx;
|
||||
++map->entry_ct;
|
||||
++toadd;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
logwarning(nc, "Couldn't find any glyphs for set %zu\n", sidx);
|
||||
}
|
||||
}
|
||||
if(toadd == 0){
|
||||
return 0;
|
||||
}
|
||||
if(ioctl(nc->ttyfd, PIO_UNIMAP, map)){
|
||||
logwarning(nc, "Error setting kernel unicode map (%s)\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
loginfo(nc, "Successfully added %d kernel unicode mapping%s\n",
|
||||
toadd, toadd == 1 ? "" : "s");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
reprogram_linux_font(const notcurses* nc, struct consolefontdesc* cfd,
|
||||
struct unimapdesc* map){
|
||||
if(ioctl(nc->ttyfd, GIO_FONTX, cfd)){
|
||||
logwarning(nc, "Error reading Linux kernelfont (%s)\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
loginfo(nc, "Kernel font size (glyphcount): %hu\n", cfd->charcount);
|
||||
loginfo(nc, "Kernel font character geometry: 8x%hu\n", cfd->charheight);
|
||||
if(cfd->charcount > 512){
|
||||
logwarning(nc, "Warning: kernel returned excess charcount\n");
|
||||
return -1;
|
||||
}
|
||||
if(ioctl(nc->ttyfd, GIO_UNIMAP, map)){
|
||||
logwarning(nc, "Error reading Linux unimap (%s)\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
loginfo(nc, "Kernel Unimap size: %hu/%hu\n", map->entry_ct, USHRT_MAX);
|
||||
// for certain sets of characters, we're not going to draw them in, but we
|
||||
// do want to ensure they map to something plausible...
|
||||
if(program_line_drawing_chars(nc, map)){
|
||||
return -1;
|
||||
}
|
||||
for(unsigned idx = 0 ; idx < map->entry_ct ; ++idx){
|
||||
// FIXME check to see if our desired codepoints already map
|
||||
// if already declared, trust it?
|
||||
// if not, see if we ought add one or reuse something
|
||||
// if we want to add, find a good place
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
reprogram_console_font(const notcurses* nc){
|
||||
struct consolefontdesc cfd = {};
|
||||
cfd.charcount = 512;
|
||||
size_t totsize = 32 * cfd.charcount;
|
||||
cfd.chardata = malloc(totsize);
|
||||
if(cfd.chardata == NULL){
|
||||
logwarning(nc, "Error acquiring %zub for font descriptors (%s)\n", totsize, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
struct unimapdesc map = {};
|
||||
map.entry_ct = USHRT_MAX;
|
||||
totsize = map.entry_ct * sizeof(struct unipair);
|
||||
map.entries = malloc(totsize);
|
||||
if(map.entries == NULL){
|
||||
logwarning(nc, "Error acquiring %zub for Unicode font map (%s)\n", totsize, strerror(errno));
|
||||
free(cfd.chardata);
|
||||
return -1;
|
||||
}
|
||||
int r = reprogram_linux_font(nc, &cfd, &map);
|
||||
free(cfd.chardata);
|
||||
free(map.entries);
|
||||
return r;
|
||||
}
|
||||
|
||||
// is the provided fd a Linux console?
|
||||
bool is_linux_console(const notcurses* nc){
|
||||
bool is_linux_console(const notcurses* nc, unsigned no_font_changes){
|
||||
if(nc->ttyfd < 0){
|
||||
return false;
|
||||
}
|
||||
int mode, r;
|
||||
if( (r = ioctl(nc->ttyfd, KDGETMODE, &mode)) ){
|
||||
logdebug(nc, "Not a Linux console, KDGETMODE failed\n");
|
||||
return false;
|
||||
}
|
||||
loginfo(nc, "Verified Linux console, mode %d\n", mode);
|
||||
if(no_font_changes){
|
||||
logdebug(nc, "Not reprogramming the console font due to option\n");
|
||||
return true;
|
||||
}
|
||||
reprogram_console_font(nc);
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
bool is_linux_console(const notcurses* nc){
|
||||
bool is_linux_console(const notcurses* nc, unsigned no_font_changes){
|
||||
(void)nc;
|
||||
(void)no_font_changes;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
@ -822,7 +822,7 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
|
||||
return NULL;
|
||||
}
|
||||
ret->ttyfd = get_tty_fd(ret, ret->ttyfp);
|
||||
is_linux_console(ret);
|
||||
is_linux_console(ret, !!(opts->flags & NCOPTION_NO_FONT_CHANGES));
|
||||
notcurses_mouse_disable(ret);
|
||||
if(ret->ttyfd >= 0){
|
||||
if(tcgetattr(ret->ttyfd, &ret->tpreserved)){
|
||||
|
@ -4,8 +4,7 @@ void DrawBackground(const std::string& s) { // drawn to the standard plane
|
||||
backg_->decode();
|
||||
ncvisual_options opts{};
|
||||
opts.scaling = NCSCALE_STRETCH;
|
||||
auto p = backg_->render(&opts);
|
||||
ncplane_greyscale(p);
|
||||
backg_->render(&opts);
|
||||
}
|
||||
|
||||
void DrawBoard() { // draw all fixed components of the game
|
||||
|
Loading…
x
Reference in New Issue
Block a user