1 #ifndef NOTCURSES_INTERNAL
2 #define NOTCURSES_INTERNAL
3 
4 #ifdef __cplusplus
5 extern "C" {
6 #endif
7 
8 #include "version.h"
9 #include "builddef.h"
10 #include "compat/compat.h"
11 #include "notcurses/ncport.h"
12 #include "notcurses/notcurses.h"
13 #include "notcurses/direct.h"
14 
15 #ifndef __MINGW64__
16 #define API __attribute__((visibility("default")))
17 #else
18 #define API __declspec(dllexport)
19 #endif
20 #define ALLOC __attribute__((malloc)) __attribute__((warn_unused_result))
21 
22 // KEY_EVENT is defined by both ncurses.h (prior to 6.3) and wincon.h. since we
23 // don't use either definition, kill it before inclusion of ncurses.h.
24 #undef KEY_EVENT
25 #include <term.h>
26 #include <time.h>
27 #include <term.h>
28 #include <stdio.h>
29 #include <stdint.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <string.h>
34 #include <signal.h>
35 #include <wctype.h>
36 #include <pthread.h>
37 #include <stdbool.h>
38 #include <inttypes.h>
39 #include <unictype.h>
40 #ifndef __MINGW64__
41 #include <langinfo.h>
42 #endif
43 #include "lib/termdesc.h"
44 #include "lib/egcpool.h"
45 #include "lib/sprite.h"
46 #include "lib/fbuf.h"
47 #include "lib/gpm.h"
48 
49 struct sixelmap;
50 struct ncvisual_details;
51 
52 // Was this glyph drawn as part of an ncvisual? If so, we need to honor
53 // blitter stacking rather than the standard trichannel solver.
54 #define NC_BLITTERSTACK_MASK  NC_NOBACKGROUND_MASK
55 
56 // we can't define multipart ncvisual here, because OIIO requires C++ syntax,
57 // and we can't go throwing C++ syntax into this header. so it goes.
58 
59 // A plane is memory for some rectilinear virtual window, plus current cursor
60 // state for that window, and part of a pile. Each pile has a total order along
61 // its z-axis. Functions update these virtual planes over a series of API
62 // calls. Eventually, notcurses_render() is called. We then do a depth buffer
63 // blit of updated cells. A cell is updated if the topmost plane including that
64 // cell updates it, not simply if any plane updates it.
65 //
66 // A plane may be partially or wholly offscreen--this might occur if the
67 // screen is resized, for example. Offscreen portions will not be rendered.
68 // Accesses beyond the borders of a panel, however, are errors.
69 //
70 // The framebuffer 'fb' is a set of rows. For scrolling, we interpret it as a
71 // circular buffer of rows. 'logrow' is the index of the row at the logical top
72 // of the plane. It only changes from 0 if the plane is scrollable.
73 typedef struct ncplane {
74   nccell* fb;            // "framebuffer" of character cells
75   int logrow;            // logical top row, starts at 0, add one for each scroll
76   unsigned x, y;         // current cursor location within this plane
77   // ncplane_yx() etc. use coordinates relative to the plane to which this
78   // plane is bound, but absx/absy are always relative to the terminal origin.
79   // they must thus be translated by any function which moves a parent plane.
80   int absx, absy;        // origin of the plane relative to the pile's origin
81                          //  also used as left and top margin on resize by
82                          //  ncplane_resize_marginalized()
83   unsigned lenx, leny;   // size of the plane, [0..len{x,y}) is addressable
84   egcpool pool;          // attached storage pool for UTF-8 EGCs
85   uint64_t channels;     // works the same way as cells
86 
87   // a notcurses context is made up of piles, each rooted by one or more root
88   // planes. each pile has its own (totally ordered) z-axis.
89   struct ncpile* pile;   // pile of which we are a part
90   struct ncplane* above; // plane above us, NULL if we're on top
91   struct ncplane* below; // plane below us, NULL if we're on bottom
92 
93   // every plane is bound to some other plane, unless it is a root plane of a
94   // pile. a pile has a set of one or more root planes, all of them siblings.
95   // root planes are bound to themselves. the standard plane is always a root
96   // plane (since it cannot be reparented). a path exists to a root plane.
97   struct ncplane* bnext;  // next in the blist
98   struct ncplane** bprev; // blist link back to us
99   struct ncplane* blist;  // head of list of bound planes
100   struct ncplane* boundto;// plane to which we are bound (ourself for roots)
101 
102   sprixel* sprite;       // pointer into the sprixel cache
103   tament* tam;           // transparency-annihilation sprite matrix
104 
105   void* userptr;         // slot for the user to stick some opaque pointer
106   int (*resizecb)(struct ncplane*); // callback after parent is resized
107   nccell basecell;       // cell written anywhere that fb[i].gcluster == 0
108   char* name;            // used only for debugging
109   ncalign_e halign;      // relative to parent plane, for automatic realignment
110   ncalign_e valign;      // relative to parent plane, for automatic realignment
111   uint16_t stylemask;    // same deal as in a cell
112   int margin_b, margin_r;// bottom and right margins, stored for resize
113   bool scrolling;        // is scrolling enabled? always disabled by default
114   bool fixedbound;       // are we fixed relative to the parent's scrolling?
115   bool autogrow;         // do we grow to accommodate output?
116 
117   // we need to track any widget to which we are bound, so that (1) we don't
118   // end up bound to two widgets and (2) we can clean them up on shutdown
119   // (assuming they weren't explicitly cleaned up by the client).
120   void* widget;            // widget to which we are bound, can be NULL
121   void(*wdestruct)(void*); // widget destructor, NULL iff widget is NULL
122 } ncplane;
123 
124 // current presentation state of the terminal. it is carried across render
125 // instances. initialize everything to 0 on a terminal reset / startup.
126 typedef struct rasterstate {
127   // we assemble the encoded (rasterized) output in an fbuf (a portable POSIX
128   // memstream, basically), and keep it around between uses. this could be a
129   // problem if it ever tremendously spiked, but that seems unlikely?
130   fbuf f;         // buffer for preparing raster glyph/escape stream
131 
132   // the current cursor position. this is independent of whether the cursor is
133   // visible. it is the cell at which the next write will take place. this is
134   // modified by: output, cursor moves, clearing the screen (during refresh).
135   int y, x;
136 
137   const ncplane* lastsrcp; // last source plane (we emit hpa on plane changes)
138 
139   unsigned lastr;   // foreground rgb, overloaded for palindexed fg
140   unsigned lastg;
141   unsigned lastb;
142   unsigned lastbr;  // background rgb, overloaded for palindexed bg
143   unsigned lastbg;
144   unsigned lastbb;
145 
146   // used in CLI mode, these track the end of logical output, to place the
147   // cursor following each rasterization. they are tracked thusly:
148   //  * initialized to the initial physical cursor position
149   //  * when we write to a physical row greater than logendy, update both
150   //  * when we scroll, subtract one from logendy
151   //   * if logendy reaches -1, reset both to 0
152   int logendy, logendx;
153 
154   uint16_t curattr; // current attributes set (does not include colors)
155   // we elide a color escape iff the color has not changed between two cells
156   bool fgelidable;
157   bool bgelidable;
158   bool fgpalelidable;
159   bool bgpalelidable;
160   bool fgdefelidable;
161   bool bgdefelidable;
162 } rasterstate;
163 
164 // Tablets are the toplevel entitites within an ncreel. Each corresponds to
165 // a single, distinct ncplane.
166 typedef struct nctablet {
167   ncplane* p;                  // border plane, NULL when offscreen
168   ncplane* cbp;                // data plane, NULL when offscreen
169   struct nctablet* next;
170   struct nctablet* prev;
171   tabletcb cbfxn;              // application callback to draw cbp
172   void* curry;                 // application data provided to cbfxn
173 } nctablet;
174 
175 typedef struct ncreel {
176   ncplane* p;           // ncplane this ncreel occupies, under tablets
177   // doubly-linked list, a circular one when infinity scrolling is in effect.
178   // points at the focused tablet (when at least one tablet exists, one must be
179   // focused). it will be visibly focused following the next redraw.
180   nctablet* tablets;
181   nctablet* vft;        // the visibly-focused tablet
182   enum {
183     LASTDIRECTION_UP,
184     LASTDIRECTION_DOWN,
185   } direction;          // last direction of travel
186   int tabletcount;      // could be derived, but we keep it o(1)
187   ncreel_options ropts; // copied in ncreel_create()
188 } ncreel;
189 
190 typedef struct ncfdplane {
191   ncfdplane_callback cb;      // invoked with fresh hot data
192   ncfdplane_done_cb donecb;   // invoked on EOF (if !follow) or error
193   void* curry;                // passed to the callbacks
194   int fd;                     // we take ownership of the fd, and close it
195   bool follow;                // keep trying to read past the end (event-based)
196   ncplane* ncp;               // bound ncplane
197   pthread_t tid;              // thread servicing this i/o
198   bool destroyed;             // set in ncfdplane_destroy() in our own context
199 } ncfdplane;
200 
201 typedef struct ncsubproc {
202   ncfdplane* nfp;
203   pid_t pid;                  // subprocess
204   int pidfd;                  // for signalling/watching the subprocess
205   pthread_t waittid;          // wait()ing thread if pidfd is not available
206   pthread_mutex_t lock;       // guards waited
207   bool waited;                // we've wait()ed on it, don't kill/wait further
208 } ncsubproc;
209 
210 typedef struct ncreader {
211   ncplane* ncp;               // always owned by ncreader
212   uint64_t tchannels;         // channels for input text
213   uint32_t tattrs;            // attributes for input text
214   ncplane* textarea;          // grows as needed iff scrolling is enabled
215   int xproject;               // virtual x location of ncp origin on textarea
216   bool horscroll;             // is there horizontal panning?
217   bool no_cmd_keys;           // are shortcuts disabled?
218   bool manage_cursor;         // enable and place a virtual cursor
219 } ncreader;
220 
221 typedef struct ncprogbar {
222   ncplane* ncp;
223   double progress;          // on the range [0, 1]
224   uint32_t ulchannel, urchannel, blchannel, brchannel;
225   bool retrograde;
226 } ncprogbar;
227 
228 typedef struct nctab {
229   struct nctabbed* nt; // The nctabbed this belongs to
230   tabcb cb;     // tab callback
231   char* name;   // tab name
232   int namecols; // tab name width in columns
233   void* curry;  // user pointer
234   struct nctab* prev;
235   struct nctab* next;
236 } nctab;
237 
238 // various moving parts within a notcurses context (and the user) might need to
239 // access the stats object, so throw a lock on it. we don't want the lock in
240 // the actual structure since (a) it's usually unnecessary and (b) it breaks
241 // memset() and memcpy().
242 typedef struct ncsharedstats {
243   pthread_mutex_t lock;
244   ncstats s;
245 } ncsharedstats;
246 
247 typedef struct ncdirect {
248   ncpalette palette;         // 256-indexed palette can be used instead of/with RGB
249   FILE* ttyfp;               // FILE* for output tty
250   tinfo tcache;              // terminfo cache
251   uint64_t channels;         // current channels
252   uint16_t stylemask;        // current styles
253   uint64_t flags;            // copied in ncdirect_init() from param
254   ncsharedstats stats;       // stats! not as broadly used as in notcurses
255 } ncdirect;
256 
257 // Extracellular state for a cell during the render process. There is one
258 // crender per rendered cell, and they are initialized to all zeroes.
259 struct crender {
260   const ncplane *p; // source of glyph for this cell
261   nccell c;
262   uint32_t hcfg;       // fg channel prior to HIGHCONTRAST (need full channel)
263   sprixel* sprixel;    // bitmap encountered during traversal
264   struct {
265     // If the glyph we render is from an ncvisual, and has a transparent or
266     // blended background, blitter stacking is in effect. This is a complicated
267     // issue, but essentially, imagine a bottom block is rendered with a green
268     // bottom and transparent top. on a lower plane, a top block is rendered
269     // with a red foreground and blue background. Normally, this would result
270     // in a blue top and green bottom, but that's not what we ever wanted --
271     // what makes sense is a red top and green bottom. So ncvisual rendering
272     // sets bits from CELL_BLITTERSTACK_MASK when rendering a cell with a
273     // transparent background. When paint() selects a glyph, it checks for these
274     // bits. If they are set, any lower planes with CELL_BLITTERSTACK_MASK set
275     // take this into account when solving the background color.
276     unsigned blittedquads: 4;
277     unsigned damaged: 1; // only used in rasterization
278     // if NCALPHA_HIGHCONTRAST is in play, we apply the HSV flip once the
279     // background is locked in. set highcontrast to indicate this.
280     unsigned highcontrast: 1;
281     unsigned fgblends: 8;
282     unsigned bgblends: 8;
283     // we'll need recalculate the foreground relative to the solved background,
284     // and then reapply any foreground shading from above the highcontrast
285     // declaration. save the foreground state when we go highcontrast.
286     unsigned hcfgblends: 8; // number of foreground blends prior to HIGHCONTRAST
287     unsigned sprixeled: 1; // have we passed through a sprixel?
288     unsigned p_beats_sprixel: 1; // did we solve for our glyph above the bitmap?
289   } s;
290 };
291 
292 // a pile is a collection of planes which will be rendered together. piles are
293 // completely distinct with regards to thread-safety; one can always operate
294 // concurrently on distinct piles (save rasterizing, of course). material from
295 // other piles will be blown away whenever a pile is rasterized. no ordering
296 // is meaningful between planes. when a new one is added, or one is destroyed,
297 // the pilelock (in struct notcurses) is taken. a pile is destroyed whenever
298 // its last plane is destroyed or reparented away. a pile contains a totally-
299 // ordered list of piles (ordered on the z axis) and a directed acyclic forest
300 // of bound planes, with each root plane rooting a DAG.
301 //
302 // the geometries are updated each time the pile is rendered. until a pile is
303 // rendered, its geometries might be out-of-date with regards to the terminal
304 // description in tcache. when it is rendered again, the geometries will be
305 // updated, sprixels will have their TAMs rebuilt (if necessary), and each
306 // root plane will have its resize callback invoked (possibly invoking its
307 // bound planes' resize callbacks in turn).
308 //
309 // at context start, there is one pile (the standard pile), containing one
310 // plane (the standard plane). each ncplane holds a pointer to its pile.
311 typedef struct ncpile {
312   ncplane* top;               // topmost plane, never NULL
313   ncplane* bottom;            // bottommost plane, never NULL
314   ncplane* roots;             // head of root plane list
315   struct crender* crender;    // array (rows * cols crender objects)
316   struct notcurses* nc;       // notcurses context
317   struct ncpile *prev, *next; // circular list pointers
318   size_t crenderlen;          // size of crender vector
319   unsigned dimy, dimx;        // rows and cols at last render/creation
320   unsigned cellpxx, cellpxy;  // cell-pixel geometry at last render/creation
321   int scrolls;                // how many real lines need be scrolled at raster
322   sprixel* sprixelcache;      // sorted list of sprixels, assembled during paint
323 } ncpile;
324 
325 // the standard pile can be reached through ->stdplane.
326 typedef struct notcurses {
327   ncplane* stdplane; // standard plane, covers screen
328 
329   // the style state of the terminal is carried across render runs
330   rasterstate rstate;
331 
332   // we keep a copy of the last rendered frame. this facilitates O(1)
333   // notcurses_at_yx() and O(1) damage detection (at the cost of some memory).
334   // FIXME why isn't this just an ncplane rather than ~10 different members?
335   nccell* lastframe;// last rasterized framebuffer, NULL until first raster
336   // the last pile we rasterized. NULL until we've rasterized once. might
337   // be invalid due to the pile being destroyed; you are only allowed to
338   // evaluate it for equality to the pile being currently rasterized. when
339   // we switch piles, we need to clear all displayed sprixels, and
340   // invalidate the new pile's, pursuant to their display.
341   ncpile* last_pile;
342   egcpool pool;   // egcpool for lastframe
343 
344   unsigned lfdimx; // dimensions of lastframe, unchanged by screen resize
345   unsigned lfdimy; // lfdimx/lfdimy are 0 until first rasterization
346 
347   int cursory;    // desired cursor placement according to user.
348   int cursorx;    // -1 is don't-care, otherwise moved here after each render.
349 
350   ncsharedstats stats;   // some statistics across the lifetime of the context
351   ncstats stashed_stats; // retain across a context reset, for closing banner
352 
353   FILE* ttyfp;    // FILE* for writing rasterized data
354   tinfo tcache;   // terminfo cache
355   pthread_mutex_t pilelock; // guards pile list, locks resize in render
356 
357   // desired margins (best-effort only), copied in from notcurses_options
358   int margin_t, margin_b, margin_r, margin_l;
359   int loglevel;
360   ncpalette palette; // 256-indexed palette can be used instead of/with RGB
361   bool palette_damage[NCPALETTESIZE];
362   bool touched_palette; // have we ever changed a palette entry?
363   uint64_t flags;  // copied from notcurses_options
364 } notcurses;
365 
366 typedef struct blitterargs {
367   // FIXME begy/begx are really only of interest to scaling; they ought be
368   // consumed there, and blitters ought always work with the scaled output.
369   int begy;            // upper left start within visual
370   int begx;
371   int leny;            // number of source pixels to use
372   int lenx;
373   uint64_t flags;      // flags (as selected from ncvisual_options->flags)
374   uint32_t transcolor; // if non-zero, treat the lower 24 bits as a transparent color
375   union { // cell vs pixel-specific arguments
376     struct {
377       int placey;      // placement within ncplane
378       int placex;
379     } cell;            // for cells
380     struct {
381       int colorregs;   // number of color registers
382       sprixel* spx;    // sprixel object
383       int pxoffy;      // pixel y-offset within origin cell
384       int pxoffx;      // pixel x-offset within origin cell
385       int cellpxy;     // targeted cell-pixel geometry. this ought come from the
386       int cellpxx;     // target ncpile, or tcache in Direct Mode
387     } pixel;           // for pixels
388   } u;
389 } blitterargs;
390 
391 // scaledy and scaledx are output geometry from scaling; data is output data
392 // from scaling. we might actually need more pixels due to framing concerns,
393 // in which case just assume transparent input pixels where needed.
394 typedef int (*ncblitter)(struct ncplane* n, int linesize, const void* data,
395                          int scaledy, int scaledx, const blitterargs* bargs);
396 
397 // a system for rendering RGBA pixels as text glyphs or sixel/kitty bitmaps
398 struct blitset {
399   ncblitter_e geom;
400   unsigned width;   // number of input pixels per output cell, width
401   unsigned height;  // number of input pixels per output cell, height
402   // the EGCs which form the blitter. bits grow left to right, and then top to
403   // bottom. the first character is always a space, the last a full block.
404   const wchar_t* egcs;
405   // the EGCs which form the various levels of a given plotset. if the geometry
406   // is wide, things are arranged with the rightmost side increasing most
407   // quickly, i.e. it can be indexed as height arrays of 1 + height glyphs. i.e.
408   // the first five braille EGCs are all 0 on the left, [0..4] on the right.
409   const wchar_t* plotegcs;
410   ncblitter blit;
411   const char* name;
412   bool fill;
413 };
414 
415 #include "blitset.h"
416 
417 void reset_stats(ncstats* stats);
418 void summarize_stats(notcurses* nc);
419 
420 void update_raster_stats(const struct timespec* time1, const struct timespec* time0, ncstats* stats);
421 void update_render_stats(const struct timespec* time1, const struct timespec* time0, ncstats* stats);
422 void update_raster_bytes(ncstats* stats, int bytes);
423 void update_write_stats(const struct timespec* time1, const struct timespec* time0, ncstats* stats, int bytes);
424 
425 void sigwinch_handler(int signo);
426 
427 void init_lang(void);
428 
429 int reset_term_attributes(const tinfo* ti, fbuf* f);
430 int reset_term_palette(const tinfo* ti, fbuf* f, unsigned touchedpalette);
431 
432 // if there were missing elements we wanted from terminfo, bitch about them here
433 void warn_terminfo(const notcurses* nc, const tinfo* ti);
434 
435 int resize_callbacks_children(ncplane* n);
436 
437 static inline ncpile*
ncplane_pile(const ncplane * n)438 ncplane_pile(const ncplane* n){
439   return n->pile;
440 }
441 
442 static inline const ncpile*
ncplane_pile_const(const ncplane * n)443 ncplane_pile_const(const ncplane* n){
444   return n->pile;
445 }
446 
447 static inline ncplane*
ncplane_stdplane(ncplane * n)448 ncplane_stdplane(ncplane* n){
449   return notcurses_stdplane(ncplane_notcurses(n));
450 }
451 
452 static inline const ncplane*
ncplane_stdplane_const(const ncplane * n)453 ncplane_stdplane_const(const ncplane* n){
454   return notcurses_stdplane_const(ncplane_notcurses_const(n));
455 }
456 
457 // set the plane's widget and wdestruct fields, returning non-zero if they're
458 // already non-NULL (i.e. if the plane is already bound), unless we pass NULL
459 // (which ought be done from the widget destructor, to avoid corecursion).
460 static inline int
ncplane_set_widget(ncplane * n,void * w,void (* wdestruct)(void *))461 ncplane_set_widget(ncplane* n, void* w, void(*wdestruct)(void*)){
462   if(n->widget){
463     if(w){
464       logerror("plane is already bound to a widget\n");
465       return -1;
466     }
467   }else if(w == NULL){
468     return -1;
469   }
470   n->widget = w;
471   n->wdestruct = wdestruct;
472   return 0;
473 }
474 
475 // initialize visualization backend (ffmpeg/oiio)
476 int ncvisual_init(int loglevel);
477 
478 static inline int
fbcellidx(int row,int rowlen,int col)479 fbcellidx(int row, int rowlen, int col){
480 //fprintf(stderr, "row: %d rowlen: %d col: %d\n", row, rowlen, col);
481   return row * rowlen + col;
482 }
483 
484 // take a logical 'y' and convert it to the virtual 'y'. see HACKING.md.
485 static inline int
logical_to_virtual(const ncplane * n,int y)486 logical_to_virtual(const ncplane* n, int y){
487 //fprintf(stderr, "y: %d n->logrow: %d n->leny: %d\n", y, n->logrow, n->leny);
488   return (y + n->logrow) % n->leny;
489 }
490 
491 int clear_and_home(notcurses* nc, tinfo* ti, fbuf* f);
492 
493 static inline int
nfbcellidx(const ncplane * n,int row,int col)494 nfbcellidx(const ncplane* n, int row, int col){
495   return fbcellidx(logical_to_virtual(n, row), n->lenx, col);
496 }
497 
498 // is the rgb value greyish? note that pure white and pure black are both
499 // considered greyish according to the definition of this function =].
500 static inline bool
rgb_greyish_p(unsigned r,unsigned g,unsigned b)501 rgb_greyish_p(unsigned r, unsigned g, unsigned b){
502   const unsigned GREYMASK = 0xf8;
503   if((r & GREYMASK) == (g & GREYMASK) && (g & GREYMASK) == (b & GREYMASK)){
504     return true;
505   }
506   return false;
507 }
508 
509 // For our first attempt, O(1) uniform conversion from 8-bit r/g/b down to
510 // ~2.4-bit 6x6x6 cube + greyscale (assumed on entry; I know no way to
511 // even semi-portably recover the palette) proceeds via: map each 8-bit to
512 // a 5-bit target grey. if all 3 components match, select that grey.
513 // otherwise, c / 42.7 to map to 6 values.
514 static inline int
rgb_quantize_256(unsigned r,unsigned g,unsigned b)515 rgb_quantize_256(unsigned r, unsigned g, unsigned b){
516   // if all 5 MSBs match, return grey from 24-member grey ramp or pure
517   // black/white from original 16 (0 and 15, respectively)
518   if(rgb_greyish_p(r, g, b)){
519     // 8 and 238 come from https://terminalguide.namepad.de/attr/fgcol256/,
520     // which suggests 10-nit intervals otherwise.
521     if(r < 8){
522       return 0;
523     }else if(r > 238){
524       return 15;
525     }
526     return 232 + (r - 8) / 10;
527   }
528   r /= 43;
529   g /= 43;
530   b /= 43;
531   return r * 36 + g * 6 + b + 16;
532 }
533 
534 // We get black, red, green, yellow, blue, magenta, cyan, and white. Ugh. Under
535 // an additive model: G + R -> Y, G + B -> C, R + B -> M, R + G + B -> W
536 static inline int
rgb_quantize_8(unsigned r,unsigned g,unsigned b)537 rgb_quantize_8(unsigned r, unsigned g, unsigned b){
538   static const int BLACK = 0;
539   static const int RED = 1;
540   static const int GREEN = 2;
541   static const int YELLOW = 3;
542   static const int BLUE = 4;
543   static const int MAGENTA = 5;
544   static const int CYAN = 6;
545   static const int WHITE = 7;
546   if(rgb_greyish_p(r, g, b)){
547     if(r < 64){
548       return BLACK;
549     }
550     return WHITE;
551   }
552   if(r < 128){ // we have no red
553     if(g < 128){ // we have no green
554       if(b < 128){
555         return BLACK;
556       }
557       return BLUE;
558     } // we have green
559     if(b < 128){
560       return GREEN;
561     }
562     return CYAN;
563   }else if(g < 128){ // we have red, but not green
564     if(b < 128){
565       return RED;
566     }
567     return MAGENTA;
568   }else if(b < 128){ // we have red and green
569     return YELLOW;
570   }
571   return WHITE;
572 }
573 
574 // Given r, g, and b values 0..255, do a weighted average per Rec. 601, and
575 // return the 8-bit greyscale value (this value will be the r, g, and b value
576 // for the new color).
577 static inline int
rgb_greyscale(int r,int g,int b)578 rgb_greyscale(int r, int g, int b){
579   if(r < 0 || r > 255){
580     return -1;
581   }
582   if(g < 0 || g > 255){
583     return -1;
584   }
585   if(b < 0 || b > 255){
586     return -1;
587   }
588   // Use Rec. 601 scaling plus linear approximation of gamma decompression
589   float fg = (0.299 * (r / 255.0) + 0.587 * (g / 255.0) + 0.114 * (b / 255.0));
590   return fg * 255;
591 }
592 
593 static inline const char*
pool_extended_gcluster(const egcpool * pool,const nccell * c)594 pool_extended_gcluster(const egcpool* pool, const nccell* c){
595   if(cell_simple_p(c)){
596     return (const char*)&c->gcluster;
597   }
598   return egcpool_extended_gcluster(pool, c);
599 }
600 
601 static inline nccell*
ncplane_cell_ref_yx(const ncplane * n,unsigned y,unsigned x)602 ncplane_cell_ref_yx(const ncplane* n, unsigned y, unsigned x){
603   return &n->fb[nfbcellidx(n, y, x)];
604 }
605 
606 static inline void
cell_debug(const egcpool * p,const nccell * c)607 cell_debug(const egcpool* p, const nccell* c){
608   fprintf(stderr, "gcluster: %08x %s style: 0x%04x chan: 0x%016" PRIx64 "\n",
609           c->gcluster, egcpool_extended_gcluster(p, c), c->stylemask,
610           c->channels);
611 }
612 
613 static inline void
plane_debug(const ncplane * n,bool details)614 plane_debug(const ncplane* n, bool details){
615   unsigned dimy, dimx;
616   ncplane_dim_yx(n, &dimy, &dimx);
617   fprintf(stderr, "p: %p dim: %d/%d poolsize: %d\n", n, dimy, dimx, n->pool.poolsize);
618   if(details){
619     for(unsigned y = 0 ; y < 1 ; ++y){
620       for(unsigned x = 0 ; x < 10 ; ++x){
621         const nccell* c = &n->fb[fbcellidx(y, dimx, x)];
622         fprintf(stderr, "[%03d/%03d] ", y, x);
623         cell_debug(&n->pool, c);
624       }
625     }
626   }
627 }
628 
629 static inline notcurses*
ncpile_notcurses(ncpile * p)630 ncpile_notcurses(ncpile* p){
631   return p->nc;
632 }
633 
634 static inline const notcurses*
ncpile_notcurses_const(const ncpile * p)635 ncpile_notcurses_const(const ncpile* p){
636   return p->nc;
637 }
638 
639 static inline void
ncpile_debug(const ncpile * p,fbuf * f)640 ncpile_debug(const ncpile* p, fbuf* f){
641   fbuf_printf(f, "  ************************* %16p pile ****************************\n", p);
642   const ncplane* n = p->top;
643   const ncplane* prev = NULL;
644   int planeidx = 0;
645   while(n){
646     fbuf_printf(f, "%04d off y: %3d x: %3d geom y: %3u x: %3u curs y: %3u x: %3u %p %.4s\n",
647                 planeidx, n->absy, n->absx, n->leny, n->lenx, n->y, n->x, n, n->name);
648     if(n->boundto || n->bnext || n->bprev || n->blist){
649       fbuf_printf(f, " bound %p ← %p → %p binds %p\n",
650                   n->boundto, n->bprev, n->bnext, n->blist);
651     }
652     if(n->bprev && (*n->bprev != n)){
653       fbuf_printf(f, " WARNING: expected *->bprev %p, got %p\n", n, *n->bprev);
654     }
655     if(n->above != prev){
656       fbuf_printf(f, " WARNING: expected ->above %p, got %p\n", prev, n->above);
657     }
658     if(ncplane_pile_const(n) != p){
659       fbuf_printf(f, " WARNING: expected pile %p, got %p\n", p, ncplane_pile_const(n));
660     }
661     prev = n;
662     n = n->below;
663     ++planeidx;
664   }
665   if(p->bottom != prev){
666     fbuf_printf(f, " WARNING: expected ->bottom %p, got %p\n", prev, p->bottom);
667   }
668 }
669 
670 static inline void
notcurses_debug_fbuf(const notcurses * nc,fbuf * f)671 notcurses_debug_fbuf(const notcurses* nc, fbuf* f){
672   const ncpile* p = ncplane_pile(nc->stdplane);
673   fbuf_printf(f, " -------------------------- notcurses debug state -----------------------------\n");
674   const ncpile* p0 = p;
675   do{
676     ncpile_debug(p0, f);
677     const ncpile* prev = p0;
678     p0 = p0->next;
679     if(p0->prev != prev){
680       fbuf_printf(f, "WARNING: expected ->prev %p, got %p\n", prev, p0->prev);
681     }
682   }while(p != p0);
683   fbuf_printf(f, " ______________________________________________________________________________\n");
684 }
685 
686 // cell coordinates *within the sprixel*, not absolute
687 int sprite_wipe(const notcurses* nc, sprixel* s, int y, int x);
688 void sprixel_free(sprixel* s);
689 void sprixel_hide(sprixel* s);
690 
691 // dimy and dimx are cell geometry, not pixel.
692 sprixel* sprixel_alloc(ncplane* n, int dimy, int dimx);
693 sprixel* sprixel_recycle(ncplane* n);
694 int sprite_init(const tinfo* t, int fd);
695 int sprite_clear_all(const tinfo* t, fbuf* f);
696 // these three all use absolute coordinates
697 void sprixel_invalidate(sprixel* s, int y, int x);
698 void sprixel_movefrom(sprixel* s, int y, int x);
699 void sprixel_debug(const sprixel* s, FILE* out);
700 void sixelmap_free(struct sixelmap *s);
701 
702 // update any necessary cells underneath the sprixel pursuant to its removal.
703 // for sixel, this *achieves* the removal, and is performed on every cell.
704 // returns 1 if the graphic can be immediately freed (which is equivalent to
705 // asking whether it was sixel and there were no errors).
706 static inline int
sprite_scrub(const notcurses * n,const ncpile * p,sprixel * s)707 sprite_scrub(const notcurses* n, const ncpile* p, sprixel* s){
708 //sprixel_debug(s, stderr);
709   logdebug("sprixel %u state %d\n", s->id, s->invalidated);
710   return n->tcache.pixel_scrub(p, s);
711 }
712 
713 // precondition: s->invalidated is SPRIXEL_INVALIDATED or SPRIXEL_MOVED.
714 // returns -1 on error, or the number of bytes written.
715 static inline int
sprite_draw(const tinfo * ti,const ncpile * p,sprixel * s,fbuf * f,int yoff,int xoff)716 sprite_draw(const tinfo* ti, const ncpile* p, sprixel* s, fbuf* f,
717             int yoff, int xoff){
718   if(!ti->pixel_draw){
719     return 0;
720   }
721 //sprixel_debug(s, stderr);
722   logdebug("sprixel %u state %d\n", s->id, s->invalidated);
723   return ti->pixel_draw(ti, p, s, f, yoff, xoff);
724 }
725 
726 // precondition: s->invalidated is SPRIXEL_MOVED or SPRIXEL_INVALIDATED
727 // returns -1 on error, or the number of bytes written.
728 static inline int
sprite_redraw(notcurses * nc,const ncpile * p,sprixel * s,fbuf * f,int y,int x)729 sprite_redraw(notcurses* nc, const ncpile* p, sprixel* s, fbuf* f, int y, int x){
730 //sprixel_debug(s, stderr);
731   const tinfo* ti = &nc->tcache;
732   logdebug("sprixel %u state %d\n", s->id, s->invalidated);
733   if(s->invalidated == SPRIXEL_MOVED && ti->pixel_move){
734     // if we are kitty prior to 0.20.0, C=1 isn't available to us, and we must
735     // not emit it. we use sixel_maxy_pristine as a side channel to encode
736     // this version information.
737     bool noscroll = !ti->sixel_maxy_pristine;
738     return ti->pixel_move(s, f, noscroll, y, x);
739   }else{
740     if(!ti->pixel_draw){
741       return 0;
742     }
743     int r = ti->pixel_draw(ti, p, s, f, y, x);
744     // different terminals put the cursor at different places following
745     // emission of a bitmap graphic. just reset y/x.
746     nc->rstate.y = -1;
747     nc->rstate.x = -1;
748     return r;
749   }
750 }
751 
752 // present a loaded graphic. only defined for kitty.
753 static inline int
sprite_commit(tinfo * ti,fbuf * f,sprixel * s,unsigned forcescroll)754 sprite_commit(tinfo* ti, fbuf* f, sprixel* s, unsigned forcescroll){
755   if(ti->pixel_commit){
756     // if we are kitty prior to 0.20.0, C=1 isn't available to us, and we must
757     // not emit it. we use sixel_maxy_pristine as a side channel to encode
758     // this version information. direct mode, meanwhile, sets forcescroll.
759     bool noscroll = !ti->sixel_maxy_pristine && !forcescroll;
760     if(ti->pixel_commit(f, s, noscroll) < 0){
761       return -1;
762     }
763   }
764   return 0;
765 }
766 
767 static inline void
cleanup_tam(tament * tam,int ydim,int xdim)768 cleanup_tam(tament* tam, int ydim, int xdim){
769   for(int y = 0 ; y < ydim ; ++y){
770     for(int x = 0 ; x < xdim ; ++x){
771       free(tam[y * xdim + x].auxvector);
772       tam[y * xdim + x].auxvector = NULL;
773     }
774   }
775 }
776 
777 static inline void
destroy_tam(ncplane * p)778 destroy_tam(ncplane* p){
779   if(p->tam){
780     cleanup_tam(p->tam, p->leny, p->lenx);
781     free(p->tam);
782     p->tam = NULL;
783   }
784 }
785 
786 static inline int
sprite_rebuild(const notcurses * nc,sprixel * s,int ycell,int xcell)787 sprite_rebuild(const notcurses* nc, sprixel* s, int ycell, int xcell){
788   logdebug("rebuilding %d %d/%d\n", s->id, ycell, xcell);
789   const int idx = s->dimx * ycell + xcell;
790   int ret = 0;
791   // special case the transition back to SPRIXCELL_TRANSPARENT; this can be
792   // done in O(1), since the actual glyph needn't change.
793   if(s->n->tam[idx].state == SPRIXCELL_ANNIHILATED_TRANS){
794     s->n->tam[idx].state = SPRIXCELL_TRANSPARENT;
795   }else if(s->n->tam[idx].state == SPRIXCELL_ANNIHILATED){
796     uint8_t* auxvec = (uint8_t*)s->n->tam[idx].auxvector;
797     assert(auxvec);
798     // sets the new state itself
799     ret = nc->tcache.pixel_rebuild(s, ycell, xcell, auxvec);
800     if(ret > 0){
801       free(auxvec);
802       s->n->tam[idx].auxvector = NULL;
803     }
804   }else{
805     return 0;
806   }
807   // don't upset SPRIXEL_MOVED
808   if(s->invalidated == SPRIXEL_QUIESCENT){
809     if(s->n->tam[idx].state != SPRIXCELL_TRANSPARENT &&
810        s->n->tam[idx].state != SPRIXCELL_ANNIHILATED &&
811        s->n->tam[idx].state != SPRIXCELL_ANNIHILATED_TRANS){
812       s->invalidated = SPRIXEL_INVALIDATED;
813     }
814   }
815   return ret;
816 }
817 
818 // |y| and |x| are scaled geometry on input, and clamped scaled geometry on
819 // output. |outy| is output geometry on output, and unused on input. output
820 // geometry is derived from scaled geometry and output requirements (that Sixel
821 // must be a multiple of six pixels tall). output width is always equal to
822 // scaled width. all are pixels.
823 // happy fact: common reported values for maximum sixel height are 256, 1024,
824 // and 4096...not a single goddamn one of which is divisible by six. augh.
825 static inline void
clamp_to_sixelmax(const tinfo * t,unsigned * y,unsigned * x,unsigned * outy,ncscale_e scaling)826 clamp_to_sixelmax(const tinfo* t, unsigned* y, unsigned* x, unsigned* outy, ncscale_e scaling){
827   if(t->sixel_maxy && *y > t->sixel_maxy){
828     *y = t->sixel_maxy;
829   }
830   *outy = *y;
831   if(*outy % t->sprixel_scale_height){
832     *outy += t->sprixel_scale_height - (*outy % t->sprixel_scale_height);
833     // FIXME use closed form
834     while(t->sixel_maxy && *outy > t->sixel_maxy){
835       *outy -= t->sprixel_scale_height;
836     }
837     if(scaling == NCSCALE_STRETCH || *y > *outy){
838       *y = *outy;
839     }
840   }
841   if(t->sixel_maxx && *x > t->sixel_maxx){
842     *x = t->sixel_maxx;
843   }
844 }
845 
846 // any sprixcell which does not cover the entirety of the underlying cell
847 // cannot be SPRIXCELL_OPAQUE. this postprocesses the TAM, flipping any
848 // such sprixcells to SPRIXCELL_MIXED. |leny| and |lenx| are output geometry
849 // in pixels. |cdimy| and |cdimx| are output coverage in cells.
850 static inline void
scrub_tam_boundaries(tament * tam,int leny,int lenx,int cdimy,int cdimx)851 scrub_tam_boundaries(tament* tam, int leny, int lenx, int cdimy, int cdimx){
852   // any sprixcells which don't cover the full cell underneath them cannot
853   // be SPRIXCELL_OPAQUE
854   const int cols = (lenx + cdimx - 1) / cdimx;
855   if(lenx % cdimx){
856     for(int y = 0 ; y < (leny + cdimy - 1) / cdimy ; ++y){
857       if(tam[y * cols + cols - 1].state == SPRIXCELL_OPAQUE_KITTY){
858         tam[y * cols + cols - 1].state = SPRIXCELL_MIXED_KITTY;
859       }else if(tam[y * cols + cols - 1].state == SPRIXCELL_OPAQUE_SIXEL){
860         tam[y * cols + cols - 1].state = SPRIXCELL_MIXED_SIXEL;
861       }
862     }
863   }
864   if(leny % cdimy){
865     const int y = (leny + cdimy - 1) / cdimy - 1;
866     for(int x = 0 ; x < cols ; ++x){
867       if(tam[y * cols + x].state == SPRIXCELL_OPAQUE_KITTY){
868         tam[y * cols + x].state = SPRIXCELL_MIXED_KITTY;
869       }else if(tam[y * cols + x].state == SPRIXCELL_OPAQUE_SIXEL){
870         tam[y * cols + x].state = SPRIXCELL_MIXED_SIXEL;
871       }
872     }
873   }
874 }
875 
876 // get the TAM entry for these (absolute) coordinates
877 static inline sprixcell_e
sprixel_state(const sprixel * s,int y,int x)878 sprixel_state(const sprixel* s, int y, int x){
879   const ncplane* stdn = notcurses_stdplane_const(ncplane_notcurses_const(s->n));
880   int localy = y - (s->n->absy - stdn->absy);
881   int localx = x - (s->n->absx - stdn->absx);
882 //fprintf(stderr, "TAM %d at %d/%d (%d/%d, %d/%d)\n", s->n->tam[localy * s->dimx + localx].state, localy, localx, y, x, s->dimy, s->dimx);
883   assert(localy >= 0);
884   assert(localy < (int)s->dimy);
885   assert(localx >= 0);
886   assert(localx < (int)s->dimx);
887   return s->n->tam[localy * s->dimx + localx].state;
888 }
889 
890 static inline void
pool_release(egcpool * pool,nccell * c)891 pool_release(egcpool* pool, nccell* c){
892   if(cell_extended_p(c)){
893     egcpool_release(pool, cell_egc_idx(c));
894   }
895   c->gcluster = 0; // don't subject ourselves to double-release problems
896   c->width = 0;    // don't subject ourselves to geometric ambiguities
897 }
898 
899 // set the nccell 'c' to point into the egcpool at location 'eoffset'
900 static inline void
set_gcluster_egc(nccell * c,int eoffset)901 set_gcluster_egc(nccell* c, int eoffset){
902   c->gcluster = htole(0x01000000ul) + htole(eoffset);
903 }
904 
905 // Duplicate one nccell onto another, possibly crossing ncplanes.
906 static inline int
cell_duplicate_far(egcpool * tpool,nccell * targ,const ncplane * splane,const nccell * c)907 cell_duplicate_far(egcpool* tpool, nccell* targ, const ncplane* splane, const nccell* c){
908   pool_release(tpool, targ);
909   targ->stylemask = c->stylemask;
910   targ->channels = c->channels;
911   targ->width = c->width;
912   if(!cell_extended_p(c)){
913     targ->gcluster = c->gcluster;
914     return 0;
915   }
916   const char* egc = nccell_extended_gcluster(splane, c);
917   size_t ulen = strlen(egc);
918   int eoffset = egcpool_stash(tpool, egc, ulen);
919   if(eoffset < 0){
920     return -1;
921   }
922   set_gcluster_egc(targ, eoffset);
923   return 0;
924 }
925 
926 int ncplane_resize_internal(ncplane* n, int keepy, int keepx,
927                             unsigned keepleny, unsigned keeplenx,
928                             int yoff, int xoff,
929                             unsigned ylen, unsigned xlen);
930 
931 int update_term_dimensions(unsigned* rows, unsigned* cols, tinfo* tcache, int margin_b,
932                            unsigned* cgeo_changed, unsigned* pgeo_changed)
933   __attribute__ ((nonnull (3, 5, 6)));
934 
935 ALLOC static inline void*
memdup(const void * src,size_t len)936 memdup(const void* src, size_t len){
937   void* ret = malloc(len);
938   if(ret){
939     memcpy(ret, src, len);
940   }
941   return ret;
942 }
943 
944 ALLOC void* bgra_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha);
945 ALLOC void* rgb_loose_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha);
946 ALLOC void* rgb_packed_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha);
947 
948 // find the "center" cell of two lengths. in the case of even rows/columns, we
949 // place the center on the top/left. in such a case there will be one more
950 // cell to the bottom/right of the center.
951 static inline void
center_box(int * RESTRICT y,int * RESTRICT x)952 center_box(int* RESTRICT y, int* RESTRICT x){
953   if(y){
954     *y = (*y - 1) / 2;
955   }
956   if(x){
957     *x = (*x - 1) / 2;
958   }
959 }
960 
961 // find the "center" cell of a plane. in the case of even rows/columns, we
962 // place the center on the top/left. in such a case there will be one more
963 // cell to the bottom/right of the center.
964 static inline void
ncplane_center(const ncplane * n,int * RESTRICT y,int * RESTRICT x)965 ncplane_center(const ncplane* n, int* RESTRICT y, int* RESTRICT x){
966   *y = n->leny;
967   *x = n->lenx;
968   center_box(y, x);
969 }
970 
971 int ncvisual_bounding_box(const struct ncvisual* ncv, int* leny, int* lenx,
972                           int* offy, int* offx);
973 
974 // Our gradient is a 2d lerp among the four corners of the region. We start
975 // with the observation that each corner ought be its exact specified corner,
976 // and the middle ought be the exact average of all four corners' components.
977 // Another observation is that if all four corners are the same, every cell
978 // ought be the exact same color. From this arises the observation that a
979 // perimeter element is not affected by the other three sides:
980 //
981 //  a corner element is defined by itself
982 //  a perimeter element is defined by the two points on its side
983 //  an internal element is defined by all four points
984 //
985 // 2D equation of state: solve for each quadrant's contribution (min 2x2):
986 //
987 //  X' = (xlen - 1) - X
988 //  Y' = (ylen - 1) - Y
989 //  TLC: X' * Y' * TL
990 //  TRC: X * Y' * TR
991 //  BLC: X' * Y * BL
992 //  BRC: X * Y * BR
993 //  steps: (xlen - 1) * (ylen - 1) [maximum steps away from origin]
994 //
995 // Then add TLC + TRC + BLC + BRC + steps / 2, and divide by steps (the
996 //  steps / 2 is to work around truncate-towards-zero).
997 static int
calc_gradient_component(unsigned tl,unsigned tr,unsigned bl,unsigned br,unsigned y,unsigned x,unsigned ylen,unsigned xlen)998 calc_gradient_component(unsigned tl, unsigned tr, unsigned bl, unsigned br,
999                         unsigned y, unsigned x, unsigned ylen, unsigned xlen){
1000   const int avm = (ylen - 1) - y;
1001   const int ahm = (xlen - 1) - x;
1002   if(xlen < 2){
1003     if(ylen < 2){
1004       return tl;
1005     }
1006     return (tl * avm + bl * y) / (ylen - 1);
1007   }
1008   if(ylen < 2){
1009     return (tl * ahm + tr * x) / (xlen - 1);
1010   }
1011   const int tlc = ahm * avm * tl;
1012   const int blc = ahm * y * bl;
1013   const int trc = x * avm * tr;
1014   const int brc = y * x * br;
1015   const int divisor = (ylen - 1) * (xlen - 1);
1016   return ((tlc + blc + trc + brc) + divisor / 2) / divisor;
1017 }
1018 
1019 // calculate one of the channels of a gradient at a particular point.
1020 static inline uint32_t
calc_gradient_channel(uint32_t ul,uint32_t ur,uint32_t ll,uint32_t lr,unsigned y,unsigned x,unsigned ylen,unsigned xlen)1021 calc_gradient_channel(uint32_t ul, uint32_t ur, uint32_t ll, uint32_t lr,
1022                       unsigned y, unsigned x, unsigned ylen, unsigned xlen){
1023   uint32_t chan = 0;
1024   ncchannel_set_rgb8_clipped(&chan,
1025                            calc_gradient_component(ncchannel_r(ul), ncchannel_r(ur),
1026                                                    ncchannel_r(ll), ncchannel_r(lr),
1027                                                    y, x, ylen, xlen),
1028                            calc_gradient_component(ncchannel_g(ul), ncchannel_g(ur),
1029                                                    ncchannel_g(ll), ncchannel_g(lr),
1030                                                    y, x, ylen, xlen),
1031                            calc_gradient_component(ncchannel_b(ul), ncchannel_b(ur),
1032                                                    ncchannel_b(ll), ncchannel_b(lr),
1033                                                    y, x, ylen, xlen));
1034   ncchannel_set_alpha(&chan, ncchannel_alpha(ul)); // precondition: all αs are equal
1035   return chan;
1036 }
1037 
1038 // calculate both channels of a gradient at a particular point, storing them
1039 // into `channels'. x and y ought be the location within the gradient.
1040 static inline void
calc_gradient_channels(uint64_t * channels,uint64_t ul,uint64_t ur,uint64_t ll,uint64_t lr,unsigned y,unsigned x,unsigned ylen,unsigned xlen)1041 calc_gradient_channels(uint64_t* channels, uint64_t ul, uint64_t ur,
1042                        uint64_t ll, uint64_t lr, unsigned y, unsigned x,
1043                        unsigned ylen, unsigned xlen){
1044   if(!ncchannels_fg_default_p(ul)){
1045     ncchannels_set_fchannel(channels,
1046                             calc_gradient_channel(ncchannels_fchannel(ul),
1047                                                   ncchannels_fchannel(ur),
1048                                                   ncchannels_fchannel(ll),
1049                                                   ncchannels_fchannel(lr),
1050                                                   y, x, ylen, xlen));
1051   }else{
1052     ncchannels_set_fg_default(channels);
1053   }
1054   if(!ncchannels_bg_default_p(ul)){
1055     ncchannels_set_bchannel(channels,
1056                             calc_gradient_channel(ncchannels_bchannel(ul),
1057                                                   ncchannels_bchannel(ur),
1058                                                   ncchannels_bchannel(ll),
1059                                                   ncchannels_bchannel(lr),
1060                                                   y, x, ylen, xlen));
1061   }else{
1062     ncchannels_set_bg_default(channels);
1063   }
1064 }
1065 
1066 // ncdirect needs to "fake" an isolated ncplane as a drawing surface for
1067 // ncvisual_blit(), and thus calls these low-level internal functions.
1068 // they are not for general use -- check ncplane_new() and ncplane_destroy().
1069 ncplane* ncplane_new_internal(notcurses* nc, ncplane* n, const ncplane_options* nopts);
1070 
1071 void free_plane(ncplane* p);
1072 
1073 // heap-allocated formatted output
1074 ALLOC char* ncplane_vprintf_prep(const char* format, va_list ap);
1075 
1076 // Resize the provided ncvisual to the specified 'rows' x 'cols', but do not
1077 // change the internals of the ncvisual. Uses oframe.
1078 int ncvisual_blit_internal(struct ncvisual* ncv, int rows, int cols,
1079                            ncplane* n, const struct blitset* bset,
1080                            const blitterargs* bargs);
1081 
1082 static inline int
tty_emit(const char * seq,int fd)1083 tty_emit(const char* seq, int fd){
1084   if(!seq){
1085     return -1;
1086   }
1087   size_t slen = strlen(seq);
1088   if(blocking_write(fd, seq, slen)){
1089     return -1;
1090   }
1091   return 0;
1092 }
1093 
1094 int set_fd_nonblocking(int fd, unsigned state, unsigned* oldstate);
1095 
1096 static inline int
term_bg_palindex(const notcurses * nc,fbuf * f,unsigned pal)1097 term_bg_palindex(const notcurses* nc, fbuf* f, unsigned pal){
1098   const char* setab = get_escape(&nc->tcache, ESCAPE_SETAB);
1099   if(setab){
1100     return fbuf_emit(f, tiparm(setab, pal));
1101   }
1102   return 0;
1103 }
1104 
1105 static inline int
term_fg_palindex(const notcurses * nc,fbuf * f,unsigned pal)1106 term_fg_palindex(const notcurses* nc, fbuf* f, unsigned pal){
1107   const char* setaf = get_escape(&nc->tcache, ESCAPE_SETAF);
1108   if(setaf){
1109     return fbuf_emit(f, tiparm(setaf, pal));
1110   }
1111   return 0;
1112 }
1113 
1114 // check the current and target style bitmasks against the specified 'stylebit'.
1115 // if they are different, and we have the necessary capability, write the
1116 // applicable terminfo entry to 'out'. returns -1 only on a true error.
1117 static int
term_setstyle(fbuf * f,unsigned cur,unsigned targ,unsigned stylebit,const char * ton,const char * toff)1118 term_setstyle(fbuf* f, unsigned cur, unsigned targ, unsigned stylebit,
1119               const char* ton, const char* toff){
1120   int ret = 0;
1121   unsigned curon = cur & stylebit;
1122   unsigned targon = targ & stylebit;
1123   if(curon != targon){
1124     if(targon){
1125       if(ton){
1126         ret = fbuf_emit(f, ton);
1127       }
1128     }else{
1129       if(toff){ // how did this happen? we can turn it on, but not off?
1130         ret = fbuf_emit(f, toff);
1131       }
1132     }
1133   }
1134   if(ret < 0){
1135     return -1;
1136   }
1137   return 0;
1138 }
1139 
1140 // emit escapes such that the current style is equal to newstyle. if this
1141 // required an sgr0 (which resets colors), normalized will be non-zero upon
1142 // a successful return.
1143 static inline int
coerce_styles(fbuf * f,const tinfo * ti,uint16_t * curstyle,uint16_t newstyle,unsigned * normalized)1144 coerce_styles(fbuf* f, const tinfo* ti, uint16_t* curstyle,
1145               uint16_t newstyle, unsigned* normalized){
1146   *normalized = 0; // we never currently use sgr0
1147   int ret = 0;
1148   ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_BOLD,
1149                        get_escape(ti, ESCAPE_BOLD), get_escape(ti, ESCAPE_NOBOLD));
1150   ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_ITALIC,
1151                        get_escape(ti, ESCAPE_SITM), get_escape(ti, ESCAPE_RITM));
1152   ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_STRUCK,
1153                        get_escape(ti, ESCAPE_SMXX), get_escape(ti, ESCAPE_RMXX));
1154   // underline and undercurl are exclusive. if we set one, don't go unsetting
1155   // the other.
1156   if(newstyle & NCSTYLE_UNDERLINE){ // turn on underline, or do nothing
1157     ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_UNDERLINE,
1158                          get_escape(ti, ESCAPE_SMUL), get_escape(ti, ESCAPE_RMUL));
1159   }else if(newstyle & NCSTYLE_UNDERCURL){ // turn on undercurl, or do nothing
1160     ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_UNDERCURL,
1161                          get_escape(ti, ESCAPE_SMULX), get_escape(ti, ESCAPE_SMULNOX));
1162   }else{ // turn off any underlining
1163     ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_UNDERCURL | NCSTYLE_UNDERLINE,
1164                          NULL, get_escape(ti, ESCAPE_RMUL));
1165   }
1166   *curstyle = newstyle;
1167   return ret;
1168 }
1169 
1170 // DEC private mode set (DECSET) parameters (and corresponding XTerm resources)
1171 #define SET_X10_MOUSE_PROT       "9" // outdated, do not use, use x11
1172 // we can combine 1000--1004, and then use 1005/1006/1015 for extended coordinates
1173 #define SET_X11_MOUSE_PROT    "1000" // for button events
1174 #define SET_HILITE_MOUSE_PROT "1001" // for highlight tracking
1175 #define SET_BTN_EVENT_MOUSE   "1002" // for motion events with buttons
1176 #define SET_ALL_EVENT_MOUSE   "1003" // for motion events without buttons
1177 #define SET_FOCUS_EVENT_MOUSE "1004" // for focus events
1178 #define SET_UTF8_MOUSE_PROT   "1005" // utf8-style extended coordinates
1179 #define SET_SGR_MOUSE_PROT    "1006" // sgr-style extended coordinates
1180 #define SET_ALTERNATE_SCROLL  "1007" // scroll in alternate screen (alternateScroll)
1181 #define SET_TTYOUTPUT_SCROLL  "1010" // scroll on tty output (scrollTtyOutput)
1182 #define SET_KEYPRESS_SCROLL   "1011" // scroll on keypress (scrollKey)
1183 #define SET_URXVT_MOUSE_PROT  "1015" // urxvt-style extended coordinates
1184 #define SET_PIXEL_MOUSE_PROT  "1016" // sgr-style, using pixles rather than cells
1185 #define SET_ENABLE_ALTSCREEN  "1046" // enable alternate screen (*sets* titeInhibit)
1186 #define SET_ALTERNATE_SCREEN  "1047" // replaces 47 (conflict w/DECGRPM) (titeInhibit)
1187 #define SET_SAVE_CURSOR       "1048" // save cursor ala DECSC (titeInhibit)
1188 #define SET_SMCUP             "1049" // 1047+1048 (titeInhibit)
1189 // DECSET/DECRSTs can be chained with semicolons; can we generalize this? FIXME
1190 #define DECSET(p) "\x1b[?" p "h"
1191 #define DECRST(p) "\x1b[?" p "l"
1192 
1193 int mouse_setup(tinfo* ti, unsigned eventmask);
1194 
1195 // sync the drawing position to the specified location with as little overhead
1196 // as possible (with nothing, if already at the right location). we prefer
1197 // absolute horizontal moves (hpa) to relative ones, in the rare event that
1198 // our understanding of our horizontal location is faulty. if we're moving from
1199 // one plane to another, we emit an hpa no matter what.
1200 // FIXME fall back to synthesized moves in the absence of capabilities (i.e.
1201 // textronix lacks cup; fake it with horiz+vert moves)
1202 // if hardcursorpos is non-zero, we always perform a cup. this is done when we
1203 // don't know where the cursor currently is =].
1204 static inline int
goto_location(notcurses * nc,fbuf * f,int y,int x,const ncplane * srcp)1205 goto_location(notcurses* nc, fbuf* f, int y, int x, const ncplane* srcp){
1206 //fprintf(stderr, "going to %d/%d from %d/%d hard: %u\n", y, x, nc->rstate.y, nc->rstate.x, nc->rstate.hardcursorpos);
1207   int ret = 0;
1208   // if we don't have hpa, force a cup even if we're only 1 char away. the only
1209   // TERM i know supporting cup sans hpa is vt100, and vt100 can suck it.
1210   // you can't use cuf for backwards moves anyway; again, vt100 can suck it.
1211   const char* hpa = get_escape(&nc->tcache, ESCAPE_HPA);
1212   if(nc->rstate.y == y && hpa){ // only need move x
1213     if(nc->rstate.x == x){
1214       if(nc->rstate.lastsrcp == srcp || !nc->tcache.gratuitous_hpa){
1215         return 0; // needn't move shit
1216       }
1217       ++nc->stats.s.hpa_gratuitous;
1218     }
1219     if(fbuf_emit(f, tiparm(hpa, x))){
1220       return -1;
1221     }
1222   }else{
1223     // cup is required, no need to verify existence
1224     const char* cup = get_escape(&nc->tcache, ESCAPE_CUP);
1225     if(fbuf_emit(f, tiparm(cup, y, x))){
1226       return -1;
1227     }
1228   }
1229   nc->rstate.x = x;
1230   nc->rstate.y = y;
1231   nc->rstate.lastsrcp = srcp;
1232   return ret;
1233 }
1234 
1235 // how many edges need touch a corner for it to be printed?
1236 static inline unsigned
box_corner_needs(unsigned ctlword)1237 box_corner_needs(unsigned ctlword){
1238   return (ctlword & NCBOXCORNER_MASK) >> NCBOXCORNER_SHIFT;
1239 }
1240 
1241 // True if the cell does not generate background pixels (i.e., the cell is a
1242 // solid or shaded block, or certain emoji).
1243 static inline bool
nccell_nobackground_p(const nccell * c)1244 nccell_nobackground_p(const nccell* c){
1245   // needs to match all four bits of NC_NOBACKGROUND_MASK, not just one
1246   return (c->channels & NC_NOBACKGROUND_MASK) == NC_NOBACKGROUND_MASK;
1247 }
1248 
1249 // True iff the foreground and background color are both RGB, and equal.
1250 static inline bool
nccell_rgbequal_p(const nccell * c)1251 nccell_rgbequal_p(const nccell* c){
1252   if(nccell_fg_default_p(c) || nccell_fg_palindex_p(c)){
1253     return false;
1254   }
1255   if(nccell_bg_default_p(c) || nccell_bg_palindex_p(c)){
1256     return false;
1257   }
1258   return nccell_fg_rgb(c) == nccell_bg_rgb(c);
1259 }
1260 
1261 // Returns a number 0 <= n <= 15 representing the four quadrants, and which (if
1262 // any) are occupied due to blitting with a transparent background. The mapping
1263 // is {tl, tr, bl, br}.
1264 static inline unsigned
cell_blittedquadrants(const nccell * c)1265 cell_blittedquadrants(const nccell* c){
1266   return ((c->channels & 0x8000000000000000ull) ? 1 : 0) |
1267          ((c->channels & 0x0400000000000000ull) ? 2 : 0) |
1268          ((c->channels & 0x0200000000000000ull) ? 4 : 0) |
1269          ((c->channels & 0x0100000000000000ull) ? 8 : 0);
1270 }
1271 
1272 // Set this whenever blitting an ncvisual, when we have a transparent
1273 // background. In such cases, ncvisuals underneath the cell must be rendered
1274 // slightly differently.
1275 static inline void
cell_set_blitquadrants(nccell * c,unsigned tl,unsigned tr,unsigned bl,unsigned br)1276 cell_set_blitquadrants(nccell* c, unsigned tl, unsigned tr, unsigned bl, unsigned br){
1277   // FIXME want a static assert that these four constants OR together to
1278   // equal CELL_BLITTERSTACK_MASK, bah
1279   uint64_t newval = (tl ? 0x8000000000000000ull : 0) |
1280                     (tr ? 0x0400000000000000ull : 0) |
1281                     (bl ? 0x0200000000000000ull : 0) |
1282                     (br ? 0x0100000000000000ull : 0);
1283   c->channels = ((c->channels & ~NC_BLITTERSTACK_MASK) | newval);
1284 }
1285 
1286 // Destroy a plane and all its bound descendants.
1287 int ncplane_destroy_family(ncplane *ncp);
1288 
1289 // Extract the 32-bit background channel from a cell.
1290 static inline uint32_t
cell_bchannel(const nccell * cl)1291 cell_bchannel(const nccell* cl){
1292   return ncchannels_bchannel(cl->channels);
1293 }
1294 
1295 // Extract those elements of the channel which are common to both foreground
1296 // and background channel representations.
1297 static inline uint32_t
channel_common(uint32_t channel)1298 channel_common(uint32_t channel){
1299   return channel & (NC_BGDEFAULT_MASK | NC_BG_RGB_MASK |
1300                     NC_BG_PALETTE | NC_BG_ALPHA_MASK);
1301 }
1302 
1303 // Extract those elements of the background channel which may be freely swapped
1304 // with the foreground channel (alpha and coloring info).
1305 static inline uint32_t
cell_bchannel_common(const nccell * cl)1306 cell_bchannel_common(const nccell* cl){
1307   return channel_common(cell_bchannel(cl));
1308 }
1309 
1310 // Extract the 32-bit foreground channel from a cell.
1311 static inline uint32_t
cell_fchannel(const nccell * cl)1312 cell_fchannel(const nccell* cl){
1313   return ncchannels_fchannel(cl->channels);
1314 }
1315 
1316 // Extract those elements of the foreground channel which may be freely swapped
1317 // with the background channel (alpha and coloring info).
1318 static inline uint32_t
cell_fchannel_common(const nccell * cl)1319 cell_fchannel_common(const nccell* cl){
1320   return channel_common(cell_fchannel(cl));
1321 }
1322 
1323 // Set the 32-bit background channel of an nccell.
1324 static inline uint64_t
cell_set_bchannel(nccell * cl,uint32_t channel)1325 cell_set_bchannel(nccell* cl, uint32_t channel){
1326   return ncchannels_set_bchannel(&cl->channels, channel);
1327 }
1328 
1329 // Set the 32-bit foreground channel of an nccell.
1330 static inline uint64_t
cell_set_fchannel(nccell * cl,uint32_t channel)1331 cell_set_fchannel(nccell* cl, uint32_t channel){
1332   return ncchannels_set_fchannel(&cl->channels, channel);
1333 }
1334 
1335 // Returns the result of blending two channels. 'blends' indicates how heavily
1336 // 'c1' ought be weighed. If 'blends' is 0, 'c1' will be entirely replaced by
1337 // 'c2'. If 'c1' is otherwise the default color, 'c1' will not be touched,
1338 // since we can't blend default colors. Likewise, if 'c2' is a default color,
1339 // it will not be used (unless 'blends' is 0).
1340 static inline unsigned
channels_blend(notcurses * nc,unsigned c1,unsigned c2,unsigned * blends)1341 channels_blend(notcurses* nc, unsigned c1, unsigned c2, unsigned* blends){
1342   if(ncchannel_alpha(c2) == NCALPHA_TRANSPARENT){
1343     return c1; // do *not* increment *blends
1344   }
1345   bool c2default = ncchannel_default_p(c2);
1346   bool c2palette = ncchannel_palindex_p(c2);
1347   if(*blends == 0){
1348     // don't just return c2, or you set wide status and all kinds of crap
1349     if(c2default){
1350       ncchannel_set_default(&c1);
1351     }else if(c2palette){
1352       ncchannel_set_palindex(&c1, ncchannel_palindex(c2));
1353     }else{
1354       ncchannel_set(&c1, c2 & NC_BG_RGB_MASK);
1355     }
1356     ncchannel_set_alpha(&c1, ncchannel_alpha(c2));
1357   }else if(!c2default && !ncchannel_default_p(c1)){
1358     unsigned rsum, gsum, bsum;
1359     if(c2palette){
1360       uint32_t rgb = nc->palette.chans[ncchannel_palindex(c2)];
1361       bsum = rgb & 0xff;
1362       gsum = (rgb >> 8u) & 0xff;
1363       rsum = (rgb >> 16u) & 0xff;
1364     }else{
1365       ncchannel_rgb8(c2, &rsum, &gsum, &bsum);
1366     }
1367     rsum = (ncchannel_r(c1) * *blends + rsum) / (*blends + 1);
1368     gsum = (ncchannel_g(c1) * *blends + gsum) / (*blends + 1);
1369     bsum = (ncchannel_b(c1) * *blends + bsum) / (*blends + 1);
1370     ncchannel_set_rgb8(&c1, rsum, gsum, bsum);
1371     ncchannel_set_alpha(&c1, ncchannel_alpha(c2));
1372   }
1373   ++*blends;
1374   return c1;
1375 }
1376 
1377 // do not pass palette-indexed channels!
1378 static inline uint64_t
cell_blend_fchannel(notcurses * nc,nccell * cl,unsigned channel,unsigned * blends)1379 cell_blend_fchannel(notcurses* nc, nccell* cl, unsigned channel, unsigned* blends){
1380   return cell_set_fchannel(cl, channels_blend(nc, cell_fchannel(cl), channel, blends));
1381 }
1382 
1383 static inline uint64_t
cell_blend_bchannel(notcurses * nc,nccell * cl,unsigned channel,unsigned * blends)1384 cell_blend_bchannel(notcurses* nc, nccell* cl, unsigned channel, unsigned* blends){
1385   return cell_set_bchannel(cl, channels_blend(nc, cell_bchannel(cl), channel, blends));
1386 }
1387 
1388 // a sprixel occupies the entirety of its associated plane, usually an entirely
1389 // new, purpose-specific plane. |leny| and |lenx| are output geometry in pixels.
1390 static inline int
plane_blit_sixel(sprixel * spx,fbuf * f,int leny,int lenx,int parse_start,tament * tam,sprixel_e state)1391 plane_blit_sixel(sprixel* spx, fbuf* f, int leny, int lenx,
1392                  int parse_start, tament* tam, sprixel_e state){
1393   if(sprixel_load(spx, f, leny, lenx, parse_start, state)){
1394     return -1;
1395   }
1396   ncplane* n = spx->n;
1397   if(n){
1398 //fprintf(stderr, "TAM WAS: %p NOW: %p\n", n->tam, tam);
1399     if(n->tam != tam){
1400       destroy_tam(n);
1401     }
1402     n->tam = tam;
1403     n->sprite = spx;
1404   }
1405   return 0;
1406 }
1407 
1408 // is it a control character? check C0 and C1, but don't count empty strings,
1409 // nor single-byte strings containing only a NUL character.
1410 static inline bool
is_control_egc(const unsigned char * egc,int bytes)1411 is_control_egc(const unsigned char* egc, int bytes){
1412   if(bytes == 1){
1413     if(*egc && iscntrl(*egc)){
1414       return true;
1415     }
1416   }else if(bytes == 2){
1417     // 0xc2 followed by 0x80--0x9f are controls. 0xc2 followed by <0x80 is
1418     // simply invalid utf8.
1419     if(egc[0] == 0xc2){
1420       if(egc[1] < 0xa0){
1421         return true;
1422       }
1423     }
1424   }
1425   return false;
1426 }
1427 
1428 // lowest level of cell+pool setup. if the EGC changes the output to RTL, it
1429 // must be suffixed with a LTR-forcing character by now. The four bits of
1430 // NC_BLITTERSTACK_MASK ought already be initialized. If gcluster is four
1431 // bytes or fewer, this function cannot fail.
1432 static inline int
pool_blit_direct(egcpool * pool,nccell * c,const char * gcluster,int bytes,int cols)1433 pool_blit_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int cols){
1434   pool_release(pool, c);
1435   if(bytes < 0 || cols < 0){
1436     return -1;
1437   }
1438   // we allow newlines to be blitted into the pool, as they're picked up and
1439   // given special semantics by paint() and rasterization.
1440   if(*gcluster != '\n' && is_control_egc((const unsigned char*)gcluster, bytes)){
1441     logerror("not loading control character %u\n", *(const unsigned char*)gcluster);
1442     return -1;
1443   }
1444   c->width = cols;
1445   if(bytes <= 4){
1446     c->gcluster = 0;
1447     memcpy(&c->gcluster, gcluster, bytes);
1448   }else{
1449     int eoffset = egcpool_stash(pool, gcluster, bytes);
1450     if(eoffset < 0){
1451       return -1;
1452     }
1453     set_gcluster_egc(c, eoffset);
1454   }
1455   return bytes;
1456 }
1457 
1458 // Reset the quadrant occupancy bits, and pass the cell down to
1459 // pool_blit_direct(). Returns the number of bytes loaded.
1460 static inline int
pool_load_direct(egcpool * pool,nccell * c,const char * gcluster,int bytes,int cols)1461 pool_load_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int cols){
1462   c->channels &= ~NC_NOBACKGROUND_MASK;
1463   return pool_blit_direct(pool, c, gcluster, bytes, cols);
1464 }
1465 
1466 static inline int
cell_load_direct(ncplane * n,nccell * c,const char * gcluster,int bytes,int cols)1467 cell_load_direct(ncplane* n, nccell* c, const char* gcluster, int bytes, int cols){
1468   return pool_load_direct(&n->pool, c, gcluster, bytes, cols);
1469 }
1470 
1471 // increment y by 1 and rotate the framebuffer up one line. x moves to 0.
1472 void scroll_down(ncplane* n);
1473 
1474 static inline bool
islinebreak(wchar_t wchar)1475 islinebreak(wchar_t wchar){
1476   // UC_LINE_SEPARATOR + UC_PARAGRAPH_SEPARATOR
1477   if(wchar == L'\n' || wchar == L'\v' || wchar == L'\f'){
1478     return true;
1479   }
1480   const uint32_t mask = UC_CATEGORY_MASK_Zl | UC_CATEGORY_MASK_Zp;
1481   return uc_is_general_category_withtable(wchar, mask);
1482 }
1483 
1484 static inline bool
iswordbreak(wchar_t wchar)1485 iswordbreak(wchar_t wchar){
1486   const uint32_t mask = UC_CATEGORY_MASK_Z |
1487                         UC_CATEGORY_MASK_Zs;
1488   return uc_is_general_category_withtable(wchar, mask);
1489 }
1490 
1491 // the heart of damage detection. compare two nccells (from two different
1492 // planes) for equality. if they are equal, return 0. otherwise, dup the second
1493 // onto the first and return non-zero.
1494 static inline int
cellcmp_and_dupfar(egcpool * dampool,nccell * damcell,const ncplane * srcplane,const nccell * srccell)1495 cellcmp_and_dupfar(egcpool* dampool, nccell* damcell,
1496                    const ncplane* srcplane, const nccell* srccell){
1497   if(damcell->stylemask == srccell->stylemask){
1498     if(damcell->channels == srccell->channels){
1499       const char* srcegc = nccell_extended_gcluster(srcplane, srccell);
1500       const char* damegc = pool_extended_gcluster(dampool, damcell);
1501       if(strcmp(damegc, srcegc) == 0){
1502         return 0; // EGC match
1503       }
1504     }
1505   }
1506   cell_duplicate_far(dampool, damcell, srcplane, srccell);
1507   return 1;
1508 }
1509 
1510 int get_tty_fd(FILE* ttyfp);
1511 
1512 // Given the four channels arguments, verify that:
1513 //
1514 // - if any is default foreground, all are default foreground
1515 // - if any is default background, all are default background
1516 // - all foregrounds must have the same alpha
1517 // - all backgrounds must have the same alpha
1518 // - palette-indexed color must not be used
1519 //
1520 // If you only want to check n < 4 channels, just duplicate one.
1521 bool check_gradient_args(uint64_t ul, uint64_t ur, uint64_t bl, uint64_t br);
1522 
1523 // takes a signed starting coordinate (where -1 indicates the cursor's
1524 // position), and an unsigned vector (where 0 indicates "everything
1525 // remaining", i.e. to the right and below). returns 0 iff everything
1526 // is valid and on the plane, filling in 'ystart'/'xstart' with the
1527 // (non-negative) starting coordinates and 'ylen'/'xlen with the
1528 // (positive) dimensions of the affected area.
1529 static inline int
check_geometry_args(const ncplane * n,int y,int x,unsigned * ylen,unsigned * xlen,unsigned * ystart,unsigned * xstart)1530 check_geometry_args(const ncplane* n, int y, int x,
1531                     unsigned* ylen, unsigned* xlen,
1532                     unsigned* ystart, unsigned* xstart){
1533   // handle the special -1 case for y/x, and reject other negatives
1534   if(y < 0){
1535     if(y != -1){
1536       logerror("invalid y: %d\n", y);
1537       return -1;
1538     }
1539     y = n->y;
1540   }
1541   if(x < 0){
1542     if(x != -1){
1543       logerror("invalid x: %d\n", x);
1544       return -1;
1545     }
1546     x = n->x;
1547   }
1548   // y and x are both now definitely positive, but might be off-plane.
1549   // lock in y and x as ystart and xstart for unsigned comparisons.
1550   *ystart = y;
1551   *xstart = x;
1552   unsigned ymax, xmax;
1553   ncplane_dim_yx(n, &ymax, &xmax);
1554   if(*ystart >= ymax || *xstart >= xmax){
1555     logerror("invalid starting coordinates: %u/%u\n", *ystart, *xstart);
1556     return -1;
1557   }
1558   // handle the special 0 case for ylen/xlen
1559   if(*ylen == 0){
1560     *ylen = ymax - *ystart;
1561   }
1562   if(*xlen == 0){
1563     *xlen = xmax - *xstart;
1564   }
1565   // ensure ylen/xlen are on-plane
1566   if(*ylen > ymax){
1567     logerror("ylen > dimy %u > %u\n", *ylen, ymax);
1568     return -1;
1569   }
1570   if(*xlen > xmax){
1571     logerror("xlen > dimx %u > %u\n", *xlen, xmax);
1572     return -1;
1573   }
1574   // ensure x + xlen and y + ylen are on-plane, without overflow
1575   if(ymax - *ylen < *ystart){
1576     logerror("y + ylen > ymax %u + %u > %u\n", *ystart, *ylen, ymax);
1577     return -1;
1578   }
1579   if(xmax - *xlen < *xstart){
1580     logerror("x + xlen > xmax %u + %u > %u\n", *xstart, *xlen, xmax);
1581     return -1;
1582   }
1583   return 0;
1584 }
1585 
1586 void ncvisual_printbanner(fbuf* f);
1587 
1588 // alpha comes to us 0--255, but we have only 3 alpha values to map them to
1589 // (opaque, blended, and transparent). it's necessary that we display
1590 // something for any non-zero alpha (see #1540), so the threshold is 1.
1591 // we might want to map this to blended, but we only use opaque and
1592 // transparent for now. if |transcolor| is non-zero, match its lower 24
1593 // bits against each pixel's RGB value, and treat a match as transparent.
1594 static inline bool
rgba_trans_p(uint32_t p,uint32_t transcolor)1595 rgba_trans_p(uint32_t p, uint32_t transcolor){
1596   if(ncpixel_a(p) < 192){
1597     return true;
1598   }
1599   if(transcolor &&
1600       (ncpixel_r(p) == (transcolor & 0xff0000ull) >> 16) &&
1601       (ncpixel_g(p) == (transcolor & 0xff00ull) >> 8) &&
1602       (ncpixel_b(p) == (transcolor & 0xffull))){
1603     return true;
1604   }
1605   return false;
1606 }
1607 
1608 // get a non-negative "manhattan distance" between two rgb values
1609 static inline uint32_t
rgb_diff(unsigned r1,unsigned g1,unsigned b1,unsigned r2,unsigned g2,unsigned b2)1610 rgb_diff(unsigned r1, unsigned g1, unsigned b1, unsigned r2, unsigned g2, unsigned b2){
1611   uint32_t distance = 0;
1612   distance += r1 > r2 ? r1 - r2 : r2 - r1;
1613   distance += g1 > g2 ? g1 - g2 : g2 - g1;
1614   distance += b1 > b2 ? b1 - b2 : b2 - b1;
1615 //fprintf(stderr, "RGBDIFF %u %u %u %u %u %u: %u\n", r1, g1, b1, r2, g2, b2, distance);
1616   return distance;
1617 }
1618 
1619 // returns non-zero iff the two planes intersect
1620 static inline unsigned
ncplanes_intersect_p(const ncplane * p1,const ncplane * p2)1621 ncplanes_intersect_p(const ncplane* p1, const ncplane* p2){
1622   int y1, x1, y2, x2;
1623   int b1, r1, b2, r2;
1624   ncplane_abs_yx(p1, &y1, &x1);
1625   b1 = y1 + ncplane_dim_y(p1) - 1;
1626   r1 = x1 + ncplane_dim_x(p1) - 1;
1627   ncplane_abs_yx(p2, &y2, &x2);
1628   b2 = y2 + ncplane_dim_y(p2) - 1;
1629   r2 = x2 + ncplane_dim_x(p2) - 1;
1630   if(b1 < y2){ // p1 is above p2, no intersection
1631     return 0;
1632   }
1633   if(b2 < y1){ // p2 is above p1, no intersection
1634     return 0;
1635   }
1636   if(r1 < x2){ // p1 is to the left of p2, no intersection
1637     return 0;
1638   }
1639   if(r2 < x1){ // p2 is to the left of p1, no intersection
1640     return 0;
1641   }
1642   return 1;
1643 }
1644 
1645 static inline uint64_t
ncdirect_channels(const ncdirect * nc)1646 ncdirect_channels(const ncdirect* nc){
1647   return nc->channels;
1648 }
1649 
1650 static inline bool
ncdirect_fg_default_p(const ncdirect * nc)1651 ncdirect_fg_default_p(const ncdirect* nc){
1652   return ncchannels_fg_default_p(ncdirect_channels(nc));
1653 }
1654 
1655 static inline bool
ncdirect_bg_default_p(const ncdirect * nc)1656 ncdirect_bg_default_p(const ncdirect* nc){
1657   return ncchannels_bg_default_p(ncdirect_channels(nc));
1658 }
1659 
1660 static inline bool
ncdirect_fg_palindex_p(const ncdirect * nc)1661 ncdirect_fg_palindex_p(const ncdirect* nc){
1662   return ncchannels_fg_palindex_p(ncdirect_channels(nc));
1663 }
1664 
1665 static inline bool
ncdirect_bg_palindex_p(const ncdirect * nc)1666 ncdirect_bg_palindex_p(const ncdirect* nc){
1667   return ncchannels_bg_palindex_p(ncdirect_channels(nc));
1668 }
1669 
1670 int ncdirect_set_fg_rgb_f(ncdirect* nc, unsigned rgb, fbuf* f);
1671 int ncdirect_set_bg_rgb_f(ncdirect* nc, unsigned rgb, fbuf* f);
1672 int term_fg_rgb8(const tinfo* ti, fbuf* f, unsigned r, unsigned g, unsigned b);
1673 
1674 const struct blitset* lookup_blitset(const tinfo* tcache, ncblitter_e setid, bool may_degrade);
1675 
1676 static inline int
rgba_blit_dispatch(ncplane * nc,const struct blitset * bset,int linesize,const void * data,int leny,int lenx,const blitterargs * bargs)1677 rgba_blit_dispatch(ncplane* nc, const struct blitset* bset,
1678                    int linesize, const void* data,
1679                    int leny, int lenx, const blitterargs* bargs){
1680   return bset->blit(nc, linesize, data, leny, lenx, bargs);
1681 }
1682 
1683 int ncvisual_geom_inner(const tinfo* ti, const struct ncvisual* n,
1684                         const struct ncvisual_options* vopts, ncvgeom* geom,
1685                         const struct blitset** bset,
1686                         unsigned* disppxy, unsigned* disppxx,
1687                         unsigned* outy, unsigned* outx,
1688                         int* placey, int* placex);
1689 
1690 static inline const struct blitset*
rgba_blitter_low(const tinfo * tcache,ncscale_e scale,bool maydegrade,ncblitter_e blitrec)1691 rgba_blitter_low(const tinfo* tcache, ncscale_e scale, bool maydegrade,
1692                  ncblitter_e blitrec) {
1693   if(blitrec == NCBLIT_DEFAULT){
1694     blitrec = rgba_blitter_default(tcache, scale);
1695   }
1696   return lookup_blitset(tcache, blitrec, maydegrade);
1697 }
1698 
1699 // RGBA visuals all use NCBLIT_2x1 by default (or NCBLIT_1x1 if not in
1700 // UTF-8 mode), but an alternative can be specified.
1701 static inline const struct blitset*
rgba_blitter(const struct tinfo * tcache,const struct ncvisual_options * opts)1702 rgba_blitter(const struct tinfo* tcache, const struct ncvisual_options* opts) {
1703   const bool maydegrade = !(opts && (opts->flags & NCVISUAL_OPTION_NODEGRADE));
1704   const ncscale_e scale = opts ? opts->scaling : NCSCALE_NONE;
1705   return rgba_blitter_low(tcache, scale, maydegrade, opts ? opts->blitter : NCBLIT_DEFAULT);
1706 }
1707 
1708 // naive resize of |bmap| from |srows|x|scols| -> |drows|x|dcols|, suitable for
1709 // pixel art. we either select at a constant interval (for shrinking) or duplicate
1710 // at a constant ratio (for inflation). in the absence of a multimedia engine, this
1711 // is the only kind of resizing we support.
1712 static inline uint32_t*
resize_bitmap(const uint32_t * bmap,int srows,int scols,size_t sstride,int drows,int dcols,size_t dstride)1713 resize_bitmap(const uint32_t* bmap, int srows, int scols, size_t sstride,
1714               int drows, int dcols, size_t dstride){
1715   if(sstride < scols * sizeof(*bmap)){
1716     return NULL;
1717   }
1718   if(dstride < dcols * sizeof(*bmap)){
1719     return NULL;
1720   }
1721   // FIXME if parameters match current setup, do nothing, and return bmap
1722   size_t size = drows * dstride;
1723   uint32_t* ret = (uint32_t*)malloc(size);
1724   if(ret == NULL){
1725     return NULL;
1726   }
1727   float xrat = (float)dcols / scols;
1728   float yrat = (float)drows / srows;
1729   int dy = 0;
1730   for(int y = 0 ; y < srows ; ++y){
1731     float ytarg = (y + 1) * yrat;
1732     if(ytarg > drows){
1733       ytarg = drows;
1734     }
1735     while(ytarg > dy){
1736       int dx = 0;
1737       for(int x = 0 ; x < scols ; ++x){
1738         float xtarg = (x + 1) * xrat;
1739         if(xtarg > dcols){
1740           xtarg = dcols;
1741         }
1742         while(xtarg > dx){
1743           ret[dy * dstride / sizeof(*ret) + dx] = bmap[y * sstride / sizeof(*ret) + x];
1744           ++dx;
1745         }
1746       }
1747       ++dy;
1748     }
1749   }
1750   return ret;
1751 }
1752 
1753 // a neighbor on which to polyfill. by the time we get to it, it might or
1754 // might not have been filled in. if so, discard immediately. otherwise,
1755 // check self, and if valid, push all neighbors.
1756 struct topolyfill {
1757   int y, x;
1758   struct topolyfill* next;
1759 };
1760 
1761 static inline struct topolyfill*
create_polyfill_op(int y,int x,struct topolyfill ** stck)1762 create_polyfill_op(int y, int x, struct topolyfill** stck){
1763   // cast for the benefit of c++ callers
1764   struct topolyfill* n = (struct topolyfill*)malloc(sizeof(*n));
1765   if(n){
1766     n->y = y;
1767     n->x = x;
1768     n->next = *stck;
1769     *stck = n;
1770   }
1771   return n;
1772 }
1773 
1774 // implemented by a multimedia backend (ffmpeg or oiio), and installed
1775 // prior to calling notcurses_core_init() (by notcurses_init()).
1776 typedef struct ncvisual_implementation {
1777   int (*visual_init)(int loglevel);
1778   void (*visual_printbanner)(fbuf* f);
1779   int (*visual_blit)(struct ncvisual* ncv, unsigned rows, unsigned cols, ncplane* n,
1780                      const struct blitset* bset, const blitterargs* barg);
1781   struct ncvisual* (*visual_create)(void);
1782   struct ncvisual* (*visual_from_file)(const char* fname);
1783   // ncv constructors other than ncvisual_from_file() need to set up the
1784   // AVFrame* 'frame' according to their own data, which is assumed to
1785   // have been prepared already in 'ncv'.
1786   void (*visual_details_seed)(struct ncvisual* ncv);
1787   int (*visual_decode)(struct ncvisual* nc);
1788   int (*visual_decode_loop)(struct ncvisual* nc);
1789   int (*visual_stream)(notcurses* nc, struct ncvisual* ncv, float timescale,
1790                        ncstreamcb streamer, const struct ncvisual_options* vopts, void* curry);
1791   ncplane* (*visual_subtitle)(ncplane* parent, const struct ncvisual* ncv);
1792   int rowalign; // rowstride base, can be 0 for no padding
1793   // do a persistent resize, changing the ncv itself
1794   int (*visual_resize)(struct ncvisual* ncv, unsigned rows, unsigned cols);
1795   void (*visual_destroy)(struct ncvisual* ncv);
1796   bool canopen_images;
1797   bool canopen_videos;
1798 } ncvisual_implementation;
1799 
1800 // populated by libnotcurses.so if linked with multimedia
1801 API extern ncvisual_implementation* visual_implementation;
1802 
1803 // prepend base with the Notcurses data directory as configured.
1804 static inline char*
prefix_data(const char * base)1805 prefix_data(const char* base){
1806   // need a byte for each of directory separator and nul terminator
1807   const size_t dlen = strlen(NOTCURSES_SHARE);
1808   size_t len = dlen + strlen(base) + 2;
1809   char* path = (char*)malloc(len); // cast for C++ includers
1810   if(path){
1811     memcpy(path, NOTCURSES_SHARE, dlen);
1812     path[dlen] = path_separator();
1813     strcpy(path + dlen + 1, base);
1814   }
1815   return path;
1816 }
1817 
1818 // within unix, we can just use isatty(3). on windows, things work
1819 // differently. for a true Windows Terminal, we'll have HANDLE pointers
1820 // rather than file descriptors. in cygwin/msys2, isatty(3) always fails.
1821 // so for __MINGW64__, always return true. otherwise return isatty(fd).
1822 static inline int
tty_check(int fd)1823 tty_check(int fd){
1824 #ifdef __MINGW64__
1825   return _isatty(fd);
1826 #endif
1827   return isatty(fd);
1828 }
1829 
1830 // attempt to cancel the specified thread (not an error if we can't; it might
1831 // have already exited), and then join it (an error here is propagated).
1832 static inline int
cancel_and_join(const char * name,pthread_t tid,void ** res)1833 cancel_and_join(const char* name, pthread_t tid, void** res){
1834   if(pthread_cancel(tid)){
1835     logerror("couldn't cancel %s thread\n", name); // tid might have died
1836   }
1837   if(pthread_join(tid, res)){
1838     logerror("error joining %s thread\n", name);
1839     return -1;
1840   }
1841   return 0;
1842 }
1843 
1844 static inline int
emit_scrolls(const tinfo * ti,int count,fbuf * f)1845 emit_scrolls(const tinfo* ti, int count, fbuf* f){
1846   logdebug("emitting %d scrolls\n", count);
1847   if(count > 1){
1848     const char* indn = get_escape(ti, ESCAPE_INDN);
1849     if(indn){
1850       if(fbuf_emit(f, tiparm(indn, count)) < 0){
1851         return -1;
1852       }
1853       return 0;
1854     }
1855   }
1856   const char* ind = get_escape(ti, ESCAPE_IND);
1857   if(ind == NULL){
1858     ind = "\v";
1859   }
1860   // fell through if we had no indn
1861   while(count > 0){
1862     if(fbuf_emit(f, ind) < 0){
1863       return -1;
1864     }
1865     --count;
1866   }
1867   return 0;
1868 }
1869 
1870 // both emit the |count > 0| scroll ops to |f|, and update the cursor
1871 // tracking in |nc|
1872 static inline int
emit_scrolls_track(notcurses * nc,int count,fbuf * f)1873 emit_scrolls_track(notcurses* nc, int count, fbuf* f){
1874   if(emit_scrolls(&nc->tcache, count, f)){
1875     return -1;
1876   }
1877   nc->rstate.y -= count;
1878   nc->rstate.x = 0;
1879   return 0;
1880 }
1881 
1882 // replace or populate the TERM environment variable with 'termname'
1883 int putenv_term(const char* termname) __attribute__ ((nonnull (1)));
1884 
1885 // check environment for NOTCURSES_LOGLEVEL, and use it if defined.
1886 int set_loglevel_from_env(ncloglevel_e* loglevel)
1887   __attribute__ ((nonnull (1)));
1888 
1889 #undef API
1890 #undef ALLOC
1891 
1892 #ifdef __cplusplus
1893 }
1894 #endif
1895 
1896 #endif
1897