You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
notcurses/src/lib/internal.h

929 lines
33 KiB
C

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#ifndef NOTCURSES_INTERNAL
#define NOTCURSES_INTERNAL
#ifdef __cplusplus
extern "C" {
#endif
#include "version.h"
#ifdef USE_FFMPEG
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/pixdesc.h>
#include <libavutil/version.h>
#include <libswscale/version.h>
#include <libavformat/version.h>
#else
#ifdef USE_OIIO
const char* oiio_version(void);
#endif
#endif
#include <term.h>
#include <time.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <wctype.h>
#include <pthread.h>
#include <termios.h>
#include <stdbool.h>
#include <langinfo.h>
#include "notcurses/notcurses.h"
#include "egcpool.h"
struct esctrie;
// we can't define multipart ncvisual here, because OIIO requires C++ syntax,
// and we can't go throwing C++ syntax into this header. so it goes.
// A plane is memory for some rectilinear virtual window, plus current cursor
// state for that window. A notcurses context describes a single terminal, and
// has a z-order of planes (I see no advantage to maintaining a poset, and we
// instead just use a list, top-to-bottom). Every cell on the terminal is part
// of at least one plane, and at least one plane covers the entirety of the
// terminal (this plane is created during initialization).
//
// Functions update these virtual planes over a series of API calls. Eventually,
// notcurses_render() is called. We then do a depth buffer blit of updated
// cells. A cell is updated if the topmost plane including that cell updates it,
// not simply if any plane updates it.
//
// A plane may be partially or wholly offscreen--this might occur if the
// screen is resized, for example. Offscreen portions will not be rendered.
// Accesses beyond the borders of a panel, however, are errors.
//
// The framebuffer 'fb' is a set of rows. For scrolling, we interpret it as a
// circular buffer of rows. 'logrow' is the index of the row at the logical top
// of the plane.
typedef struct ncplane {
cell* fb; // "framebuffer" of character cells
int logrow; // logical top row, starts at 0, add one for each scroll
int x, y; // current cursor location within this plane
// ncplane_yx() etc. use coordinates relative to the plane to which this
// plane is bound, but absx/absy are always relative to the terminal origin.
// they must thus be translated by any such function.
int absx, absy; // origin of the plane relative to the screen
int lenx, leny; // size of the plane, [0..len{x,y}) is addressable
struct ncplane* above; // plane above us, NULL if we're on top
struct ncplane* below; // plane below us, NULL if we're on bottom
struct ncplane* bnext; // next in the bound list of plane to which we are bound
struct ncplane** bprev;// link to us iff we're bound, NULL otherwise
struct ncplane* blist; // head of list of bound planes
struct ncplane* boundto; // plane to which we are bound, if any
egcpool pool; // attached storage pool for UTF-8 EGCs
uint64_t channels; // works the same way as cells
void* userptr; // slot for the user to stick some opaque pointer
cell basecell; // cell written anywhere that fb[i].gcluster == 0
struct notcurses* nc; // notcurses object of which we are a part
char* name; // used only for debugging
uint16_t stylemask; // same deal as in a cell
bool scrolling; // is scrolling enabled? always disabled by default
} ncplane;
#include "blitset.h"
// current presentation state of the terminal. it is carried across render
// instances. initialize everything to 0 on a terminal reset / startup.
typedef struct renderstate {
// we assemble the encoded output in a POSIX memstream, and keep it around
// between uses. this could be a problem if it ever tremendously spiked, but
// that's a highly unlikely situation.
char* mstream; // buffer for rendering memstream, see open_memstream(3)
FILE* mstreamfp;// FILE* for rendering memstream
size_t mstrsize;// size of rendering memstream
// the current cursor position. this is independent of whether the cursor is
// visible. it is the cell at which the next write will take place. this is
// modified by: output, cursor moves, clearing the screen (during refresh)
int y, x;
uint32_t curattr;// current attributes set (does not include colors)
unsigned lastr; // foreground rgb, overloaded for palindexed fg
unsigned lastg;
unsigned lastb;
unsigned lastbr; // background rgb, overloaded for palindexed bg
unsigned lastbg;
unsigned lastbb;
// 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;
bool bgelidable;
bool fgpalelidable;
bool bgpalelidable;
bool defaultelidable;
} renderstate;
// Tablets are the toplevel entitites within an ncreel. Each corresponds to
// a single, distinct ncplane.
typedef struct nctablet {
ncplane* p; // border plane, NULL when offscreen
ncplane* cbp; // data plane, NULL when offscreen
struct nctablet* next;
struct nctablet* prev;
tabletcb cbfxn; // application callback to draw cbp
void* curry; // application data provided to cbfxn
} nctablet;
typedef struct ncreel {
ncplane* p; // ncplane this ncreel occupies, under tablets
// doubly-linked list, a circular one when infinity scrolling is in effect.
// points at the focused tablet (when at least one tablet exists, one must be
// focused). it will be visibly focused following the next redraw.
nctablet* tablets;
nctablet* vft; // the visibly-focused tablet
enum {
LASTDIRECTION_UP,
LASTDIRECTION_DOWN,
} direction; // last direction of travel
int tabletcount; // could be derived, but we keep it o(1)
ncreel_options ropts; // copied in ncreel_create()
} ncreel;
// ncmenu_item and ncmenu_section have internal and (minimal) external forms
typedef struct ncmenu_int_item {
char* desc; // utf-8 menu item, NULL for horizontal separator
ncinput shortcut; // shortcut, all should be distinct
int shortcut_offset; // column offset with desc of shortcut EGC
char* shortdesc; // description of shortcut, can be NULL
int shortdesccols; // columns occupied by shortcut description
} ncmenu_int_item;
typedef struct ncmenu_int_section {
char* name; // utf-8 c string
int itemcount;
ncmenu_int_item* items; // items, NULL iff itemcount == 0
ncinput shortcut; // shortcut, will be underlined if present in name
int xoff; // column offset from beginning of menu bar
int bodycols; // column width of longest item
int itemselected; // current item selected, -1 for no selection
int shortcut_offset; // column offset within name of shortcut EGC
} ncmenu_int_section;
typedef struct ncfdplane {
ncfdplane_callback cb; // invoked with fresh hot data
ncfdplane_done_cb donecb; // invoked on EOF (if !follow) or error
void* curry; // passed to the callbacks
int fd; // we take ownership of the fd, and close it
bool follow; // keep trying to read past the end (event-based)
ncplane* ncp; // bound ncplane
pthread_t tid; // thread servicing this i/o
bool destroyed; // set in ncfdplane_destroy() in our own context
} ncfdplane;
typedef struct ncsubproc {
ncfdplane* nfp;
pid_t pid; // subprocess
int pidfd; // for signalling/watching the subprocess
pthread_t waittid; // wait()ing thread if pidfd is not available
pthread_mutex_t lock; // guards waited
bool waited; // we've wait()ed on it, don't kill/wait further
} ncsubproc;
typedef struct ncreader {
ncplane* ncp; // always owned by ncreader
uint64_t tchannels; // channels for input text
uint32_t tattrs; // attributes for input text
} ncreader;
typedef struct ncmenu {
ncplane* ncp;
int sectioncount; // must be positive
ncmenu_int_section* sections; // NULL iff sectioncount == 0
int unrolledsection; // currently unrolled section, -1 if none
int headerwidth; // minimum space necessary to display all sections
uint64_t headerchannels; // styling for header
uint64_t sectionchannels; // styling for sections
bool bottom; // are we on the bottom (vs top)?
} ncmenu;
// terminfo cache
typedef struct tinfo {
unsigned colors;// number of colors terminfo reported usable for this screen
char* sgr; // set many graphics properties at once
char* sgr0; // restore default presentation properties
char* setaf; // set foreground color (ANSI)
char* setab; // set background color (ANSI)
char* op; // set foreground and background color to default
char* cup; // move cursor
char* cuu; // move N cells up
char* cub; // move N cells left
char* cuf; // move N cells right
char* cud; // move N cells down
char* cuf1; // move 1 cell right
char* cub1; // move 1 cell left
char* home; // home cursor
char* civis; // hide cursor
char* cnorm; // restore cursor to default state
char* hpa; // horizontal position adjusment (move cursor on row)
char* vpa; // vertical position adjustment (move cursor on column)
char* standout; // CELL_STYLE_STANDOUT
char* uline; // CELL_STYLE_UNDERLINK
char* reverse; // CELL_STYLE_REVERSE
char* blink; // CELL_STYLE_BLINK
char* dim; // CELL_STYLE_DIM
char* bold; // CELL_STYLE_BOLD
char* italics; // CELL_STYLE_ITALIC
char* italoff; // CELL_STYLE_ITALIC (disable)
char* initc; // set a palette entry's RGB value
char* oc; // restore original colors
char* clearscr; // erase screen and home cursor
char* cleareol; // clear to end of line
char* clearbol; // clear to beginning of line
char* sc; // push the cursor location onto the stack
char* rc; // pop the cursor location off the stack
char* smkx; // enter keypad transmit mode (keypad_xmit)
char* rmkx; // leave keypad transmit mode (keypad_local)
char* getm; // get mouse events
bool RGBflag; // ti-reported "RGB" flag for 24bpc truecolor
bool CCCflag; // ti-reported "CCC" flag for palette set capability
bool AMflag; // ti-reported "AM" flag for automatic movement to next line
char* smcup; // enter alternate mode
char* rmcup; // restore primary mode
} tinfo;
typedef struct ncdirect {
palette256 palette; // 256-indexed palette can be used instead of/with RGB
FILE* ttyfp; // FILE* for output tty
int ctermfd; // fd for controlling terminal
tinfo tcache; // terminfo cache
unsigned fgrgb, bgrgb; // last RGB values of foreground/background
uint16_t stylemask; // current styles
bool fgdefault, bgdefault; // are FG/BG currently using default colors?
bool utf8; // are we using utf-8 encoding, as hoped?
} ncdirect;
typedef struct notcurses {
ncplane* top; // topmost plane, never NULL
ncplane* bottom; // bottommost plane, never NULL
ncplane* stdplane;// standard plane, covers screen
// the style state of the terminal is carried across render runs
renderstate rstate;
// we keep a copy of the last rendered frame. this facilitates O(1)
// notcurses_at_yx() and O(1) damage detection (at the cost of some memory).
cell* lastframe;// last rendered framebuffer, NULL until first render
int lfdimx; // dimensions of lastframe, unchanged by screen resize
int lfdimy; // lfdimx/lfdimy are 0 until first render
egcpool pool; // duplicate EGCs into this pool
ncstats stats; // some statistics across the lifetime of the notcurses ctx
ncstats stashstats; // cumulative stats, unaffected by notcurses_reset_stats()
int truecols; // true number of columns in the physical rendering area.
// used only to see if output motion takes us to the next
// line thanks to terminal action alone.
tinfo tcache; // terminfo cache
FILE* ttyfp; // FILE* for writing rasterized data
int ttyfd; // file descriptor for controlling tty
FILE* ttyinfp; // FILE* for processing input
FILE* renderfp; // debugging FILE* to which renderings are written
struct termios tpreserved; // terminal state upon entry
bool suppress_banner; // from notcurses_options
unsigned char inputbuf[BUFSIZ];
// we keep a wee ringbuffer of input queued up for delivery. if
// inputbuf_occupied == sizeof(inputbuf), there is no room. otherwise, data
// can be read to inputbuf_write_at until we fill up. the first datum
// available for the app is at inputbuf_valid_starts iff inputbuf_occupied is
// not 0. the main purpose is working around bad predictions of escapes.
unsigned inputbuf_occupied;
unsigned inputbuf_valid_starts;
unsigned inputbuf_write_at;
// number of input events seen. does not belong in ncstats, since it must not
// be reset (semantics are relied upon by widgets for mouse click detection).
uint64_t input_events;
// desired margins (best-effort only), copied in from notcurses_options
int margin_t, margin_b, margin_r, margin_l;
int loglevel;
palette256 palette; // 256-indexed palette can be used instead of/with RGB
bool palette_damage[NCPALETTESIZE];
struct esctrie* inputescapes; // trie of input escapes -> ncspecial_keys
bool utf8; // are we using utf-8 encoding, as hoped?
bool libsixel; // do we have Sixel support?
} notcurses;
void sigwinch_handler(int signo);
void init_lang(struct notcurses* nc); // nc may be NULL, only used for logging
int terminfostr(char** gseq, const char* name);
int interrogate_terminfo(tinfo* ti);
// Search the provided multibyte (UTF8) string 's' for the provided unicode
// codepoint 'cp'. If found, return the column offset of the EGC in which the
// codepoint appears in 'col', and the byte offset as the return value. If not
// found, -1 is returned, and 'col' is meaningless.
static inline int
mbstr_find_codepoint(const char* s, char32_t cp, int* col){
mbstate_t ps;
memset(&ps, 0, sizeof(ps));
size_t bytes = 0;
size_t r;
wchar_t w;
*col = 0;
while((r = mbrtowc(&w, s + bytes, MB_CUR_MAX, &ps)) != (size_t)-1 && r != (size_t)-2){
if(r == 0){
break;
}
if(towlower(cp) == towlower(w)){
return bytes;
}
*col += wcwidth(w);
bytes += r;
}
return -1;
}
static inline ncplane*
ncplane_stdplane(ncplane* n){
return notcurses_stdplane(n->nc);
}
static inline const ncplane*
ncplane_stdplane_const(const ncplane* n){
return notcurses_stdplane_const(n->nc);
}
// load all known special keys from terminfo, and build the input sequence trie
int prep_special_keys(notcurses* nc);
// free up the input escapes trie
void input_free_esctrie(struct esctrie** trie);
// initialize libav
int ncvisual_init(int loglevel);
static inline int
fbcellidx(int row, int rowlen, int col){
//fprintf(stderr, "row: %d rowlen: %d col: %d\n", row, rowlen, col);
return row * rowlen + col;
}
// take a logical 'y' and convert it to the virtual 'y'. see HACKING.md.
static inline int
logical_to_virtual(const ncplane* n, int y){
//fprintf(stderr, "y: %d n->logrow: %d n->leny: %d\n", y, n->logrow, n->leny);
return (y + n->logrow) % n->leny;
}
static inline int
nfbcellidx(const ncplane* n, int row, int col){
return fbcellidx(logical_to_virtual(n, row), n->lenx, col);
}
// For our first attempt, O(1) uniform conversion from 8-bit r/g/b down to
// ~2.4-bit 6x6x6 cube + greyscale (assumed on entry; I know no way to
// even semi-portably recover the palette) proceeds via: map each 8-bit to
// a 5-bit target grey. if all 3 components match, select that grey.
// otherwise, c / 42.7 to map to 6 values.
static inline int
rgb_quantize_256(unsigned r, unsigned g, unsigned b){
const unsigned GREYMASK = 0xf8;
// if all 5 MSBs match, return grey from 24-member grey ramp or pure
// black/white from original 16 (0 and 15, respectively)
if((r & GREYMASK) == (g & GREYMASK) && (g & GREYMASK) == (b & GREYMASK)){
// 256 / 26 == 9.846
int gidx = r * 5 / 49 - 1;
if(gidx < 0){
return 0;
}
if(gidx >= 24){
return 15;
}
return 232 + gidx;
}
r /= 43;
g /= 43;
b /= 43;
return r * 36 + g * 6 + b + 16;
}
// We get black, red, green, yellow, blue, magenta, cyan, and white. Ugh. Under
// an additive model: G + R -> Y, G + B -> C, R + B -> M, R + G + B -> W
static inline int
rgb_quantize_8(unsigned r, unsigned g, unsigned b){
static const int BLACK = 0;
static const int RED = 1;
static const int GREEN = 2;
static const int YELLOW = 3;
static const int BLUE = 4;
static const int MAGENTA = 5;
static const int CYAN = 6;
static const int WHITE = 7;
if(r < 128){ // we have no red
if(g < 128){ // we have no green
if(b < 128){
return BLACK;
}
return BLUE;
} // we have green
if(b < 128){
return GREEN;
}
return CYAN;
}else if(g < 128){ // we have red, but not green
if(b < 128){
return RED;
}
return MAGENTA;
}else if(b < 128){ // we have red and green
return YELLOW;
}
return WHITE;
}
// Given r, g, and b values 0..255, do a weighted average per Rec. 601, and
// return the 8-bit greyscale value (this value will be the r, g, and b value
// for the new color).
static inline int
rgb_greyscale(int r, int g, int b){
if(r < 0 || r > 255){
return -1;
}
if(g < 0 || g > 255){
return -1;
}
if(b < 0 || b > 255){
return -1;
}
// Use Rec. 601 scaling plus linear approximation of gamma decompression
float fg = (0.299 * (r / 255.0) + 0.587 * (g / 255.0) + 0.114 * (b / 255.0));
return fg * 255;
}
static inline int
tty_emit(const char* name __attribute__ ((unused)), const char* seq, int fd){
if(!seq){
return -1;
}
size_t slen = strlen(seq);
size_t written = 0;
do{
ssize_t ret = write(fd, seq, slen);
if(ret > 0){
written += ret;
}
if(ret < 0){
if(errno != EAGAIN){
break;
}
}
}while(written < slen);
if(written < slen){
//fprintf(stderr, "Error emitting %zub %s escape (%s)\n", strlen(seq), name, strerror(errno));
return -1;
}
return 0;
}
static inline int
term_emit(const char* name __attribute__ ((unused)), const char* seq,
FILE* out, bool flush){
if(!seq){
return -1;
}
if(fputs(seq, out) == EOF){
//fprintf(stderr, "Error emitting %zub %s escape (%s)\n", strlen(seq), name, strerror(errno));
return -1;
}
if(flush){
while(fflush(out) == EOF){
if(errno != EAGAIN){
fprintf(stderr, "Error flushing after %zub %s sequence (%s)\n",
strlen(seq), name, strerror(errno));
return -1;
}
}
}
return 0;
}
static inline int
term_bg_palindex(const notcurses* nc, FILE* out, unsigned pal){
if(nc->tcache.setab == NULL){
return 0;
}
return term_emit("setab", tiparm(nc->tcache.setab, pal), out, false);
}
static inline int
term_fg_palindex(const notcurses* nc, FILE* out, unsigned pal){
if(nc->tcache.setaf == NULL){
return 0;
}
return term_emit("setaf", tiparm(nc->tcache.setaf, pal), out, false);
}
static inline const char*
pool_extended_gcluster(const egcpool* pool, const cell* c){
if(cell_simple_p(c)){
return (const char*)&c->gcluster;
}
return egcpool_extended_gcluster(pool, c);
}
cell* ncplane_cell_ref_yx(ncplane* n, int y, int x);
static inline void
cell_set_wide(cell* c){
c->channels |= CELL_WIDEASIAN_MASK;
}
#define NANOSECS_IN_SEC 1000000000
static inline uint64_t
timespec_to_ns(const struct timespec* t){
return t->tv_sec * NANOSECS_IN_SEC + t->tv_nsec;
}
static inline struct timespec*
ns_to_timespec(uint64_t ns, struct timespec* ts){
ts->tv_sec = ns / NANOSECS_IN_SEC;
ts->tv_nsec = ns % NANOSECS_IN_SEC;
return ts;
}
static inline void
cell_debug(const egcpool* p, const cell* c){
fprintf(stderr, "gcluster: %u %s style: 0x%04x chan: 0x%016jx\n",
c->gcluster, egcpool_extended_gcluster(p, c), c->stylemask, c->channels);
}
static inline void
plane_debug(const ncplane* n, bool details){
int dimy, dimx;
ncplane_dim_yx(n, &dimy, &dimx);
fprintf(stderr, "p: %p dim: %d/%d poolsize: %d\n", n, dimy, dimx, n->pool.poolsize);
if(details){
for(int y = 0 ; y < 1 ; ++y){
for(int x = 0 ; x < 10 ; ++x){
const cell* c = &n->fb[fbcellidx(y, dimx, x)];
fprintf(stderr, "[%03d/%03d] ", y, x);
cell_debug(&n->pool, c);
}
}
}
}
static inline void
pool_release(egcpool* pool, cell* c){
if(!cell_simple_p(c)){
egcpool_release(pool, cell_egc_idx(c));
c->gcluster = 0; // don't subject ourselves to double-release problems
}
}
// Duplicate one cell onto another, possibly crossing ncplanes.
static inline int
cell_duplicate_far(egcpool* tpool, cell* targ, const ncplane* splane, const cell* c){
pool_release(tpool, targ);
targ->stylemask = c->stylemask;
targ->channels = c->channels;
if(cell_simple_p(c)){
targ->gcluster = c->gcluster;
return 0;
}
assert(splane);
const char* egc = cell_extended_gcluster(splane, c);
size_t ulen = strlen(egc);
int eoffset = egcpool_stash(tpool, egc, ulen);
if(eoffset < 0){
return -1;
}
targ->gcluster = 0x01000000ul + eoffset;
return 0;
}
int ncplane_resize_internal(ncplane* n, int keepy, int keepx,
int keepleny, int keeplenx, int yoff, int xoff,
int ylen, int xlen);
int update_term_dimensions(int fd, int* rows, int* cols);
static inline void*
memdup(const void* src, size_t len){
void* ret = malloc(len);
if(ret){
memcpy(ret, src, len);
}
return ret;
}
void* bgra_to_rgba(const void* data, int rows, int rowstride, int cols);
int rgba_blit_dispatch(ncplane* nc, const struct blitset* bset, int placey,
int placex, int linesize, const void* data, int begy,
int begx, int leny, int lenx, bool blendcolors);
// find the "center" cell of two lengths. in the case of even rows/columns, we
// place the center on the top/left. in such a case there will be one more
// cell to the bottom/right of the center.
static inline void
center_box(int* RESTRICT y, int* RESTRICT x){
if(y){
*y = (*y - 1) / 2;
}
if(x){
*x = (*x - 1) / 2;
}
}
// find the "center" cell of a plane. in the case of even rows/columns, we
// place the center on the top/left. in such a case there will be one more
// cell to the bottom/right of the center.
static inline void
ncplane_center(const ncplane* n, int* RESTRICT y, int* RESTRICT x){
*y = n->leny;
*x = n->lenx;
center_box(y, x);
}
int ncvisual_bounding_box(const struct ncvisual* ncv, int* leny, int* lenx,
int* offy, int* offx);
// Our gradient is a 2d lerp among the four corners of the region. We start
// with the observation that each corner ought be its exact specified corner,
// and the middle ought be the exact average of all four corners' components.
// Another observation is that if all four corners are the same, every cell
// ought be the exact same color. From this arises the observation that a
// perimeter element is not affected by the other three sides:
//
// a corner element is defined by itself
// a perimeter element is defined by the two points on its side
// an internal element is defined by all four points
//
// 2D equation of state: solve for each quadrant's contribution (min 2x2):
//
// X' = (xlen - 1) - X
// Y' = (ylen - 1) - Y
// TLC: X' * Y' * TL
// TRC: X * Y' * TR
// BLC: X' * Y * BL
// BRC: X * Y * BR
// steps: (xlen - 1) * (ylen - 1) [maximum steps away from origin]
//
// Then add TLC + TRC + BLC + BRC + steps / 2, and divide by steps (the
// steps / 2 is to work around truncate-towards-zero).
static int
calc_gradient_component(unsigned tl, unsigned tr, unsigned bl, unsigned br,
int y, int x, int ylen, int xlen){
assert(y >= 0);
assert(y < ylen);
assert(x >= 0);
assert(x < xlen);
const int avm = (ylen - 1) - y;
const int ahm = (xlen - 1) - x;
if(xlen < 2){
if(ylen < 2){
return tl;
}
return (tl * avm + bl * y) / (ylen - 1);
}
if(ylen < 2){
return (tl * ahm + tr * x) / (xlen - 1);
}
const int tlc = ahm * avm * tl;
const int blc = ahm * y * bl;
const int trc = x * avm * tr;
const int brc = y * x * br;
const int divisor = (ylen - 1) * (xlen - 1);
return ((tlc + blc + trc + brc) + divisor / 2) / divisor;
}
// calculate one of the channels of a gradient at a particular point.
static inline uint32_t
calc_gradient_channel(uint32_t ul, uint32_t ur, uint32_t ll, uint32_t lr,
int y, int x, int ylen, int xlen){
uint32_t chan = 0;
channel_set_rgb_clipped(&chan,
calc_gradient_component(channel_r(ul), channel_r(ur),
channel_r(ll), channel_r(lr),
y, x, ylen, xlen),
calc_gradient_component(channel_g(ul), channel_g(ur),
channel_g(ll), channel_g(lr),
y, x, ylen, xlen),
calc_gradient_component(channel_b(ul), channel_b(ur),
channel_b(ll), channel_b(lr),
y, x, ylen, xlen));
channel_set_alpha(&chan, channel_alpha(ul)); // precondition: all αs are equal
return chan;
}
// calculate both channels of a gradient at a particular point, storing them
// into `channels'. x and y ought be the location within the gradient.
static inline void
calc_gradient_channels(uint64_t* channels, uint64_t ul, uint64_t ur,
uint64_t ll, uint64_t lr, int y, int x,
int ylen, int xlen){
if(!channels_fg_default_p(ul)){
channels_set_fchannel(channels,
calc_gradient_channel(channels_fchannel(ul),
channels_fchannel(ur),
channels_fchannel(ll),
channels_fchannel(lr),
y, x, ylen, xlen));
}else{
channels_set_fg_default(channels);
}
if(!channels_bg_default_p(ul)){
channels_set_bchannel(channels,
calc_gradient_channel(channels_bchannel(ul),
channels_bchannel(ur),
channels_bchannel(ll),
channels_bchannel(lr),
y, x, ylen, xlen));
}else{
channels_set_bg_default(channels);
}
}
// ncdirect needs to "fake" an isolated ncplane as a drawing surface for
// ncvisual_render(), and thus calls these low-level internal functions.
// they are not for general use -- check ncplane_new() and ncplane_destroy().
ncplane* ncplane_create(notcurses* nc, ncplane* n, int rows, int cols,
int yoff, int xoff, void* opaque, const char* name);
void free_plane(ncplane* p);
// heap-allocated formatted output
char* ncplane_vprintf_prep(const char* format, va_list ap);
// Resize the provided ncviusal to the specified 'rows' x 'cols', but do not
// change the internals of the ncvisual. Uses oframe.
nc_err_e ncvisual_blit(struct ncvisual* ncv, int rows, int cols,
ncplane* n, const struct blitset* bset,
int placey, int placex, int begy, int begx,
int leny, int lenx, bool blendcolors);
void nclog(const char* fmt, ...);
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);
// logging
#define logerror(nc, fmt, ...) do{ \
if(nc){ if((nc)->loglevel >= NCLOGLEVEL_ERROR){ \
nclog("%s:%d:" fmt, __func__, __LINE__, ##__VA_ARGS__); } \
}else{ fprintf(stderr, "%s:%d:" fmt, __func__, __LINE__, ##__VA_ARGS__); } \
}while(0);
#define logwarn(nc, fmt, ...) do{ \
if(nc){ if((nc)->loglevel >= NCLOGLEVEL_WARNING){ \
nclog("%s:%d:" fmt, __func__, __LINE__, ##__VA_ARGS__); } \
}else{ fprintf(stderr, "%s:%d:" fmt, __func__, __LINE__, ##__VA_ARGS__); } \
}while(0);
#define loginfo(nc, fmt, ...) do{ \
if(nc){ if((nc)->loglevel >= NCLOGLEVEL_INFO){ \
nclog("%s:%d:" fmt, __func__, __LINE__, ##__VA_ARGS__); } \
} }while(0);
#define logverbose(nc, fmt, ...) do{ \
if(nc){ if((nc)->loglevel >= NCLOGLEVEL_VERBOSE){ \
nclog("%s:%d:" fmt, __func__, __LINE__, ##__VA_ARGS__); } \
} }while(0);
#define logdebug(nc, fmt, ...) do{ \
if(nc){ if((nc)->loglevel >= NCLOGLEVEL_DEBUG){ \
nclog("%s:%d:" fmt, __func__, __LINE__, ##__VA_ARGS__); } \
} }while(0);
#define logtrace(nc, fmt, ...) do{ \
if(nc){ 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);
int term_setstyle(FILE* out, unsigned cur, unsigned targ, unsigned stylebit,
const char* ton, const char* toff);
// how many edges need touch a corner for it to be printed?
static inline unsigned
box_corner_needs(unsigned ctlword){
return (ctlword & NCBOXCORNER_MASK) >> NCBOXCORNER_SHIFT;
}
// True if the cell does not generate background pixels (i.e., the cell is a
// solid or shaded block, or certain emoji).
static inline bool
cell_nobackground_p(const cell* c){
return c->channels & CELL_NOBACKGROUND_MASK;
}
// Destroy a plane and all its bound descendants.
int ncplane_genocide(ncplane *ncp);
// Returns the result of blending two channels. 'blends' indicates how heavily
// 'c1' ought be weighed. If 'blends' is 0, 'c1' will be entirely replaced by
// 'c2'. If 'c1' is otherwise the default color, 'c1' will not be touched,
// since we can't blend default colors. Likewise, if 'c2' is a default color,
// it will not be used (unless 'blends' is 0).
//
// Palette-indexed colors do not blend. Do not pass me palette-indexed channels!
static inline unsigned
channels_blend(unsigned c1, unsigned c2, unsigned* blends){
if(channel_alpha(c2) == CELL_ALPHA_TRANSPARENT){
return c1; // do *not* increment *blends
}
unsigned rsum, gsum, bsum;
channel_rgb(c2, &rsum, &gsum, &bsum);
bool c2default = channel_default_p(c2);
if(*blends == 0){
// don't just return c2, or you set wide status and all kinds of crap
if(channel_default_p(c2)){
channel_set_default(&c1);
}else{
channel_set_rgb(&c1, rsum, gsum, bsum);
}
channel_set_alpha(&c1, channel_alpha(c2));
}else if(!c2default && !channel_default_p(c1)){
rsum = (channel_r(c1) * *blends + rsum) / (*blends + 1);
gsum = (channel_g(c1) * *blends + gsum) / (*blends + 1);
bsum = (channel_b(c1) * *blends + bsum) / (*blends + 1);
channel_set_rgb(&c1, rsum, gsum, bsum);
channel_set_alpha(&c1, channel_alpha(c2));
}
++*blends;
return c1;
}
// do not pass palette-indexed channels!
static inline uint64_t
cell_blend_fchannel(cell* cl, unsigned channel, unsigned* blends){
return cell_set_fchannel(cl, channels_blend(cell_fchannel(cl), channel, blends));
}
static inline uint64_t
cell_blend_bchannel(cell* cl, unsigned channel, unsigned* blends){
return cell_set_bchannel(cl, channels_blend(cell_bchannel(cl), channel, blends));
}
static inline int
pool_load_direct(egcpool* pool, cell* c, const char* gcluster, int bytes, int cols){
if(bytes < 0 || cols < 0){
return -1;
}
if(bytes <= 1){
assert(cols < 2);
pool_release(pool, c);
c->channels &= ~(CELL_WIDEASIAN_MASK | CELL_NOBACKGROUND_MASK);
c->gcluster = *gcluster;
return bytes;
}
// FIXME also shaded blocks! ░ etc. are there combined EGCs involving these?
if(strcmp(gcluster, "\xe2\x96\x88")){
c->channels &= ~CELL_NOBACKGROUND_MASK;
if(cols < 2){
c->channels &= ~CELL_WIDEASIAN_MASK;
}else{
c->channels |= CELL_WIDEASIAN_MASK;
}
}else{
c->channels |= CELL_NOBACKGROUND_MASK;
c->channels &= ~CELL_WIDEASIAN_MASK;
}
if(bytes <= 4){
if(strcmp(gcluster, (const char*)&c->gcluster)){
pool_release(pool, c);
c->gcluster = 0;
memcpy(&c->gcluster, gcluster, bytes);
}
return bytes;
}
int eoffset = egcpool_stash(pool, gcluster, bytes);
if(eoffset < 0){
return -1;
}
pool_release(pool, c);
c->gcluster = 0x01000000ul + eoffset;
return bytes;
}
static inline int
pool_load(egcpool* pool, cell* c, const char* gcluster){
int cols;
int bytes = utf8_egc_len(gcluster, &cols);
return pool_load_direct(pool, c, gcluster, bytes, cols);
}
// increment y by 1 and rotate the framebuffer up one line. x moves to 0.
void scroll_down(ncplane* n);
#ifdef __cplusplus
}
#endif
#endif