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
pull/795/head
Nick Black 4 years ago committed by GitHub
parent 62f6896715
commit 50ce76bad8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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);

@ -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…
Cancel
Save