mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
* unidamage PoC * add cell_load_simple() * clear CELL_WIDEASIAN_MASK in cell_load() * split out render code * add CELL_SIMPLE_INITIALIZER * widecolor: fix message plane * widecolor: simplify color increments * document wide character handling * unit tests for wide obliteration #117 * widechar obliteration hardening #117 * widecolor -> widechomper, update man page
This commit is contained in:
parent
040607c6f9
commit
957549105b
53
README.md
53
README.md
@ -14,8 +14,8 @@ by [nick black](https://nick-black.com/dankwiki/index.php/Hack_on) (<nickblack@l
|
||||
* [Requirements](#requirements)
|
||||
* [Use](#use)
|
||||
* [Input](#input)
|
||||
* [Planes](#planes)
|
||||
* [Cells](#cells)
|
||||
* [Planes](#planes) ([Plane Channels API](#plane-channels-api), [Wide chars](#wide-chars))
|
||||
* [Cells](#cells) ([Cell Channels API](#cell-channels-api))
|
||||
* [Multimedia](#multimedia)
|
||||
* [Panelreels](#panelreels)
|
||||
* [Channels](#channels)
|
||||
@ -467,6 +467,10 @@ not necessarily reflect anything on the actual screen).
|
||||
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
|
||||
int ncplane_at_cursor(struct ncplane* n, cell* c);
|
||||
|
||||
// Retrieve the cell at the specified location on the specified plane, returning
|
||||
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
|
||||
int ncplane_at_yx(struct ncplane* n, int y, int x, cell* c);
|
||||
|
||||
// Manipulate the opaque user pointer associated with this plane.
|
||||
// ncplane_set_userptr() returns the previous userptr after replacing
|
||||
// it with 'opaque'. the others simply return the userptr.
|
||||
@ -868,6 +872,32 @@ void ncplane_set_fg_default(struct ncplane* n);
|
||||
void ncplane_set_bg_default(struct ncplane* n);
|
||||
```
|
||||
|
||||
#### Wide chars
|
||||
|
||||
Notcurses assumes that all glyphs occupy widths which are an integral multiple
|
||||
of the smallest possible glyph's cell width (aka a "fixed-width font"). Unicode
|
||||
introduces characters which generally occupy two such cells, known as wide
|
||||
characters (though in the end, width of a glyph is a property of the font). It
|
||||
is not possible to print half of such a glyph, nor is it generally possible to
|
||||
print a wide glyph on the last column of a terminal.
|
||||
|
||||
Notcurses does not consider it an error to place a wide character on the last
|
||||
column of a line. It will obliterate any content which was in that cell, but
|
||||
will not itself be rendered. The default content will not be reproduced in such
|
||||
a cell, either. When any character is placed atop a wide character's left or
|
||||
right half, the wide character is obliterated in its entirety. When a wide
|
||||
character is placed, any character under its left or right side is annihilated,
|
||||
including wide characters. It is thus possible for two wide characters to sit
|
||||
at columns 0 and 2, and for both to be obliterated by a single wide character
|
||||
placed at column 1.
|
||||
|
||||
Likewise, when rendering, a plane which would partially obstruct a wide glyph
|
||||
prevents it from being rendered entirely. A pathological case would be that of
|
||||
a terminal _n_ columns in width, containing _n-1_ planes, each 2 columns wide.
|
||||
The planes are placed at offsets [0..n - 2]. Each plane is above the plane to
|
||||
its left, and each plane contains a single wide character. Were this to be
|
||||
rendered, only the rightmost plane (and its single glyph) would be rendered!
|
||||
|
||||
### Cells
|
||||
|
||||
Unlike the `notcurses` or `ncplane` objects, the definition of `cell` is
|
||||
@ -932,12 +962,14 @@ typedef struct cell {
|
||||
#define CELL_ALPHA_OPAQUE 0
|
||||
```
|
||||
|
||||
`cell`s must be initialized with `CELL_TRIVIAL_INITIALIZER` or `cell_init()`
|
||||
before any other use (both merely zero out the `cell`).
|
||||
`cell`s must be initialized with an initialization macro or `cell_init()`
|
||||
before any other use. `cell_init()` and `CELL_TRIVIAL_INITIALIZER` both
|
||||
simply zero out the `cell`.
|
||||
|
||||
```c
|
||||
#define CELL_TRIVIAL_INITIALIZER { .gcluster = '\0', .attrword = 0, .channels = 0, }
|
||||
#define CELL_SIMPLE_INITIALIZER(c) { .gcluster = c, .attrword = 0, .channels = 0, }
|
||||
#define CELL_SIMPLE_INITIALIZER(c) { .gcluster = (c), .attrword = 0, .channels = 0, }
|
||||
#define CELL_INITIALIZER(c, a, chan) { .gcluster = (c), .attrword = (a), .channels = (chan), }
|
||||
|
||||
static inline void
|
||||
cell_init(cell* c){
|
||||
@ -1036,6 +1068,17 @@ cell_simple_p(const cell* c){
|
||||
return c->gcluster < 0x80;
|
||||
}
|
||||
|
||||
static inline int
|
||||
cell_load_simple(struct ncplane* n, cell* c, char ch){
|
||||
cell_release(n, c);
|
||||
c->channels &= ~CELL_WIDEASIAN_MASK;
|
||||
c->gcluster = ch;
|
||||
if(cell_simple_p(c)){
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// get the offset into the egcpool for this cell's EGC. returns meaningless and
|
||||
// unsafe results if called on a simple cell.
|
||||
static inline uint32_t
|
||||
|
@ -27,25 +27,25 @@ contains a set of text-based demonstrations of capabilities from the notcurses l
|
||||
.P
|
||||
(i)ntro—a setting of tone
|
||||
.P
|
||||
(s)liders—a missing-piece puzzle made up of colorful blocks
|
||||
.P
|
||||
(u)niblocks—a series of blocks detailing Unicode pages
|
||||
.P
|
||||
(m)axcolors—smoothly changing colors
|
||||
.P
|
||||
(l)uigi-a dashing plumber of Apennine persuasion
|
||||
.P
|
||||
(u)niblocks—a series of blocks detailing Unicode pages
|
||||
.P
|
||||
(b)oxes—pulsating boxes with a transparent center
|
||||
.P
|
||||
(g)rid—a gradient of color lain atop a great grid
|
||||
.P
|
||||
(w)idecolors—letters of many languages in many colors
|
||||
(s)liders—a missing-piece puzzle made up of colorful blocks
|
||||
.P
|
||||
(v)iew—in which PNGs are rendered as text, and a video, too
|
||||
(w)idechomper—a gremlin feasts upon wide characters
|
||||
.P
|
||||
(v)iew—images and a video are rendered as text
|
||||
.P
|
||||
(p)anelreels—demonstration of the panelreel high-level widget
|
||||
.P
|
||||
(o)utro—a message of hope from the library author
|
||||
(o)utro—a message of hope from the library's author
|
||||
.SH NOTES
|
||||
Proper display requires a terminal advertising the RGB terminfo(5) capability (necessary for specification of arbitrary 24bpp colors), and a monospaced font with good Unicode support.
|
||||
.SH SEE ALSO
|
||||
|
@ -379,6 +379,10 @@ API struct ncplane* ncplane_below(struct ncplane* n);
|
||||
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
|
||||
API int ncplane_at_cursor(struct ncplane* n, cell* c);
|
||||
|
||||
// Retrieve the cell at the specified location on the specified plane, returning
|
||||
// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased.
|
||||
API int ncplane_at_yx(struct ncplane* n, int y, int x, cell* c);
|
||||
|
||||
// Manipulate the opaque user pointer associated with this plane.
|
||||
// ncplane_set_userptr() returns the previous userptr after replacing
|
||||
// it with 'opaque'. the others simply return the userptr.
|
||||
@ -1101,7 +1105,8 @@ API int ncplane_fadein(struct ncplane* n, const struct timespec* ts);
|
||||
// Working with cells
|
||||
|
||||
#define CELL_TRIVIAL_INITIALIZER { .gcluster = '\0', .attrword = 0, .channels = 0, }
|
||||
#define CELL_SIMPLE_INITIALIZER(c) { .gcluster = c, .attrword = 0, .channels = 0, }
|
||||
#define CELL_SIMPLE_INITIALIZER(c) { .gcluster = (c), .attrword = 0, .channels = 0, }
|
||||
#define CELL_INITIALIZER(c, a, chan) { .gcluster = (c), .attrword = (a), .channels = (chan), }
|
||||
|
||||
static inline void
|
||||
cell_init(cell* c){
|
||||
@ -1201,6 +1206,17 @@ cell_simple_p(const cell* c){
|
||||
return c->gcluster < 0x80;
|
||||
}
|
||||
|
||||
static inline int
|
||||
cell_load_simple(struct ncplane* n, cell* c, char ch){
|
||||
cell_release(n, c);
|
||||
c->channels &= ~CELL_WIDEASIAN_MASK;
|
||||
c->gcluster = ch;
|
||||
if(cell_simple_p(c)){
|
||||
return 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// get the offset into the egcpool for this cell's EGC. returns meaningless and
|
||||
// unsafe results if called on a simple cell.
|
||||
static inline uint32_t
|
||||
|
@ -52,7 +52,7 @@ usage(const char* exe, int status){
|
||||
fprintf(out, " s: run shuffle\n");
|
||||
fprintf(out, " u: run uniblock\n");
|
||||
fprintf(out, " v: run view\n");
|
||||
fprintf(out, " w: run widecolors\n");
|
||||
fprintf(out, " w: run widechomper\n");
|
||||
exit(status);
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ ext_demos(struct notcurses* nc, const char* demos){
|
||||
case 'g': ret = grid_demo(nc); break;
|
||||
case 'l': ret = luigi_demo(nc); break;
|
||||
case 'v': ret = view_demo(nc); break;
|
||||
case 'w': ret = widecolor_demo(nc); break;
|
||||
case 'w': ret = widechomper_demo(nc); break;
|
||||
case 'p': ret = panelreel_demo(nc); break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown demo specification: %c\n", *demos);
|
||||
|
@ -12,7 +12,7 @@ extern "C" {
|
||||
extern struct timespec demodelay;
|
||||
|
||||
int unicodeblocks_demo(struct notcurses* nc);
|
||||
int widecolor_demo(struct notcurses* nc);
|
||||
int widechomper_demo(struct notcurses* nc);
|
||||
int box_demo(struct notcurses* nc);
|
||||
int maxcolor_demo(struct notcurses* nc);
|
||||
int grid_demo(struct notcurses* nc);
|
||||
|
@ -102,6 +102,9 @@ gridswitch_demo(struct notcurses* nc, struct ncplane *n){
|
||||
// center
|
||||
for(y = 1 ; y < maxy - 1 ; ++y){
|
||||
x = 0;
|
||||
if(ncplane_cursor_move_yx(n, y, x)){
|
||||
return -1;
|
||||
}
|
||||
cell_set_fg_rgb(&cl, 255 - rs * y, 255 - gs * (x + y), 255 - bs * x);
|
||||
cell_set_bg_rgb(&cl, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
|
||||
ncplane_putc(n, &cl);
|
||||
@ -117,6 +120,9 @@ gridswitch_demo(struct notcurses* nc, struct ncplane *n){
|
||||
|
||||
// bottom line
|
||||
x = 0;
|
||||
if(ncplane_cursor_move_yx(n, y, x)){
|
||||
return -1;
|
||||
}
|
||||
cell_set_fg_rgb(&ll, 255 - rs * y, 255 - gs * (x + y), 255 - bs * x);
|
||||
cell_set_bg_rgb(&ll, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
|
||||
ncplane_putc(n, &ll);
|
||||
@ -167,6 +173,9 @@ gridinv_demo(struct notcurses* nc, struct ncplane *n){
|
||||
// center
|
||||
for(y = 1 ; y < maxy - 1 ; ++y){
|
||||
x = 0;
|
||||
if(ncplane_cursor_move_yx(n, y, x)){
|
||||
return -1;
|
||||
}
|
||||
cell_set_fg_rgb(&cl, 0, 0, 0);
|
||||
cell_set_bg_rgb(&cl, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
|
||||
ncplane_putc(n, &cl);
|
||||
@ -182,6 +191,9 @@ gridinv_demo(struct notcurses* nc, struct ncplane *n){
|
||||
|
||||
// bottom line
|
||||
x = 0;
|
||||
if(ncplane_cursor_move_yx(n, y, x)){
|
||||
return -1;
|
||||
}
|
||||
cell_set_fg_rgb(&ll, 0, 0, 0);
|
||||
cell_set_bg_rgb(&ll, 255 - rs * x, 255 - gs * (x + y), 255 - bs * y);
|
||||
ncplane_putc(n, &ll);
|
||||
@ -233,6 +245,9 @@ int grid_demo(struct notcurses* nc){
|
||||
// center
|
||||
for(y = 1 ; y < maxy - 1 ; ++y){
|
||||
x = 0;
|
||||
if(ncplane_cursor_move_yx(n, y, x)){
|
||||
return -1;
|
||||
}
|
||||
cell_set_bg_rgb(&cl, y, y, y);
|
||||
cell_set_bg_rgb(&cc, y, y, y);
|
||||
cell_set_bg_rgb(&cr, y, y, y);
|
||||
@ -248,6 +263,9 @@ int grid_demo(struct notcurses* nc){
|
||||
|
||||
// bottom line
|
||||
x = 0;
|
||||
if(ncplane_cursor_move_yx(n, y, x)){
|
||||
return -1;
|
||||
}
|
||||
cell_set_bg_rgb(&ll, y, y, y);
|
||||
cell_set_bg_rgb(&lc, y, y, y);
|
||||
cell_set_bg_rgb(&lr, y, y, y);
|
||||
|
@ -7,6 +7,9 @@
|
||||
#include <pthread.h>
|
||||
#include "demo.h"
|
||||
|
||||
// Fill up the screen with as much crazy Unicode as we can, and then set a
|
||||
// gremlin loose, looking to eat up all the wide characters.
|
||||
|
||||
// FIXME throw this in there somehow
|
||||
// ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ⎧⎡⎛┌─────┐⎞⎤⎫
|
||||
// ⎪⎢⎜│a²+b³ ⎟⎥⎪
|
||||
@ -283,6 +286,7 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total,
|
||||
int bytes_out, int egs_out, int cols_out){
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
cell_load(n, &c, " ");
|
||||
cell_set_fg_alpha(&c, CELL_ALPHA_TRANS);
|
||||
cell_set_bg_alpha(&c, CELL_ALPHA_TRANS);
|
||||
ncplane_set_default(n, &c);
|
||||
cell_release(n, &c);
|
||||
@ -297,7 +301,7 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total,
|
||||
}
|
||||
// bottom handle
|
||||
ncplane_cursor_move_yx(n, 4, 17);
|
||||
ncplane_putegc(n, "┬", 0, 0, NULL);
|
||||
ncplane_putegc(n, "┬", 0, channels, NULL);
|
||||
ncplane_cursor_move_yx(n, 5, 17);
|
||||
ncplane_putegc(n, "│", 0, channels, NULL);
|
||||
ncplane_cursor_move_yx(n, 6, 17);
|
||||
@ -344,7 +348,7 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total,
|
||||
}
|
||||
|
||||
// Much of this text comes from http://kermitproject.org/utf8.html
|
||||
int widecolor_demo(struct notcurses* nc){
|
||||
int widechomper_demo(struct notcurses* nc){
|
||||
static const char* strs[] = {
|
||||
"Война и мир",
|
||||
"Бра́тья Карама́зовы",
|
||||
@ -575,9 +579,8 @@ int widecolor_demo(struct notcurses* nc){
|
||||
NULL
|
||||
};
|
||||
const char** s;
|
||||
int count = notcurses_palette_size(nc);
|
||||
const int steps[] = { 1, 0x100, 0x40000, 0x10001, };
|
||||
const int starts[] = { 0x4000, 0x40, 0x10000, 0x400040, };
|
||||
const int steps[] = { 0x100, 0x100, 0x40000, 0x10001, };
|
||||
const int starts[] = { 0x004000, 0x000040, 0x010101, 0x400040, };
|
||||
|
||||
struct ncplane* n = notcurses_stdplane(nc);
|
||||
size_t i;
|
||||
@ -588,13 +591,10 @@ int widecolor_demo(struct notcurses* nc){
|
||||
cell c;
|
||||
struct timespec screenend;
|
||||
clock_gettime(CLOCK_MONOTONIC, &screenend);
|
||||
ns_to_timespec(timespec_to_ns(&screenend) + timespec_to_ns(&demodelay), &screenend);
|
||||
ns_to_timespec(timespec_to_ns(&screenend) + 2 * timespec_to_ns(&demodelay), &screenend);
|
||||
do{ // (re)draw a screen
|
||||
const int start = starts[i];
|
||||
int step = steps[i];
|
||||
const int rollover = 256 / ((step & 0xff) | ((step & 0xff00) >> 8u)
|
||||
| ((step & 0xff0000) >> 16u));
|
||||
int rollcount = 0; // number of times we've added this step
|
||||
cell_init(&c);
|
||||
int y, x, maxy, maxx;
|
||||
ncplane_dim_yx(n, &maxy, &maxx);
|
||||
@ -634,11 +634,18 @@ int widecolor_demo(struct notcurses* nc){
|
||||
}
|
||||
int ulen = 0;
|
||||
int r;
|
||||
if((r = ncplane_putegc(n, &(*s)[idx], 0, channels, &ulen)) < 0){
|
||||
if(ulen < 0){
|
||||
if(wcwidth(wcs) <= maxx - x){
|
||||
if((r = ncplane_putegc(n, &(*s)[idx], 0, channels, &ulen)) < 0){
|
||||
if(ulen < 0){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
cell octo = CELL_INITIALIZER('#', 0, channels);
|
||||
if((r = ncplane_putc(n, &octo)) < 1){
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
cell_release(n, &octo);
|
||||
}
|
||||
ncplane_cursor_yx(n, &y, &x);
|
||||
idx += ulen;
|
||||
@ -646,19 +653,7 @@ int widecolor_demo(struct notcurses* nc){
|
||||
cols_out += r;
|
||||
++egcs_out;
|
||||
}
|
||||
if(++rollcount % rollover == 0){
|
||||
step *= 256;
|
||||
}
|
||||
if((unsigned)step >= 1ul << 24){
|
||||
step >>= 24u;
|
||||
}
|
||||
if(step == 0){
|
||||
step = 1;
|
||||
}
|
||||
if((rgb += step) >= count){
|
||||
rgb = 0;
|
||||
step *= 256;
|
||||
}
|
||||
rgb += step;
|
||||
}
|
||||
}while(y < maxy && x < maxx);
|
||||
struct ncplane* mess = notcurses_newplane(nc, 7, 57, 1, 4, NULL);
|
||||
@ -697,7 +692,7 @@ int widecolor_demo(struct notcurses* nc){
|
||||
pthread_join(tid, NULL);
|
||||
ncplane_destroy(mess);
|
||||
if(key == NCKEY_RESIZE){
|
||||
notcurses_resize(nc, NULL, NULL);
|
||||
notcurses_resize(nc, &maxy, &maxx);
|
||||
}
|
||||
}while(key == NCKEY_RESIZE);
|
||||
}
|
@ -197,6 +197,31 @@ rgb_quantize_256(unsigned r, unsigned g, unsigned b){
|
||||
return r * 36 + g * 6 + b + 16;
|
||||
}
|
||||
|
||||
static inline int
|
||||
term_emit(const char* name __attribute__ ((unused)), const char* seq,
|
||||
FILE* out, bool flush){
|
||||
int ret = fprintf(out, "%s", seq);
|
||||
if(ret < 0){
|
||||
// fprintf(stderr, "Error emitting %zub %s escape (%s)\n", strlen(seq), name, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if((size_t)ret != strlen(seq)){
|
||||
// fprintf(stderr, "Short write (%db) for %zub %s sequence\n", ret, strlen(seq), name);
|
||||
return -1;
|
||||
}
|
||||
if(flush && fflush(out)){
|
||||
// fprintf(stderr, "Error flushing after %db %s sequence (%s)\n", ret, name, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
extended_gcluster(const ncplane* n, const cell* c){
|
||||
uint32_t idx = cell_egc_idx(c);
|
||||
return n->pool.pool + idx;
|
||||
}
|
||||
|
||||
#define NANOSECS_IN_SEC 1000000000
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -2,7 +2,6 @@
|
||||
#include <time.h>
|
||||
#include <term.h>
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@ -157,40 +156,6 @@ int ncplane_putwstr_aligned(struct ncplane* n, int y, const wchar_t* gclustarr,
|
||||
return ncplane_putwstr(n, gclustarr);
|
||||
}
|
||||
|
||||
static inline uint64_t
|
||||
timespec_to_ns(const struct timespec* t){
|
||||
return t->tv_sec * NANOSECS_IN_SEC + t->tv_nsec;
|
||||
}
|
||||
|
||||
static void
|
||||
update_render_stats(const struct timespec* time1, const struct timespec* time0,
|
||||
ncstats* stats, int bytes){
|
||||
int64_t elapsed = timespec_to_ns(time1) - timespec_to_ns(time0);
|
||||
//fprintf(stderr, "Rendering took %ld.%03lds\n", elapsed / NANOSECS_IN_SEC,
|
||||
// (elapsed % NANOSECS_IN_SEC) / 1000000);
|
||||
if(bytes >= 0){
|
||||
stats->render_bytes += bytes;
|
||||
if(bytes > stats->render_max_bytes){
|
||||
stats->render_max_bytes = bytes;
|
||||
}
|
||||
if(bytes < stats->render_min_bytes){
|
||||
stats->render_min_bytes = bytes;
|
||||
}
|
||||
}else{
|
||||
++stats->failed_renders;
|
||||
}
|
||||
if(elapsed > 0){ // don't count clearly incorrect information, egads
|
||||
++stats->renders;
|
||||
stats->render_ns += elapsed;
|
||||
if(elapsed > stats->render_max_ns){
|
||||
stats->render_max_ns = elapsed;
|
||||
}
|
||||
if(elapsed < stats->render_min_ns){
|
||||
stats->render_min_ns = elapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char NOTCURSES_VERSION[] =
|
||||
notcurses_VERSION_MAJOR "."
|
||||
notcurses_VERSION_MINOR "."
|
||||
@ -238,6 +203,16 @@ int ncplane_at_cursor(ncplane* n, cell* c){
|
||||
return cell_duplicate(n, c, &n->fb[fbcellidx(n, n->y, n->x)]);
|
||||
}
|
||||
|
||||
int ncplane_at_yx(ncplane* n, int y, int x, cell* c){
|
||||
if(y >= n->leny || x >= n->lenx){
|
||||
return true;
|
||||
}
|
||||
if(y < 0 || x < 0){
|
||||
return true;
|
||||
}
|
||||
return cell_duplicate(n, c, &n->fb[fbcellidx(n, y, x)]);
|
||||
}
|
||||
|
||||
void ncplane_dim_yx(const ncplane* n, int* rows, int* cols){
|
||||
if(rows){
|
||||
*rows = n->leny;
|
||||
@ -295,24 +270,6 @@ free_plane(ncplane* p){
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
term_emit(const char* name, const char* seq, FILE* out, bool flush){
|
||||
int ret = fprintf(out, "%s", seq);
|
||||
if(ret < 0){
|
||||
fprintf(stderr, "Error emitting %zub %s escape (%s)\n", strlen(seq), name, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if((size_t)ret != strlen(seq)){
|
||||
fprintf(stderr, "Short write (%db) for %zub %s sequence\n", ret, strlen(seq), name);
|
||||
return -1;
|
||||
}
|
||||
if(flush && fflush(out)){
|
||||
fprintf(stderr, "Error flushing after %db %s sequence (%s)\n", ret, name, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// create a new ncplane at the specified location (relative to the true screen,
|
||||
// having origin at 0,0), having the specified size, and put it at the top of
|
||||
// the planestack. its cursor starts at its origin; its style starts as null.
|
||||
@ -931,139 +888,10 @@ int ncplane_default(ncplane* ncp, cell* c){
|
||||
return cell_duplicate(ncp, c, &ncp->defcell);
|
||||
}
|
||||
|
||||
// 3 for foreground, 4 for background, ugh FIXME
|
||||
static int
|
||||
term_esc_rgb(notcurses* nc __attribute__ ((unused)), FILE* out, int esc,
|
||||
unsigned r, unsigned g, unsigned b){
|
||||
// The correct way to do this is using tiparm+tputs, but doing so (at least
|
||||
// as of terminfo 6.1.20191019) both emits ~3% more bytes for a run of 'rgb'
|
||||
// and gives rise to some corrupted cells (possibly due to special handling of
|
||||
// values < 256; I'm not at this time sure). So we just cons up our own.
|
||||
/*if(esc == 4){
|
||||
return term_emit("setab", tiparm(nc->setab, (int)((r << 16u) | (g << 8u) | b)), out, false);
|
||||
}else if(esc == 3){
|
||||
return term_emit("setaf", tiparm(nc->setaf, (int)((r << 16u) | (g << 8u) | b)), out, false);
|
||||
}else{
|
||||
return -1;
|
||||
}*/
|
||||
#define RGBESC1 "\x1b" "["
|
||||
#define RGBESC2 "8;2;"
|
||||
// rrr;ggg;bbbm
|
||||
char rgbesc[] = RGBESC1 " " RGBESC2 " ";
|
||||
int len = strlen(RGBESC1);
|
||||
rgbesc[len++] = esc;
|
||||
len += strlen(RGBESC2);
|
||||
if(r > 99){
|
||||
rgbesc[len++] = r / 100 + '0';
|
||||
}
|
||||
if(r > 9){
|
||||
rgbesc[len++] = (r % 100) / 10 + '0';
|
||||
}
|
||||
rgbesc[len++] = (r % 10) + '0';
|
||||
rgbesc[len++] = ';';
|
||||
if(g > 99){ rgbesc[len++] = g / 100 + '0'; }
|
||||
if(g > 9){ rgbesc[len++] = (g % 100) / 10 + '0'; }
|
||||
rgbesc[len++] = g % 10 + '0';
|
||||
rgbesc[len++] = ';';
|
||||
if(b > 99){ rgbesc[len++] = b / 100 + '0'; }
|
||||
if(b > 9){ rgbesc[len++] = (b % 100) / 10 + '0'; }
|
||||
rgbesc[len++] = b % 10 + '0';
|
||||
rgbesc[len++] = 'm';
|
||||
rgbesc[len] = '\0';
|
||||
int w;
|
||||
if((w = fputs_unlocked(rgbesc, out)) < len){
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
term_bg_rgb8(notcurses* nc, FILE* out, unsigned r, unsigned g, unsigned b){
|
||||
// We typically want to use tputs() and tiperm() to acquire and write the
|
||||
// escapes, as these take into account terminal-specific delays, padding,
|
||||
// etc. For the case of DirectColor, there is no suitable terminfo entry, but
|
||||
// we're also in that case working with hopefully more robust terminals.
|
||||
// If it doesn't work, eh, it doesn't work. Fuck the world; save yourself.
|
||||
if(nc->RGBflag){
|
||||
return term_esc_rgb(nc, out, '4', r, g, b);
|
||||
}else{
|
||||
if(nc->setab == NULL){
|
||||
return -1;
|
||||
}
|
||||
// For 256-color indexed mode, start constructing a palette based off
|
||||
// the inputs *if we can change the palette*. If more than 256 are used on
|
||||
// a single screen, start... combining close ones? For 8-color mode, simple
|
||||
// interpolation. I have no idea what to do for 88 colors. FIXME
|
||||
if(nc->colors >= 256){
|
||||
term_emit("setab", tiparm(nc->setab, rgb_quantize_256(r, g, b)), out, false);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
term_fg_rgb8(notcurses* nc, FILE* out, unsigned r, unsigned g, unsigned b){
|
||||
// We typically want to use tputs() and tiperm() to acquire and write the
|
||||
// escapes, as these take into account terminal-specific delays, padding,
|
||||
// etc. For the case of DirectColor, there is no suitable terminfo entry, but
|
||||
// we're also in that case working with hopefully more robust terminals.
|
||||
// If it doesn't work, eh, it doesn't work. Fuck the world; save yourself.
|
||||
if(nc->RGBflag){
|
||||
return term_esc_rgb(nc, out, '3', r, g, b);
|
||||
}else{
|
||||
if(nc->setaf == NULL){
|
||||
return -1;
|
||||
}
|
||||
if(nc->colors >= 256){
|
||||
term_emit("setaf", tiparm(nc->setaf, rgb_quantize_256(r, g, b)), out, false);
|
||||
}
|
||||
// For 256-color indexed mode, start constructing a palette based off
|
||||
// the inputs *if we can change the palette*. If more than 256 are used on
|
||||
// a single screen, start... combining close ones? For 8-color mode, simple
|
||||
// interpolation. I have no idea what to do for 88 colors. FIXME
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
extended_gcluster(const ncplane* n, const cell* c){
|
||||
uint32_t idx = cell_egc_idx(c);
|
||||
return n->pool.pool + idx;
|
||||
}
|
||||
|
||||
const char* cell_extended_gcluster(const struct ncplane* n, const cell* c){
|
||||
return extended_gcluster(n, c);
|
||||
}
|
||||
|
||||
// write the cell's UTF-8 grapheme cluster to the provided FILE*. returns the
|
||||
// number of columns occupied by this EGC (only an approximation; it's actually
|
||||
// a property of the font being used).
|
||||
static int
|
||||
term_putc(FILE* out, const ncplane* n, const cell* c){
|
||||
if(cell_simple_p(c)){
|
||||
if(c->gcluster == 0 || iscntrl(c->gcluster)){
|
||||
// fprintf(stderr, "[ ]\n");
|
||||
if(fputc_unlocked(' ', out) == EOF){
|
||||
return -1;
|
||||
}
|
||||
}else{
|
||||
// fprintf(stderr, "[%c]\n", c->gcluster);
|
||||
if(fputc_unlocked(c->gcluster, out) == EOF){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
const char* ext = extended_gcluster(n, c);
|
||||
// fprintf(stderr, "[%s]\n", ext);
|
||||
if(fputs_unlocked(ext, out) < 0){ // FIXME check for short write?
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
advance_cursor(ncplane* n, int cols){
|
||||
if(cursor_invalid_p(n)){
|
||||
@ -1071,156 +899,10 @@ advance_cursor(ncplane* n, int cols){
|
||||
}
|
||||
if((n->x += cols) >= n->lenx){
|
||||
++n->y;
|
||||
n->x -= n->lenx;
|
||||
n->x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// check the current and target style bitmasks against the specified 'stylebit'.
|
||||
// if they are different, and we have the necessary capability, write the
|
||||
// applicable terminfo entry to 'out'. returns -1 only on a true error.
|
||||
static int
|
||||
term_setstyle(FILE* out, unsigned cur, unsigned targ, unsigned stylebit,
|
||||
const char* ton, const char* toff){
|
||||
int ret = 0;
|
||||
unsigned curon = cur & stylebit;
|
||||
unsigned targon = targ & stylebit;
|
||||
if(curon != targon){
|
||||
if(targon){
|
||||
if(ton){
|
||||
ret = term_emit("ton", ton, out, false);
|
||||
}
|
||||
}else{
|
||||
if(toff){ // how did this happen? we can turn it on, but not off?
|
||||
ret = term_emit("toff", toff, out, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ret < 0){
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// write any escape sequences necessary to set the desired style
|
||||
static int
|
||||
term_setstyles(const notcurses* nc, FILE* out, uint32_t* curattr, const cell* c,
|
||||
bool* normalized){
|
||||
*normalized = false;
|
||||
uint32_t cellattr = cell_styles(c);
|
||||
if(cellattr == *curattr){
|
||||
return 0; // happy agreement, change nothing
|
||||
}
|
||||
int ret = 0;
|
||||
// if only italics changed, don't emit any sgr escapes. xor of current and
|
||||
// target ought have all 0s in the lower 8 bits if only italics changed.
|
||||
if((cellattr ^ *curattr) & 0x00ff0000ul){
|
||||
*normalized = true; // FIXME this is pretty conservative
|
||||
// if everything's 0, emit the shorter sgr0
|
||||
if(nc->sgr0 && ((cellattr & CELL_STYLE_MASK) == 0)){
|
||||
if(term_emit("sgr0", nc->sgr0, out, false) < 0){
|
||||
ret = -1;
|
||||
}
|
||||
}else if(term_emit("sgr", tiparm(nc->sgr, cellattr & CELL_STYLE_STANDOUT,
|
||||
cellattr & CELL_STYLE_UNDERLINE,
|
||||
cellattr & CELL_STYLE_REVERSE,
|
||||
cellattr & CELL_STYLE_BLINK,
|
||||
cellattr & CELL_STYLE_DIM,
|
||||
cellattr & CELL_STYLE_BOLD,
|
||||
cellattr & CELL_STYLE_INVIS,
|
||||
cellattr & CELL_STYLE_PROTECT, 0),
|
||||
out, false) < 0){
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
// sgr will blow away italics if they were set beforehand
|
||||
ret |= term_setstyle(out, *curattr, cellattr, CELL_STYLE_ITALIC, nc->italics, nc->italoff);
|
||||
*curattr = cellattr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Find the topmost cell for this coordinate by walking down the z-buffer,
|
||||
// looking for an intersecting ncplane. Once we've found one, check it for
|
||||
// transparency in either the back- or foreground. If the alpha channel is
|
||||
// active, keep descending and blending until we hit opacity, or bedrock. We
|
||||
// recurse to find opacity, and blend the result into what we have. The
|
||||
// 'findfore' and 'findback' bools control our recursion--there's no point in
|
||||
// going further down when a color is locked in, so don't (for instance) recurse
|
||||
// further when we have a transparent foreground and opaque background atop an
|
||||
// opaque foreground and transparent background. The cell we ultimately return
|
||||
// (a const ref to 'c') is backed by '*retp' via rawdog copy; the caller must
|
||||
// not call cell_release() upon it, nor use it beyond the scope of the render.
|
||||
//
|
||||
// So, as we go down, we find planes which can have impact on the result. Once
|
||||
// we've locked the result in (base case), write the deep values we have to 'c'.
|
||||
// Then, as we come back up, blend them as appropriate. The actual glyph is
|
||||
// whichever one occurs at the top with a non-transparent α (α < 3). To effect
|
||||
// tail recursion, though, we instead write first, and then recurse, blending
|
||||
// as we descend. α <= 0 is opaque. α >= 3 is fully transparent.
|
||||
static ncplane*
|
||||
dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha,
|
||||
bool* damage){
|
||||
while(p){
|
||||
// where in the plane this coordinate would be, based off absy/absx. the
|
||||
// true origin is 0,0, so abs=2,2 means coordinate 3,3 would be 1,1, while
|
||||
// abs=-2,-2 would make coordinate 3,3 relative 5, 5.
|
||||
int poffx, poffy;
|
||||
poffy = y - p->absy;
|
||||
poffx = x - p->absx;
|
||||
if(poffy < p->leny && poffy >= 0){
|
||||
if(poffx < p->lenx && poffx >= 0){ // p is valid for this y, x
|
||||
const cell* vis = &p->fb[fbcellidx(p, poffy, poffx)];
|
||||
// if we never loaded any content into the cell (or obliterated it by
|
||||
// writing in a zero), use the plane's background cell.
|
||||
if(vis->gcluster == 0){
|
||||
vis = &p->defcell;
|
||||
}
|
||||
bool lockedglyph = false;
|
||||
int nalpha;
|
||||
if(falpha > 0 && (nalpha = cell_get_fg_alpha(vis)) < CELL_ALPHA_TRANS){
|
||||
if(c->gcluster == 0){ // never write fully trans glyphs, never replace
|
||||
if( (c->gcluster = vis->gcluster) ){ // index copy only
|
||||
lockedglyph = true; // must return this ncplane for this glyph
|
||||
c->attrword = vis->attrword;
|
||||
cell_set_fchannel(c, cell_get_fchannel(vis)); // FIXME blend it in
|
||||
falpha -= (CELL_ALPHA_TRANS - nalpha); // FIXME blend it in
|
||||
if(p->damage[poffy]){
|
||||
*damage = true;
|
||||
p->damage[poffy] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(balpha > 0 && (nalpha = cell_get_bg_alpha(vis)) < CELL_ALPHA_TRANS){
|
||||
cell_set_bchannel(c, cell_get_bchannel(vis)); // FIXME blend it in
|
||||
balpha -= (CELL_ALPHA_TRANS - nalpha);
|
||||
if(p->damage[poffy]){
|
||||
*damage = true;
|
||||
p->damage[poffy] = false;
|
||||
}
|
||||
}
|
||||
if((falpha > 0 || balpha > 0) && p->z){ // we must go further!
|
||||
ncplane* cand = dig_visible_cell(c, y, x, p->z, falpha, balpha, damage);
|
||||
if(!lockedglyph && cand){
|
||||
p = cand;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
||||
p = p->z;
|
||||
}
|
||||
// should never happen for valid y, x thanks to the stdplane. you fucked up!
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline ncplane*
|
||||
visible_cell(cell* c, int y, int x, ncplane* n, bool* damage){
|
||||
cell_init(c);
|
||||
return dig_visible_cell(c, y, x, n, CELL_ALPHA_TRANS, CELL_ALPHA_TRANS, damage);
|
||||
}
|
||||
|
||||
// Call with c->gcluster == 3, falpha == 3, balpha == 0, *retp == topplane.
|
||||
|
||||
// 'n' ends up above 'above'
|
||||
int ncplane_move_above_unsafe(ncplane* restrict n, ncplane* restrict above){
|
||||
ncplane** an = find_above_ncplane(n);
|
||||
@ -1279,232 +961,6 @@ int ncplane_move_bottom(ncplane* n){
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
blocking_write(int fd, const char* buf, size_t buflen){
|
||||
size_t written = 0;
|
||||
do{
|
||||
ssize_t w = write(fd, buf + written, buflen - written);
|
||||
if(w < 0){
|
||||
if(errno != EAGAIN && errno != EWOULDBLOCK){
|
||||
return -1;
|
||||
}
|
||||
}else{
|
||||
written += w;
|
||||
}
|
||||
if(written < buflen){
|
||||
struct pollfd pfd = {
|
||||
.fd = fd,
|
||||
.events = POLLOUT,
|
||||
.revents = 0,
|
||||
};
|
||||
poll(&pfd, 1, -1);
|
||||
}
|
||||
}while(written < buflen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// determine the best palette for the current frame, and write the necessary
|
||||
// escape sequences to 'out'. for now, we just assume the ANSI palettes. at
|
||||
// 256 colors, this is the 16 normal ones, 6x6x6 color cubes, and 32 greys.
|
||||
// it's probably better to sample the darker regions rather than cover so much
|
||||
// chroma, but whatever....FIXME
|
||||
static int
|
||||
prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){
|
||||
if(nc->RGBflag){
|
||||
return 0; // DirectColor, no need to write palette
|
||||
}
|
||||
if(!nc->CCCflag){
|
||||
return 0; // can't change palette
|
||||
}
|
||||
// FIXME
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
// reshape the shadow framebuffer to match the stdplane's dimensions, throwing
|
||||
// away the old one.
|
||||
static int
|
||||
reshape_shadow_fb(notcurses* nc){
|
||||
const size_t size = sizeof(nc->shadowbuf) * nc->stdscr->leny * nc->stdscr->lenx;
|
||||
cell* fb = malloc(size);
|
||||
if(fb == NULL){
|
||||
return -1;
|
||||
}
|
||||
free(nc->shadowbuf);
|
||||
nc->shadowbuf = fb;
|
||||
nc->shadowy = nc->stdscr->leny;
|
||||
nc->shadowx = nc->stdscr->lenx;
|
||||
memset(fb, 0, size);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
static inline int
|
||||
notcurses_render_internal(notcurses* nc){
|
||||
int ret = 0;
|
||||
int y, x;
|
||||
FILE* out = nc->mstreamfp;
|
||||
fseeko(out, 0, SEEK_SET);
|
||||
// don't write a clearscreen. we only update things that have been changed.
|
||||
// we explicitly move the cursor at the beginning of each output line, so no
|
||||
// need to home it expliticly.
|
||||
prep_optimized_palette(nc, out); // FIXME do what on failure?
|
||||
uint32_t curattr = 0; // current attributes set (does not include colors)
|
||||
// FIXME as of at least gcc 9.2.1, we get a false -Wmaybe-uninitialized below
|
||||
// when using these without explicit initializations. for the life of me, i
|
||||
// can't see any such path, and valgrind is cool with it, so what ya gonna do?
|
||||
unsigned lastr = 0, lastg = 0, lastb = 0;
|
||||
unsigned lastbr = 0, lastbg = 0, lastbb = 0;
|
||||
// we can elide a color escape iff the color has not changed between the two
|
||||
// cells and the current cell uses no defaults, or if both the current and
|
||||
// the last used both defaults.
|
||||
bool fgelidable = false, bgelidable = false, defaultelidable = false;
|
||||
/*if(nc->stdscr->leny != nc->shadowy || nc->stdscr->lenx != nc->shadowx){
|
||||
reshape_shadow_fb(nc);
|
||||
}*/
|
||||
for(y = 0 ; y < nc->stdscr->leny ; ++y){
|
||||
bool linedamaged = false; // have we repositioned the cursor to start line?
|
||||
bool newdamage = nc->damage[y];
|
||||
// fprintf(stderr, "nc->damage[%d] (%p) = %u\n", y, nc->damage + y, nc->damage[y]);
|
||||
if(newdamage){
|
||||
nc->damage[y] = 0;
|
||||
}
|
||||
// move to the beginning of the line, in case our accounting was befouled
|
||||
// by wider- (or narrower-) than-reported characters
|
||||
for(x = 0 ; x < nc->stdscr->lenx ; ++x){
|
||||
unsigned r, g, b, br, bg, bb;
|
||||
ncplane* p;
|
||||
cell c; // no need to initialize
|
||||
p = visible_cell(&c, y, x, nc->top, &newdamage);
|
||||
assert(p);
|
||||
// don't try to print a wide character on the last column; it'll instead
|
||||
// be printed on the next line. they probably shouldn't be admitted, but
|
||||
// we can end up with one due to a resize.
|
||||
if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){
|
||||
continue;
|
||||
}
|
||||
if(!linedamaged){
|
||||
if(newdamage){
|
||||
term_emit("cup", tiparm(nc->cup, y, x), out, false);
|
||||
nc->stats.cellelisions += x;
|
||||
nc->stats.cellemissions += (nc->stdscr->lenx - x);
|
||||
linedamaged = true;
|
||||
}else{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// set the style. this can change the color back to the default; if it
|
||||
// does, we need update our elision possibilities.
|
||||
bool normalized;
|
||||
term_setstyles(nc, out, &curattr, &c, &normalized);
|
||||
if(normalized){
|
||||
defaultelidable = true;
|
||||
bgelidable = false;
|
||||
fgelidable = false;
|
||||
}
|
||||
// we allow these to be set distinctly, but terminfo only supports using
|
||||
// them both via the 'op' capability. unless we want to generate the 'op'
|
||||
// escapes ourselves, if either is set to default, we first send op, and
|
||||
// then a turnon for whichever aren't default.
|
||||
|
||||
// we can elide the default set iff the previous used both defaults
|
||||
if(cell_fg_default_p(&c) || cell_bg_default_p(&c)){
|
||||
if(!defaultelidable){
|
||||
++nc->stats.defaultemissions;
|
||||
term_emit("op", nc->op, out, false);
|
||||
}else{
|
||||
++nc->stats.defaultelisions;
|
||||
}
|
||||
// if either is not default, this will get turned off
|
||||
defaultelidable = true;
|
||||
fgelidable = false;
|
||||
bgelidable = false;
|
||||
}
|
||||
|
||||
// we can elide the foreground set iff the previous used fg and matched
|
||||
if(!cell_fg_default_p(&c)){
|
||||
cell_get_fg_rgb(&c, &r, &g, &b);
|
||||
if(fgelidable && lastr == r && lastg == g && lastb == b){
|
||||
++nc->stats.fgelisions;
|
||||
}else{
|
||||
term_fg_rgb8(nc, out, r, g, b);
|
||||
++nc->stats.fgemissions;
|
||||
fgelidable = true;
|
||||
}
|
||||
lastr = r; lastg = g; lastb = b;
|
||||
defaultelidable = false;
|
||||
}
|
||||
if(!cell_bg_default_p(&c)){
|
||||
cell_get_bg_rgb(&c, &br, &bg, &bb);
|
||||
if(bgelidable && lastbr == br && lastbg == bg && lastbb == bb){
|
||||
++nc->stats.bgelisions;
|
||||
}else{
|
||||
term_bg_rgb8(nc, out, br, bg, bb);
|
||||
++nc->stats.bgemissions;
|
||||
bgelidable = true;
|
||||
}
|
||||
lastbr = br; lastbg = bg; lastbb = bb;
|
||||
defaultelidable = false;
|
||||
}
|
||||
// fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x %p\n", y, x, r, g, b, p);
|
||||
term_putc(out, p, &c);
|
||||
if(cell_double_wide_p(&c)){
|
||||
++x;
|
||||
}
|
||||
}
|
||||
if(linedamaged == false){
|
||||
nc->stats.cellelisions += x;
|
||||
}
|
||||
}
|
||||
ret |= fflush(out);
|
||||
fflush(nc->ttyfp);
|
||||
if(blocking_write(nc->ttyfd, nc->mstream, nc->mstrsize)){
|
||||
ret = -1;
|
||||
}
|
||||
/*fprintf(stderr, "%lu/%lu %lu/%lu %lu/%lu\n", defaultelisions, defaultemissions,
|
||||
fgelisions, fgemissions, bgelisions, bgemissions);*/
|
||||
if(nc->renderfp){
|
||||
fprintf(nc->renderfp, "%s\n", nc->mstream);
|
||||
}
|
||||
return nc->mstrsize;
|
||||
}
|
||||
|
||||
static void
|
||||
mutex_unlock(void* vlock){
|
||||
pthread_mutex_unlock(vlock);
|
||||
}
|
||||
|
||||
int notcurses_refresh(notcurses* nc){
|
||||
int ret = 0;
|
||||
pthread_mutex_lock(&nc->lock);
|
||||
pthread_cleanup_push(mutex_unlock, &nc->lock);
|
||||
if(nc->mstream == NULL){
|
||||
ret = -1; // haven't rendered yet, and thus don't know what should be there
|
||||
}else if(blocking_write(nc->ttyfd, nc->mstream, nc->mstrsize)){
|
||||
ret = -1;
|
||||
}
|
||||
pthread_cleanup_pop(1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int notcurses_render(notcurses* nc){
|
||||
int ret = 0;
|
||||
struct timespec start, done;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
|
||||
pthread_mutex_lock(&nc->lock);
|
||||
pthread_cleanup_push(mutex_unlock, &nc->lock);
|
||||
int bytes = notcurses_render_internal(nc);
|
||||
int dimy, dimx;
|
||||
notcurses_resize(nc, &dimy, &dimx);
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &done);
|
||||
update_render_stats(&done, &start, &nc->stats, bytes);
|
||||
if(bytes < 0){
|
||||
ret = -1;
|
||||
}
|
||||
pthread_cleanup_pop(1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ncplane_cursor_move_yx(ncplane* n, int y, int x){
|
||||
if(x >= n->lenx || x < 0){
|
||||
return -1;
|
||||
@ -1549,18 +1005,54 @@ int cell_duplicate(ncplane* n, cell* targ, const cell* c){
|
||||
return ulen;
|
||||
}
|
||||
|
||||
static inline void
|
||||
cell_set_wide(cell* c){
|
||||
c->channels |= CELL_WIDEASIAN_MASK;
|
||||
}
|
||||
|
||||
static inline void
|
||||
cell_obliterate(ncplane* n, cell* c){
|
||||
cell_release(n, c);
|
||||
cell_init(c);
|
||||
}
|
||||
|
||||
int ncplane_putc(ncplane* n, const cell* c){
|
||||
ncplane_lock(n);
|
||||
if(cursor_invalid_p(n)){
|
||||
ncplane_unlock(n);
|
||||
return -1;
|
||||
}
|
||||
bool wide = cell_double_wide_p(c);
|
||||
// A wide character obliterates anything to its immediate right (and marks
|
||||
// that cell as wide). Any character placed atop one half of a wide character
|
||||
// obliterates the other half. Note that a wide char can thus obliterate two
|
||||
// wide chars, totalling four columns.
|
||||
cell* targ = &n->fb[fbcellidx(n, n->y, n->x)];
|
||||
if(n->x > 0){
|
||||
if(cell_double_wide_p(targ)){ // replaced cell is half of a wide char
|
||||
if(targ->gcluster == 0){ // we're the right half
|
||||
cell_obliterate(n, &n->fb[fbcellidx(n, n->y, n->x - 1)]);
|
||||
}else{
|
||||
cell_obliterate(n, &n->fb[fbcellidx(n, n->y, n->x + 1)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(cell_duplicate(n, targ, c) < 0){
|
||||
ncplane_unlock(n);
|
||||
return -1;
|
||||
}
|
||||
int cols = 1 + cell_double_wide_p(targ);
|
||||
int cols = 1 + wide;
|
||||
if(wide){ // must set our right wide, and check for further damage
|
||||
if(n->x < n->lenx - 1){ // check to our right
|
||||
cell* candidate = &n->fb[fbcellidx(n, n->y, n->x + 1)];
|
||||
if(n->x < n->lenx - 2){
|
||||
if(cell_double_wide_p(candidate) && targ->gcluster){ // left half
|
||||
cell_obliterate(n, &n->fb[fbcellidx(n, n->y, n->x + 2)]);
|
||||
}
|
||||
}
|
||||
cell_set_wide(candidate);
|
||||
}
|
||||
}
|
||||
n->damage[n->y] = true;
|
||||
advance_cursor(n, cols);
|
||||
ncplane_unlock(n);
|
||||
@ -1568,11 +1060,7 @@ int ncplane_putc(ncplane* n, const cell* c){
|
||||
}
|
||||
|
||||
int ncplane_putsimple(struct ncplane* n, char c){
|
||||
cell ce = {
|
||||
.gcluster = c,
|
||||
.attrword = ncplane_get_attr(n),
|
||||
.channels = ncplane_get_channels(n),
|
||||
};
|
||||
cell ce = CELL_INITIALIZER(c, ncplane_get_attr(n), ncplane_get_channels(n));
|
||||
if(!cell_simple_p(&ce)){
|
||||
return -1;
|
||||
}
|
||||
@ -1622,6 +1110,7 @@ int cell_load(ncplane* n, cell* c, const char* gcluster){
|
||||
int bytes;
|
||||
int cols;
|
||||
if((bytes = utf8_egc_len(gcluster, &cols)) >= 0 && bytes <= 1){
|
||||
c->channels &= ~CELL_WIDEASIAN_MASK;
|
||||
c->gcluster = *gcluster;
|
||||
return !!c->gcluster;
|
||||
}
|
||||
@ -2053,6 +1542,9 @@ int ncvisual_render(const ncvisual* ncv){
|
||||
const int linesize = f->linesize[0];
|
||||
const unsigned char* data = f->data[0];
|
||||
for(y = 0 ; y < f->height / 2 && y < dimy ; ++y){
|
||||
if(ncplane_cursor_move_yx(ncv->ncp, y, 0)){
|
||||
return -1;
|
||||
}
|
||||
for(x = 0 ; x < f->width && x < dimx ; ++x){
|
||||
int bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(f->format));
|
||||
const unsigned char* rgbbase_up = data + (linesize * (y * 2)) + (x * bpp / CHAR_BIT);
|
||||
|
532
src/lib/render.c
Normal file
532
src/lib/render.c
Normal file
@ -0,0 +1,532 @@
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/poll.h>
|
||||
#include "internal.h"
|
||||
|
||||
static inline uint64_t
|
||||
timespec_to_ns(const struct timespec* t){
|
||||
return t->tv_sec * NANOSECS_IN_SEC + t->tv_nsec;
|
||||
}
|
||||
|
||||
static void
|
||||
mutex_unlock(void* vlock){
|
||||
pthread_mutex_unlock(vlock);
|
||||
}
|
||||
|
||||
static int
|
||||
blocking_write(int fd, const char* buf, size_t buflen){
|
||||
size_t written = 0;
|
||||
do{
|
||||
ssize_t w = write(fd, buf + written, buflen - written);
|
||||
if(w < 0){
|
||||
if(errno != EAGAIN && errno != EWOULDBLOCK){
|
||||
return -1;
|
||||
}
|
||||
}else{
|
||||
written += w;
|
||||
}
|
||||
if(written < buflen){
|
||||
struct pollfd pfd = {
|
||||
.fd = fd,
|
||||
.events = POLLOUT,
|
||||
.revents = 0,
|
||||
};
|
||||
poll(&pfd, 1, -1);
|
||||
}
|
||||
}while(written < buflen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int notcurses_refresh(notcurses* nc){
|
||||
int ret = 0;
|
||||
pthread_mutex_lock(&nc->lock);
|
||||
pthread_cleanup_push(mutex_unlock, &nc->lock);
|
||||
if(nc->mstream == NULL){
|
||||
ret = -1; // haven't rendered yet, and thus don't know what should be there
|
||||
}else if(blocking_write(nc->ttyfd, nc->mstream, nc->mstrsize)){
|
||||
ret = -1;
|
||||
}
|
||||
pthread_cleanup_pop(1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
update_render_stats(const struct timespec* time1, const struct timespec* time0,
|
||||
ncstats* stats, int bytes){
|
||||
int64_t elapsed = timespec_to_ns(time1) - timespec_to_ns(time0);
|
||||
//fprintf(stderr, "Rendering took %ld.%03lds\n", elapsed / NANOSECS_IN_SEC,
|
||||
// (elapsed % NANOSECS_IN_SEC) / 1000000);
|
||||
if(bytes >= 0){
|
||||
stats->render_bytes += bytes;
|
||||
if(bytes > stats->render_max_bytes){
|
||||
stats->render_max_bytes = bytes;
|
||||
}
|
||||
if(bytes < stats->render_min_bytes){
|
||||
stats->render_min_bytes = bytes;
|
||||
}
|
||||
}else{
|
||||
++stats->failed_renders;
|
||||
}
|
||||
if(elapsed > 0){ // don't count clearly incorrect information, egads
|
||||
++stats->renders;
|
||||
stats->render_ns += elapsed;
|
||||
if(elapsed > stats->render_max_ns){
|
||||
stats->render_max_ns = elapsed;
|
||||
}
|
||||
if(elapsed < stats->render_min_ns){
|
||||
stats->render_min_ns = elapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// determine the best palette for the current frame, and write the necessary
|
||||
// escape sequences to 'out'. for now, we just assume the ANSI palettes. at
|
||||
// 256 colors, this is the 16 normal ones, 6x6x6 color cubes, and 32 greys.
|
||||
// it's probably better to sample the darker regions rather than cover so much
|
||||
// chroma, but whatever....FIXME
|
||||
static int
|
||||
prep_optimized_palette(notcurses* nc, FILE* out __attribute__ ((unused))){
|
||||
if(nc->RGBflag){
|
||||
return 0; // DirectColor, no need to write palette
|
||||
}
|
||||
if(!nc->CCCflag){
|
||||
return 0; // can't change palette
|
||||
}
|
||||
// FIXME
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
// reshape the shadow framebuffer to match the stdplane's dimensions, throwing
|
||||
// away the old one.
|
||||
static int
|
||||
reshape_shadow_fb(notcurses* nc){
|
||||
const size_t size = sizeof(nc->shadowbuf) * nc->stdscr->leny * nc->stdscr->lenx;
|
||||
cell* fb = malloc(size);
|
||||
if(fb == NULL){
|
||||
return -1;
|
||||
}
|
||||
free(nc->shadowbuf);
|
||||
nc->shadowbuf = fb;
|
||||
nc->shadowy = nc->stdscr->leny;
|
||||
nc->shadowx = nc->stdscr->lenx;
|
||||
memset(fb, 0, size);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
// Find the topmost cell for this coordinate by walking down the z-buffer,
|
||||
// looking for an intersecting ncplane. Once we've found one, check it for
|
||||
// transparency in either the back- or foreground. If the alpha channel is
|
||||
// active, keep descending and blending until we hit opacity, or bedrock. We
|
||||
// recurse to find opacity, and blend the result into what we have. The
|
||||
// 'findfore' and 'findback' bools control our recursion--there's no point in
|
||||
// going further down when a color is locked in, so don't (for instance) recurse
|
||||
// further when we have a transparent foreground and opaque background atop an
|
||||
// opaque foreground and transparent background. The cell we ultimately return
|
||||
// (a const ref to 'c') is backed by '*retp' via rawdog copy; the caller must
|
||||
// not call cell_release() upon it, nor use it beyond the scope of the render.
|
||||
//
|
||||
// So, as we go down, we find planes which can have impact on the result. Once
|
||||
// we've locked the result in (base case), write the deep values we have to 'c'.
|
||||
// Then, as we come back up, blend them as appropriate. The actual glyph is
|
||||
// whichever one occurs at the top with a non-transparent α (α < 3). To effect
|
||||
// tail recursion, though, we instead write first, and then recurse, blending
|
||||
// as we descend. α <= 0 is opaque. α >= 3 is fully transparent.
|
||||
static ncplane*
|
||||
dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha,
|
||||
bool* damage){
|
||||
while(p){
|
||||
// where in the plane this coordinate would be, based off absy/absx. the
|
||||
// true origin is 0,0, so abs=2,2 means coordinate 3,3 would be 1,1, while
|
||||
// abs=-2,-2 would make coordinate 3,3 relative 5, 5.
|
||||
int poffx, poffy;
|
||||
poffy = y - p->absy;
|
||||
poffx = x - p->absx;
|
||||
if(poffy < p->leny && poffy >= 0){
|
||||
if(poffx < p->lenx && poffx >= 0){ // p is valid for this y, x
|
||||
const cell* vis = &p->fb[fbcellidx(p, poffy, poffx)];
|
||||
// if we never loaded any content into the cell (or obliterated it by
|
||||
// writing in a zero), use the plane's background cell.
|
||||
if(vis->gcluster == 0){
|
||||
vis = &p->defcell;
|
||||
}
|
||||
bool lockedglyph = false;
|
||||
int nalpha;
|
||||
if(falpha > 0 && (nalpha = cell_get_fg_alpha(vis)) < CELL_ALPHA_TRANS){
|
||||
if(c->gcluster == 0){ // never write fully trans glyphs, never replace
|
||||
if( (c->gcluster = vis->gcluster) ){ // index copy only
|
||||
lockedglyph = true; // must return this ncplane for this glyph
|
||||
c->attrword = vis->attrword;
|
||||
cell_set_fchannel(c, cell_get_fchannel(vis)); // FIXME blend it in
|
||||
falpha -= (CELL_ALPHA_TRANS - nalpha); // FIXME blend it in
|
||||
if(p->damage[poffy]){
|
||||
*damage = true;
|
||||
p->damage[poffy] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(balpha > 0 && (nalpha = cell_get_bg_alpha(vis)) < CELL_ALPHA_TRANS){
|
||||
cell_set_bchannel(c, cell_get_bchannel(vis)); // FIXME blend it in
|
||||
balpha -= (CELL_ALPHA_TRANS - nalpha);
|
||||
if(p->damage[poffy]){
|
||||
*damage = true;
|
||||
p->damage[poffy] = false;
|
||||
}
|
||||
}
|
||||
if((falpha > 0 || balpha > 0) && p->z){ // we must go further!
|
||||
ncplane* cand = dig_visible_cell(c, y, x, p->z, falpha, balpha, damage);
|
||||
if(!lockedglyph && cand){
|
||||
p = cand;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
||||
p = p->z;
|
||||
}
|
||||
// should never happen for valid y, x thanks to the stdplane. you fucked up!
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline ncplane*
|
||||
visible_cell(cell* c, int y, int x, ncplane* n, bool* damage){
|
||||
cell_init(c);
|
||||
return dig_visible_cell(c, y, x, n, CELL_ALPHA_TRANS, CELL_ALPHA_TRANS, damage);
|
||||
}
|
||||
|
||||
// write the cell's UTF-8 grapheme cluster to the provided FILE*. returns the
|
||||
// number of columns occupied by this EGC (only an approximation; it's actually
|
||||
// a property of the font being used).
|
||||
static int
|
||||
term_putc(FILE* out, const ncplane* n, const cell* c){
|
||||
if(cell_simple_p(c)){
|
||||
if(c->gcluster == 0 || iscntrl(c->gcluster)){
|
||||
// fprintf(stderr, "[ ]\n");
|
||||
if(fputc_unlocked(' ', out) == EOF){
|
||||
return -1;
|
||||
}
|
||||
}else{
|
||||
// fprintf(stderr, "[%c]\n", c->gcluster);
|
||||
if(fputc_unlocked(c->gcluster, out) == EOF){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
const char* ext = extended_gcluster(n, c);
|
||||
// fprintf(stderr, "[%s]\n", ext);
|
||||
if(fputs_unlocked(ext, out) < 0){ // FIXME check for short write?
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// check the current and target style bitmasks against the specified 'stylebit'.
|
||||
// if they are different, and we have the necessary capability, write the
|
||||
// applicable terminfo entry to 'out'. returns -1 only on a true error.
|
||||
static int
|
||||
term_setstyle(FILE* out, unsigned cur, unsigned targ, unsigned stylebit,
|
||||
const char* ton, const char* toff){
|
||||
int ret = 0;
|
||||
unsigned curon = cur & stylebit;
|
||||
unsigned targon = targ & stylebit;
|
||||
if(curon != targon){
|
||||
if(targon){
|
||||
if(ton){
|
||||
ret = term_emit("ton", ton, out, false);
|
||||
}
|
||||
}else{
|
||||
if(toff){ // how did this happen? we can turn it on, but not off?
|
||||
ret = term_emit("toff", toff, out, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ret < 0){
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// write any escape sequences necessary to set the desired style
|
||||
static int
|
||||
term_setstyles(const notcurses* nc, FILE* out, uint32_t* curattr, const cell* c,
|
||||
bool* normalized){
|
||||
*normalized = false;
|
||||
uint32_t cellattr = cell_styles(c);
|
||||
if(cellattr == *curattr){
|
||||
return 0; // happy agreement, change nothing
|
||||
}
|
||||
int ret = 0;
|
||||
// if only italics changed, don't emit any sgr escapes. xor of current and
|
||||
// target ought have all 0s in the lower 8 bits if only italics changed.
|
||||
if((cellattr ^ *curattr) & 0x00ff0000ul){
|
||||
*normalized = true; // FIXME this is pretty conservative
|
||||
// if everything's 0, emit the shorter sgr0
|
||||
if(nc->sgr0 && ((cellattr & CELL_STYLE_MASK) == 0)){
|
||||
if(term_emit("sgr0", nc->sgr0, out, false) < 0){
|
||||
ret = -1;
|
||||
}
|
||||
}else if(term_emit("sgr", tiparm(nc->sgr, cellattr & CELL_STYLE_STANDOUT,
|
||||
cellattr & CELL_STYLE_UNDERLINE,
|
||||
cellattr & CELL_STYLE_REVERSE,
|
||||
cellattr & CELL_STYLE_BLINK,
|
||||
cellattr & CELL_STYLE_DIM,
|
||||
cellattr & CELL_STYLE_BOLD,
|
||||
cellattr & CELL_STYLE_INVIS,
|
||||
cellattr & CELL_STYLE_PROTECT, 0),
|
||||
out, false) < 0){
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
// sgr will blow away italics if they were set beforehand
|
||||
ret |= term_setstyle(out, *curattr, cellattr, CELL_STYLE_ITALIC, nc->italics, nc->italoff);
|
||||
*curattr = cellattr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 3 for foreground, 4 for background, ugh FIXME
|
||||
static int
|
||||
term_esc_rgb(notcurses* nc __attribute__ ((unused)), FILE* out, int esc,
|
||||
unsigned r, unsigned g, unsigned b){
|
||||
// The correct way to do this is using tiparm+tputs, but doing so (at least
|
||||
// as of terminfo 6.1.20191019) both emits ~3% more bytes for a run of 'rgb'
|
||||
// and gives rise to some corrupted cells (possibly due to special handling of
|
||||
// values < 256; I'm not at this time sure). So we just cons up our own.
|
||||
/*if(esc == 4){
|
||||
return term_emit("setab", tiparm(nc->setab, (int)((r << 16u) | (g << 8u) | b)), out, false);
|
||||
}else if(esc == 3){
|
||||
return term_emit("setaf", tiparm(nc->setaf, (int)((r << 16u) | (g << 8u) | b)), out, false);
|
||||
}else{
|
||||
return -1;
|
||||
}*/
|
||||
#define RGBESC1 "\x1b" "["
|
||||
#define RGBESC2 "8;2;"
|
||||
// rrr;ggg;bbbm
|
||||
char rgbesc[] = RGBESC1 " " RGBESC2 " ";
|
||||
int len = strlen(RGBESC1);
|
||||
rgbesc[len++] = esc;
|
||||
len += strlen(RGBESC2);
|
||||
if(r > 99){
|
||||
rgbesc[len++] = r / 100 + '0';
|
||||
}
|
||||
if(r > 9){
|
||||
rgbesc[len++] = (r % 100) / 10 + '0';
|
||||
}
|
||||
rgbesc[len++] = (r % 10) + '0';
|
||||
rgbesc[len++] = ';';
|
||||
if(g > 99){ rgbesc[len++] = g / 100 + '0'; }
|
||||
if(g > 9){ rgbesc[len++] = (g % 100) / 10 + '0'; }
|
||||
rgbesc[len++] = g % 10 + '0';
|
||||
rgbesc[len++] = ';';
|
||||
if(b > 99){ rgbesc[len++] = b / 100 + '0'; }
|
||||
if(b > 9){ rgbesc[len++] = (b % 100) / 10 + '0'; }
|
||||
rgbesc[len++] = b % 10 + '0';
|
||||
rgbesc[len++] = 'm';
|
||||
rgbesc[len] = '\0';
|
||||
int w;
|
||||
if((w = fputs_unlocked(rgbesc, out)) < len){
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
term_bg_rgb8(notcurses* nc, FILE* out, unsigned r, unsigned g, unsigned b){
|
||||
// We typically want to use tputs() and tiperm() to acquire and write the
|
||||
// escapes, as these take into account terminal-specific delays, padding,
|
||||
// etc. For the case of DirectColor, there is no suitable terminfo entry, but
|
||||
// we're also in that case working with hopefully more robust terminals.
|
||||
// If it doesn't work, eh, it doesn't work. Fuck the world; save yourself.
|
||||
if(nc->RGBflag){
|
||||
return term_esc_rgb(nc, out, '4', r, g, b);
|
||||
}else{
|
||||
if(nc->setab == NULL){
|
||||
return -1;
|
||||
}
|
||||
// For 256-color indexed mode, start constructing a palette based off
|
||||
// the inputs *if we can change the palette*. If more than 256 are used on
|
||||
// a single screen, start... combining close ones? For 8-color mode, simple
|
||||
// interpolation. I have no idea what to do for 88 colors. FIXME
|
||||
if(nc->colors >= 256){
|
||||
term_emit("setab", tiparm(nc->setab, rgb_quantize_256(r, g, b)), out, false);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
term_fg_rgb8(notcurses* nc, FILE* out, unsigned r, unsigned g, unsigned b){
|
||||
// We typically want to use tputs() and tiperm() to acquire and write the
|
||||
// escapes, as these take into account terminal-specific delays, padding,
|
||||
// etc. For the case of DirectColor, there is no suitable terminfo entry, but
|
||||
// we're also in that case working with hopefully more robust terminals.
|
||||
// If it doesn't work, eh, it doesn't work. Fuck the world; save yourself.
|
||||
if(nc->RGBflag){
|
||||
return term_esc_rgb(nc, out, '3', r, g, b);
|
||||
}else{
|
||||
if(nc->setaf == NULL){
|
||||
return -1;
|
||||
}
|
||||
if(nc->colors >= 256){
|
||||
term_emit("setaf", tiparm(nc->setaf, rgb_quantize_256(r, g, b)), out, false);
|
||||
}
|
||||
// For 256-color indexed mode, start constructing a palette based off
|
||||
// the inputs *if we can change the palette*. If more than 256 are used on
|
||||
// a single screen, start... combining close ones? For 8-color mode, simple
|
||||
// interpolation. I have no idea what to do for 88 colors. FIXME
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int
|
||||
notcurses_render_internal(notcurses* nc){
|
||||
int ret = 0;
|
||||
int y, x;
|
||||
FILE* out = nc->mstreamfp;
|
||||
fseeko(out, 0, SEEK_SET);
|
||||
// don't write a clearscreen. we only update things that have been changed.
|
||||
// we explicitly move the cursor at the beginning of each output line, so no
|
||||
// need to home it expliticly.
|
||||
prep_optimized_palette(nc, out); // FIXME do what on failure?
|
||||
uint32_t curattr = 0; // current attributes set (does not include colors)
|
||||
// FIXME as of at least gcc 9.2.1, we get a false -Wmaybe-uninitialized below
|
||||
// when using these without explicit initializations. for the life of me, i
|
||||
// can't see any such path, and valgrind is cool with it, so what ya gonna do?
|
||||
unsigned lastr = 0, lastg = 0, lastb = 0;
|
||||
unsigned lastbr = 0, lastbg = 0, lastbb = 0;
|
||||
// we can elide a color escape iff the color has not changed between the two
|
||||
// cells and the current cell uses no defaults, or if both the current and
|
||||
// the last used both defaults.
|
||||
bool fgelidable = false, bgelidable = false, defaultelidable = false;
|
||||
/*if(nc->stdscr->leny != nc->shadowy || nc->stdscr->lenx != nc->shadowx){
|
||||
reshape_shadow_fb(nc);
|
||||
}*/
|
||||
for(y = 0 ; y < nc->stdscr->leny ; ++y){
|
||||
bool linedamaged = false; // have we repositioned the cursor to start line?
|
||||
bool newdamage = nc->damage[y];
|
||||
// fprintf(stderr, "nc->damage[%d] (%p) = %u\n", y, nc->damage + y, nc->damage[y]);
|
||||
if(newdamage){
|
||||
nc->damage[y] = 0;
|
||||
}
|
||||
// move to the beginning of the line, in case our accounting was befouled
|
||||
// by wider- (or narrower-) than-reported characters
|
||||
for(x = 0 ; x < nc->stdscr->lenx ; ++x){
|
||||
unsigned r, g, b, br, bg, bb;
|
||||
ncplane* p;
|
||||
cell c; // no need to initialize
|
||||
p = visible_cell(&c, y, x, nc->top, &newdamage);
|
||||
assert(p);
|
||||
// don't try to print a wide character on the last column; it'll instead
|
||||
// be printed on the next line. they probably shouldn't be admitted, but
|
||||
// we can end up with one due to a resize.
|
||||
if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){
|
||||
continue;
|
||||
}
|
||||
if(!linedamaged){
|
||||
if(newdamage){
|
||||
term_emit("cup", tiparm(nc->cup, y, x), out, false);
|
||||
nc->stats.cellelisions += x;
|
||||
nc->stats.cellemissions += (nc->stdscr->lenx - x);
|
||||
linedamaged = true;
|
||||
}else{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// set the style. this can change the color back to the default; if it
|
||||
// does, we need update our elision possibilities.
|
||||
bool normalized;
|
||||
term_setstyles(nc, out, &curattr, &c, &normalized);
|
||||
if(normalized){
|
||||
defaultelidable = true;
|
||||
bgelidable = false;
|
||||
fgelidable = false;
|
||||
}
|
||||
// we allow these to be set distinctly, but terminfo only supports using
|
||||
// them both via the 'op' capability. unless we want to generate the 'op'
|
||||
// escapes ourselves, if either is set to default, we first send op, and
|
||||
// then a turnon for whichever aren't default.
|
||||
|
||||
// we can elide the default set iff the previous used both defaults
|
||||
if(cell_fg_default_p(&c) || cell_bg_default_p(&c)){
|
||||
if(!defaultelidable){
|
||||
++nc->stats.defaultemissions;
|
||||
term_emit("op", nc->op, out, false);
|
||||
}else{
|
||||
++nc->stats.defaultelisions;
|
||||
}
|
||||
// if either is not default, this will get turned off
|
||||
defaultelidable = true;
|
||||
fgelidable = false;
|
||||
bgelidable = false;
|
||||
}
|
||||
|
||||
// we can elide the foreground set iff the previous used fg and matched
|
||||
if(!cell_fg_default_p(&c)){
|
||||
cell_get_fg_rgb(&c, &r, &g, &b);
|
||||
if(fgelidable && lastr == r && lastg == g && lastb == b){
|
||||
++nc->stats.fgelisions;
|
||||
}else{
|
||||
term_fg_rgb8(nc, out, r, g, b);
|
||||
++nc->stats.fgemissions;
|
||||
fgelidable = true;
|
||||
}
|
||||
lastr = r; lastg = g; lastb = b;
|
||||
defaultelidable = false;
|
||||
}
|
||||
if(!cell_bg_default_p(&c)){
|
||||
cell_get_bg_rgb(&c, &br, &bg, &bb);
|
||||
if(bgelidable && lastbr == br && lastbg == bg && lastbb == bb){
|
||||
++nc->stats.bgelisions;
|
||||
}else{
|
||||
term_bg_rgb8(nc, out, br, bg, bb);
|
||||
++nc->stats.bgemissions;
|
||||
bgelidable = true;
|
||||
}
|
||||
lastbr = br; lastbg = bg; lastbb = bb;
|
||||
defaultelidable = false;
|
||||
}
|
||||
// fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x %p\n", y, x, r, g, b, p);
|
||||
term_putc(out, p, &c);
|
||||
if(cell_double_wide_p(&c)){
|
||||
++x;
|
||||
}
|
||||
}
|
||||
if(linedamaged == false){
|
||||
nc->stats.cellelisions += x;
|
||||
}
|
||||
}
|
||||
ret |= fflush(out);
|
||||
fflush(nc->ttyfp);
|
||||
if(blocking_write(nc->ttyfd, nc->mstream, nc->mstrsize)){
|
||||
ret = -1;
|
||||
}
|
||||
/*fprintf(stderr, "%lu/%lu %lu/%lu %lu/%lu\n", defaultelisions, defaultemissions,
|
||||
fgelisions, fgemissions, bgelisions, bgemissions);*/
|
||||
if(nc->renderfp){
|
||||
fprintf(nc->renderfp, "%s\n", nc->mstream);
|
||||
}
|
||||
return nc->mstrsize;
|
||||
}
|
||||
|
||||
int notcurses_render(notcurses* nc){
|
||||
int ret = 0;
|
||||
struct timespec start, done;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
|
||||
pthread_mutex_lock(&nc->lock);
|
||||
pthread_cleanup_push(mutex_unlock, &nc->lock);
|
||||
int bytes = notcurses_render_internal(nc);
|
||||
int dimy, dimx;
|
||||
notcurses_resize(nc, &dimy, &dimx);
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &done);
|
||||
update_render_stats(&done, &start, &nc->stats, bytes);
|
||||
if(bytes < 0){
|
||||
ret = -1;
|
||||
}
|
||||
pthread_cleanup_pop(1);
|
||||
return ret;
|
||||
}
|
||||
|
@ -18,19 +18,38 @@ int main(int argc, char** argv){
|
||||
int dimx, dimy;
|
||||
ncplane_dim_yx(n, &dimy, &dimx);
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
cell_set_bg_rgb(&c, 0, 0x80, 0);
|
||||
//ncplane_set_default(n, &c);
|
||||
if(cell_load(n, &c, "🐳") < 0){
|
||||
goto err;
|
||||
}
|
||||
if(ncplane_set_default(n, &c) < 0){
|
||||
goto err;
|
||||
}
|
||||
cell_release(n, &c);
|
||||
if(cell_load(n, &c, "x") < 0){
|
||||
goto err;
|
||||
if(dimy > 5){
|
||||
dimy = 5;
|
||||
}
|
||||
for(int i = 0 ; i < dimy ; ++i){
|
||||
for(int j = 0 ; j < dimx / 2 ; j += 2){
|
||||
if(ncplane_putc_yx(n, i, j, &c) < 0){
|
||||
for(int j = 8 ; j < dimx / 2 ; ++j){ // leave some empty spaces
|
||||
if(ncplane_putc_yx(n, i, j * 2, &c) < 0){
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
}
|
||||
ncplane_putc_yx(n, dimy, dimx - 3, &c);
|
||||
ncplane_putc_yx(n, dimy, dimx - 1, &c);
|
||||
ncplane_putc_yx(n, dimy + 1, dimx - 2, &c);
|
||||
ncplane_putc_yx(n, dimy + 1, dimx - 4, &c);
|
||||
cell_release(n, &c);
|
||||
// put these on the right side of the wide glyphs
|
||||
for(int i = 0 ; i < dimy / 2 ; ++i){
|
||||
for(int j = 5 ; j < dimx / 2 ; j += 2){
|
||||
if(ncplane_putsimple_yx(n, i, j, (j % 10) + '0') < 0){
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
}
|
||||
// put these on the left side of the wide glyphs
|
||||
for(int i = dimy / 2 ; i < dimy ; ++i){
|
||||
for(int j = 4 ; j < dimx / 2 ; j += 2){
|
||||
if(ncplane_putsimple_yx(n, i, j, (j % 10) + '0') < 0){
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
@ -38,6 +57,7 @@ int main(int argc, char** argv){
|
||||
if(notcurses_render(nc)){
|
||||
goto err;
|
||||
}
|
||||
printf("\n");
|
||||
return notcurses_stop(nc) ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
|
||||
err:
|
||||
|
@ -762,3 +762,116 @@ TEST_F(NcplaneTest, MoveToLowerRight) {
|
||||
EXPECT_EQ(0, ncplane_destroy(ncp));
|
||||
// FIXME verify with ncplane_at_cursor()
|
||||
}
|
||||
|
||||
// Placing a wide char to the immediate left of any other char ought obliterate
|
||||
// that cell.
|
||||
TEST_F(NcplaneTest, WideCharAnnihilatesRight) {
|
||||
const wchar_t* w = L"🐸";
|
||||
const wchar_t* wbashed = L"🦂";
|
||||
const char bashed = 'X';
|
||||
int sbytes = 0;
|
||||
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 1, wbashed, 0, 0, &sbytes));
|
||||
EXPECT_LT(0, ncplane_putsimple_yx(n_, 1, 1, bashed));
|
||||
int x, y;
|
||||
ncplane_cursor_yx(n_, &y, &x);
|
||||
EXPECT_EQ(1, y);
|
||||
EXPECT_EQ(2, x);
|
||||
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 0, w, 0, 0, &sbytes));
|
||||
EXPECT_LT(0, ncplane_putwegc_yx(n_, 1, 0, w, 0, 0, &sbytes));
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
ncplane_at_yx(n_, 0, 0, &c);
|
||||
const char* wres = extended_gcluster(n_, &c);
|
||||
EXPECT_EQ(0, strcmp(wres, "🐸")); // should be frog
|
||||
ncplane_at_yx(n_, 0, 1, &c);
|
||||
EXPECT_TRUE(cell_double_wide_p(&c)); // should be wide
|
||||
ncplane_at_yx(n_, 0, 2, &c);
|
||||
EXPECT_EQ(0, c.gcluster); // should be nothing
|
||||
ncplane_at_yx(n_, 1, 0, &c);
|
||||
wres = extended_gcluster(n_, &c);
|
||||
EXPECT_EQ(0, strcmp(wres, "🐸")); // should be frog
|
||||
ncplane_at_yx(n_, 1, 1, &c);
|
||||
EXPECT_TRUE(cell_double_wide_p(&c)); //should be wide
|
||||
ncplane_at_yx(n_, 0, 2, &c);
|
||||
EXPECT_EQ(0, c.gcluster);
|
||||
EXPECT_EQ(0, notcurses_render(nc_)); // should be nothing
|
||||
}
|
||||
|
||||
// Placing a wide char on the right half of a wide char ought obliterate the
|
||||
// original wide char.
|
||||
TEST_F(NcplaneTest, WideCharAnnihilatesWideLeft) {
|
||||
const wchar_t* w = L"🐍";
|
||||
const wchar_t* wbashed = L"🦂";
|
||||
int sbytes = 0;
|
||||
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 0, wbashed, 0, 0, &sbytes));
|
||||
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 1, w, 0, 0, &sbytes));
|
||||
int x, y;
|
||||
ncplane_cursor_yx(n_, &y, &x);
|
||||
EXPECT_EQ(0, y);
|
||||
EXPECT_EQ(3, x);
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
ncplane_at_yx(n_, 0, 0, &c);
|
||||
EXPECT_EQ(0, c.gcluster); // should be nothing
|
||||
ncplane_at_yx(n_, 0, 1, &c);
|
||||
const char* wres = extended_gcluster(n_, &c);
|
||||
EXPECT_EQ(0, strcmp(wres, "🐍")); // should be snake
|
||||
ncplane_at_yx(n_, 0, 2, &c);
|
||||
EXPECT_TRUE(cell_double_wide_p(&c)); // should be wide
|
||||
EXPECT_EQ(0, notcurses_render(nc_));
|
||||
}
|
||||
|
||||
// Placing a normal char on either half of a wide char ought obliterate
|
||||
// the original wide char.
|
||||
TEST_F(NcplaneTest, WideCharsAnnihilated) {
|
||||
const char cc = 'X';
|
||||
const wchar_t* wbashedl = L"🐍";
|
||||
const wchar_t* wbashedr = L"🦂";
|
||||
int sbytes = 0;
|
||||
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 0, wbashedl, 0, 0, &sbytes));
|
||||
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 2, wbashedr, 0, 0, &sbytes));
|
||||
EXPECT_EQ(1, ncplane_putsimple_yx(n_, 0, 1, cc));
|
||||
EXPECT_EQ(1, ncplane_putsimple_yx(n_, 0, 2, cc));
|
||||
int x, y;
|
||||
ncplane_cursor_yx(n_, &y, &x);
|
||||
EXPECT_EQ(0, y);
|
||||
EXPECT_EQ(3, x);
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
ncplane_at_yx(n_, 0, 0, &c);
|
||||
EXPECT_EQ(0, c.gcluster); // should be nothing
|
||||
ncplane_at_yx(n_, 0, 1, &c);
|
||||
EXPECT_EQ(cc, c.gcluster); // should be 'X'
|
||||
ncplane_at_yx(n_, 0, 2, &c);
|
||||
EXPECT_EQ(cc, c.gcluster); // should be 'X"
|
||||
ncplane_at_yx(n_, 0, 3, &c);
|
||||
EXPECT_EQ(0, c.gcluster); // should be nothing
|
||||
EXPECT_EQ(0, notcurses_render(nc_));
|
||||
}
|
||||
|
||||
// But placing something to the immediate right of any glyph, that is not a
|
||||
// problem. Ensure it is so.
|
||||
TEST_F(NcplaneTest, AdjacentCharsSafe) {
|
||||
const char cc = 'X';
|
||||
const wchar_t* wsafel = L"🐍";
|
||||
const wchar_t* wsafer = L"🦂";
|
||||
int sbytes = 0;
|
||||
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 0, wsafel, 0, 0, &sbytes));
|
||||
EXPECT_LT(0, ncplane_putwegc_yx(n_, 0, 3, wsafer, 0, 0, &sbytes));
|
||||
EXPECT_EQ(1, ncplane_putsimple_yx(n_, 0, 2, cc));
|
||||
int x, y;
|
||||
ncplane_cursor_yx(n_, &y, &x);
|
||||
EXPECT_EQ(0, y);
|
||||
EXPECT_EQ(3, x);
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
ncplane_at_yx(n_, 0, 0, &c);
|
||||
const char* wres = extended_gcluster(n_, &c);
|
||||
EXPECT_EQ(0, strcmp(wres, "🐍")); // should be snake
|
||||
ncplane_at_yx(n_, 0, 1, &c);
|
||||
EXPECT_TRUE(cell_double_wide_p(&c)); // should be snake
|
||||
ncplane_at_yx(n_, 0, 2, &c);
|
||||
EXPECT_EQ(cc, c.gcluster); // should be 'X'
|
||||
ncplane_at_yx(n_, 0, 3, &c);
|
||||
wres = extended_gcluster(n_, &c);
|
||||
EXPECT_EQ(0, strcmp(wres, "🦂")); // should be scorpion
|
||||
ncplane_at_yx(n_, 0, 4, &c);
|
||||
EXPECT_TRUE(cell_double_wide_p(&c)); // should be scorpion
|
||||
EXPECT_EQ(0, notcurses_render(nc_));
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user