1 #include <ctype.h>
2 #include <limits.h>
3 #include <unistd.h>
4 #include <notcurses/direct.h>
5 #include "internal.h"
6 #include "unixsig.h"
7 
8 sig_atomic_t sigcont_seen_for_render = 0;
9 
10 // update for a new visual area of |rows|x|cols|, neither of which may be zero.
11 // copies that area of the lastframe (damage map) which is shared between the
12 // two. new areas are initialized to empty, just like a new plane. lost areas
13 // have their egcpool entries purged.
14 static nccell*
restripe_lastframe(notcurses * nc,unsigned rows,unsigned cols)15 restripe_lastframe(notcurses* nc, unsigned rows, unsigned cols){
16   assert(rows);
17   assert(cols);
18   const size_t size = sizeof(*nc->lastframe) * (rows * cols);
19   nccell* tmp = malloc(size);
20   if(tmp == NULL){
21     return NULL;
22   }
23   size_t copycols = nc->lfdimx > cols ? cols : nc->lfdimx;
24   size_t maxlinecopy = sizeof(nccell) * copycols;
25   size_t minlineset = sizeof(nccell) * cols - maxlinecopy;
26   unsigned zorch = nc->lfdimx > cols ? nc->lfdimx - cols : 0;
27   for(unsigned y = 0 ; y < rows ; ++y){
28     if(y < nc->lfdimy){
29       if(maxlinecopy){
30         memcpy(&tmp[cols * y], &nc->lastframe[nc->lfdimx * y], maxlinecopy);
31       }
32       if(minlineset){
33         memset(&tmp[cols * y + copycols], 0, minlineset);
34       }
35       // excise any egcpool entries from the right of the new plane area
36       if(zorch){
37         for(unsigned x = copycols ; x < copycols + zorch ; ++x){
38           pool_release(&nc->pool, &nc->lastframe[fbcellidx(y, nc->lfdimx, x)]);
39         }
40       }
41     }else{
42       memset(&tmp[cols * y], 0, sizeof(nccell) * cols);
43     }
44   }
45   // excise any egcpool entries from below the new plane area
46   for(unsigned y = rows ; y < nc->lfdimy ; ++y){
47     for(unsigned x = 0 ; x < nc->lfdimx ; ++x){
48       pool_release(&nc->pool, &nc->lastframe[fbcellidx(y, nc->lfdimx, x)]);
49     }
50   }
51   free(nc->lastframe);
52   nc->lastframe = tmp;
53   nc->lfdimy = rows;
54   nc->lfdimx = cols;
55   return 0;
56 }
57 
58 // Check whether the terminal geometry has changed, and if so, copy what can
59 // be copied from the old lastframe. Assumes that the screen is always anchored
60 // at the same origin. Initiates a resize cascade for the pile containing |pp|.
61 // The current terminal geometry, changed or not, is written to |rows|/|cols|.
62 static int
notcurses_resize_internal(ncplane * pp,unsigned * restrict rows,unsigned * restrict cols)63 notcurses_resize_internal(ncplane* pp, unsigned* restrict rows, unsigned* restrict cols){
64   notcurses* n = ncplane_notcurses(pp);
65   unsigned r, c;
66   if(rows == NULL){
67     rows = &r;
68   }
69   if(cols == NULL){
70     cols = &c;
71   }
72   ncpile* pile = ncplane_pile(pp);
73   unsigned oldrows = pile->dimy;
74   unsigned oldcols = pile->dimx;
75   *rows = oldrows;
76   *cols = oldcols;
77   unsigned cgeo_changed;
78   unsigned pgeo_changed;
79   if(update_term_dimensions(rows, cols, &n->tcache, n->margin_b,
80                             &cgeo_changed, &pgeo_changed)){
81     return -1;
82   }
83   n->stats.s.cell_geo_changes += cgeo_changed;
84   n->stats.s.pixel_geo_changes += pgeo_changed;
85   *rows -= n->margin_t + n->margin_b;
86   if(*rows <= 0){
87     *rows = 1;
88   }
89   *cols -= n->margin_l + n->margin_r;
90   if(*cols <= 0){
91     *cols = 1;
92   }
93   if(*rows != n->lfdimy || *cols != n->lfdimx){
94     if(restripe_lastframe(n, *rows, *cols)){
95       return -1;
96     }
97   }
98 //fprintf(stderr, "r: %d or: %d c: %d oc: %d\n", *rows, oldrows, *cols, oldcols);
99   if(*rows == oldrows && *cols == oldcols){
100     return 0; // no change
101   }
102   pile->dimy = *rows;
103   pile->dimx = *cols;
104   int ret = 0;
105 //notcurses_debug(n, stderr);
106   // if this pile contains the standard plane, it ought be resized to match
107   // the viewing area before invoking any other resize callbacks.
108   if(ncplane_pile(notcurses_stdplane(n)) == pile){
109     ncplane_resize_maximize(notcurses_stdplane(n));
110   }
111   // now, begin a resize callback cascade on the root planes of the pile.
112   for(ncplane* rootn = pile->roots ; rootn ; rootn = rootn->bnext){
113     if(rootn->resizecb){
114       ret |= rootn->resizecb(rootn);
115     }
116   }
117   return ret;
118 }
119 
120 // Check for a window resize on the standard pile.
121 static int
notcurses_resize(notcurses * n,unsigned * restrict rows,unsigned * restrict cols)122 notcurses_resize(notcurses* n, unsigned* restrict rows, unsigned* restrict cols){
123   pthread_mutex_lock(&n->pilelock);
124   int ret = notcurses_resize_internal(notcurses_stdplane(n), rows, cols);
125   pthread_mutex_unlock(&n->pilelock);
126   return ret;
127 }
128 
nccell_release(ncplane * n,nccell * c)129 void nccell_release(ncplane* n, nccell* c){
130   pool_release(&n->pool, c);
131 }
132 
133 // Duplicate one cell onto another when they share a plane. Convenience wrapper.
nccell_duplicate(ncplane * n,nccell * targ,const nccell * c)134 int nccell_duplicate(ncplane* n, nccell* targ, const nccell* c){
135   if(cell_duplicate_far(&n->pool, targ, n, c) < 0){
136     logerror("Failed duplicating cell\n");
137     return -1;
138   }
139   return 0;
140 }
141 
142 // Emit fchannel with RGB changed to contrast effectively against bchannel.
143 static uint32_t
highcontrast(const tinfo * ti,uint32_t bchannel)144 highcontrast(const tinfo* ti, uint32_t bchannel){
145   unsigned r, g, b;
146   if(ncchannel_default_p(bchannel)){
147     // FIXME what if we couldn't identify the background color?
148     r = ncchannel_r(ti->bg_collides_default);
149     g = ncchannel_g(ti->bg_collides_default);
150     b = ncchannel_b(ti->bg_collides_default);
151   }else{
152     // FIXME need to handle palette-indexed
153     r = ncchannel_r(bchannel);
154     g = ncchannel_g(bchannel);
155     b = ncchannel_b(bchannel);
156   }
157   uint32_t conrgb = 0;
158   if(r + g + b < 320){
159     ncchannel_set(&conrgb, 0xffffff);
160   }else{
161     ncchannel_set(&conrgb, 0);
162   }
163   return conrgb;
164 }
165 
166 // wants coordinates within the sprixel, not absolute
167 // FIXME if plane is not wholly on-screen, probably need to toss plane,
168 // at least for this rendering cycle
169 static void
paint_sprixel(ncplane * p,struct crender * rvec,int starty,int startx,int offy,int offx,int dstleny,int dstlenx)170 paint_sprixel(ncplane* p, struct crender* rvec, int starty, int startx,
171               int offy, int offx, int dstleny, int dstlenx){
172   const notcurses* nc = ncplane_notcurses_const(p);
173   sprixel* s = p->sprite;
174   int dimy = s->dimy;
175   int dimx = s->dimx;
176 //fprintf(stderr, "STARTY: %d DIMY: %d dim(p): %d/%d dim(s): %d/%d\n", starty, dimy, ncplane_dim_y(p), ncplane_dim_x(p), s->dimy, s->dimx);
177   if(s->invalidated == SPRIXEL_HIDE){ // no need to do work if we're killing it
178     return;
179   }
180   for(int y = starty ; y < dimy ; ++y){
181     const int absy = y + offy;
182     // once we've passed the physical screen's bottom, we're done
183     if(absy >= dstleny || absy < 0){
184       break;
185     }
186     for(int x = startx ; x < dimx ; ++x){ // iteration for each cell
187       const int absx = x + offx;
188       if(absx >= dstlenx || absx < 0){
189         break;
190       }
191       sprixcell_e state = sprixel_state(s, absy, absx);
192       struct crender* crender = &rvec[fbcellidx(absy, dstlenx, absx)];
193 //fprintf(stderr, "presprixel: %p preid: %d state: %d\n", rvec->sprixel, rvec->sprixel ? rvec->sprixel->id : 0, s->invalidated);
194       // if we already have a glyph solved (meaning said glyph is above this
195       // sprixel), and we run into a bitmap cell, we need to null that cell out
196       // of the bitmap.
197       if(crender->p || crender->s.bgblends){
198         // if sprite_wipe_cell() fails, we presumably do not have the
199         // ability to wipe, and must reprint the character
200         if(sprite_wipe(nc, p->sprite, y, x) < 0){
201 //fprintf(stderr, "damaging due to wipe [%s] %d/%d\n", nccell_extended_gcluster(crender->p, &crender->c), absy, absx);
202           crender->s.damaged = 1;
203         }
204         crender->s.p_beats_sprixel = 1;
205       }else if(!crender->p && !crender->s.bgblends){
206         // if we are a bitmap, and above a cell that has changed (and
207         // will thus be printed), we'll need redraw the sprixel.
208         if(crender->sprixel == NULL){
209           crender->sprixel = s;
210         }
211         if(state == SPRIXCELL_ANNIHILATED || state == SPRIXCELL_ANNIHILATED_TRANS){
212 //fprintf(stderr, "REBUILDING AT %d/%d\n", y, x);
213           sprite_rebuild(nc, s, y, x);
214 //fprintf(stderr, "damaging due to rebuild [%s] %d/%d\n", nccell_extended_gcluster(crender->p, &crender->c), absy, absx);
215         }
216       }
217     }
218   }
219 }
220 
221 // Paints a single ncplane 'p' into the provided scratch framebuffer 'fb' (we
222 // can't always write directly into lastframe, because we need build state to
223 // solve certain cells, and need compare their solved result to the last frame).
224 //
225 //  dstleny: leny of target rendering area described by rvec
226 //  dstlenx: lenx of target rendering area described by rvec
227 //  dstabsy: absy of target rendering area (relative to terminal)
228 //  dstabsx: absx of target rendering area (relative to terminal)
229 //
230 // only those cells where 'p' intersects with the target rendering area are
231 // rendered.
232 //
233 // the sprixelstack orders sprixels of the plane (so we needn't keep them
234 // ordered between renders). each time we meet a sprixel, extract it from
235 // the pile's sprixel list, and update the sprixelstack.
236 __attribute__ ((nonnull (1, 2, 7))) static void
paint(ncplane * p,struct crender * rvec,int dstleny,int dstlenx,int dstabsy,int dstabsx,sprixel ** sprixelstack,unsigned pgeo_changed)237 paint(ncplane* p, struct crender* rvec, int dstleny, int dstlenx,
238       int dstabsy, int dstabsx, sprixel** sprixelstack,
239       unsigned pgeo_changed){
240   unsigned y, x, dimy, dimx;
241   int offy, offx;
242   ncplane_dim_yx(p, &dimy, &dimx);
243   offy = p->absy - dstabsy;
244   offx = p->absx - dstabsx;
245 //fprintf(stderr, "PLANE %p %d %d %d %d %d %d %p\n", p, dimy, dimx, offy, offx, dstleny, dstlenx, p->sprite);
246   // skip content above or to the left of the physical screen
247   unsigned starty, startx;
248   if(offy < 0){
249     starty = -offy;
250   }else{
251     starty = 0;
252   }
253   if(offx < 0){
254     startx = -offx;
255   }else{
256     startx = 0;
257   }
258   // if we're a sprixel, we must not register ourselves as the active
259   // glyph, but we *do* need to null out any cellregions that we've
260   // scribbled upon.
261   if(p->sprite){
262     if(pgeo_changed){
263       // do what on failure? FIXME
264       sprixel_rescale(p->sprite, ncplane_pile(p)->cellpxy, ncplane_pile(p)->cellpxx);
265     }
266     paint_sprixel(p, rvec, starty, startx, offy, offx, dstleny, dstlenx);
267     // decouple from the pile's sixel list
268     if(p->sprite->next){
269       p->sprite->next->prev = p->sprite->prev;
270     }
271     if(p->sprite->prev){
272       p->sprite->prev->next = p->sprite->next;
273     }else{
274       ncplane_pile(p)->sprixelcache = p->sprite->next;
275     }
276     // stick on the head of the running list: top sprixel is at end
277     if(*sprixelstack){
278       (*sprixelstack)->prev = p->sprite;
279     }
280     p->sprite->next = *sprixelstack;
281     p->sprite->prev = NULL;
282     *sprixelstack = p->sprite;
283     return;
284   }
285   for(y = starty ; y < dimy ; ++y){
286     const int absy = y + offy;
287     // once we've passed the physical screen's bottom, we're done
288     if(absy >= dstleny || absy < 0){
289       break;
290     }
291     for(x = startx ; x < dimx ; ++x){ // iteration for each cell
292       const int absx = x + offx;
293       if(absx >= dstlenx || absx < 0){
294         break;
295       }
296       struct crender* crender = &rvec[fbcellidx(absy, dstlenx, absx)];
297 //fprintf(stderr, "p: %p damaged: %u %d/%d\n", p, crender->s.damaged, y, x);
298       nccell* targc = &crender->c;
299       if(nccell_wide_right_p(targc)){
300         continue;
301       }
302 
303       if(nccell_fg_alpha(targc) > NCALPHA_OPAQUE){
304         const nccell* vis = &p->fb[nfbcellidx(p, y, x)];
305         if(nccell_fg_default_p(vis)){
306           vis = &p->basecell;
307         }
308         if(nccell_fg_alpha(vis) == NCALPHA_HIGHCONTRAST){
309           crender->s.highcontrast = true;
310           crender->s.hcfgblends = crender->s.fgblends;
311           crender->hcfg = cell_fchannel(targc);
312         }
313         unsigned fgblends = crender->s.fgblends;
314         cell_blend_fchannel(ncplane_notcurses(p), targc, cell_fchannel(vis), &fgblends);
315         crender->s.fgblends = fgblends;
316         // crender->highcontrast can only be true if we just set it, since we're
317         // about to set targc opaque based on crender->highcontrast (and this
318         // entire stanza is conditional on targc not being NCALPHA_OPAQUE).
319         if(crender->s.highcontrast){
320           nccell_set_fg_alpha(targc, NCALPHA_OPAQUE);
321         }
322       }
323 
324       // Background color takes effect independently of whether we have a
325       // glyph. If we've already locked in the background, it has no effect.
326       // If it's transparent, it has no effect. Otherwise, update the
327       // background channel and balpha.
328       // Evaluate the background first, in case we have HIGHCONTRAST fg text.
329       if(nccell_bg_alpha(targc) > NCALPHA_OPAQUE){
330         const nccell* vis = &p->fb[nfbcellidx(p, y, x)];
331         // to be on the blitter stacking path, we need
332         //  1) crender->s.blittedquads to be non-zero (we're below semigraphics)
333         //  2) cell_blittedquadrants(vis) to be non-zero (we're semigraphics)
334         //  3) somewhere crender is 0, blittedquads is 1 (we're visible)
335         if(!crender->s.blittedquads || !((~crender->s.blittedquads) & cell_blittedquadrants(vis))){
336           if(nccell_bg_default_p(vis)){
337             vis = &p->basecell;
338           }
339           unsigned bgblends = crender->s.bgblends;
340           cell_blend_bchannel(ncplane_notcurses(p), targc, cell_bchannel(vis), &bgblends);
341           crender->s.bgblends = bgblends;
342         }else{ // use the local foreground; we're stacking blittings
343           if(nccell_fg_default_p(vis)){
344             vis = &p->basecell;
345           }
346           unsigned bgblends = crender->s.bgblends;
347           cell_blend_bchannel(ncplane_notcurses(p), targc, cell_fchannel(vis), &bgblends);
348           crender->s.bgblends = bgblends;
349           crender->s.blittedquads = 0;
350         }
351       }
352 
353       // if we never loaded any content into the cell (or obliterated it by
354       // writing in a zero), use the plane's base cell.
355       // if we have no character in this cell, we continue to look for a
356       // character, but our foreground color will still be used unless it's
357       // been set to transparent. if that foreground color is transparent, we
358       // still use a character we find here, but its color will come entirely
359       // from cells underneath us.
360       if(!crender->p){
361         const nccell* vis = &p->fb[nfbcellidx(p, y, x)];
362         if(vis->gcluster == 0 && !nccell_double_wide_p(vis)){
363           vis = &p->basecell;
364         }
365         // if the following is true, we're a real glyph, and not the right-hand
366         // side of a wide glyph (nor the null codepoint).
367         if( (targc->gcluster = vis->gcluster) ){ // index copy only
368           if(crender->sprixel && crender->sprixel->invalidated == SPRIXEL_HIDE){
369 //fprintf(stderr, "damaged due to hide %d/%d\n", y, x);
370             crender->s.damaged = 1;
371           }
372           crender->s.blittedquads = cell_blittedquadrants(vis);
373           // we can't plop down a wide glyph if the next cell is beyond the
374           // screen, nor if we're bisected by a higher plane.
375           if(nccell_double_wide_p(vis)){
376             // are we on the last column of the real screen? if so, 0x20 us
377             if(absx >= dstlenx - 1){
378               targc->gcluster = htole(' ');
379               targc->width = 1;
380             // is the next cell occupied? if so, 0x20 us
381             }else if(crender[1].c.gcluster){
382 //fprintf(stderr, "NULLING out %d/%d (%d/%d) due to %u\n", y, x, absy, absx, crender[1].c.gcluster);
383               targc->gcluster = htole(' ');
384               targc->width = 1;
385             }else{
386               targc->stylemask = vis->stylemask;
387               targc->width = vis->width;
388             }
389           }else{
390             targc->stylemask = vis->stylemask;
391             targc->width = vis->width;
392           }
393           crender->p = p;
394         }else if(nccell_wide_right_p(vis)){
395           crender->p = p;
396           targc->width = 0;
397         }
398       }
399     }
400   }
401 }
402 
403 // it's not a pure memset(), because NCALPHA_OPAQUE is the zero value, and
404 // we need NCALPHA_TRANSPARENT
405 static inline void
init_rvec(struct crender * rvec,int totalcells)406 init_rvec(struct crender* rvec, int totalcells){
407   struct crender c = {};
408   nccell_set_fg_alpha(&c.c, NCALPHA_TRANSPARENT);
409   nccell_set_bg_alpha(&c.c, NCALPHA_TRANSPARENT);
410   for(int t = 0 ; t < totalcells ; ++t){
411     memcpy(&rvec[t], &c, sizeof(c));
412   }
413 }
414 
415 // adjust an otherwise locked-in cell if highcontrast has been requested. this
416 // should be done at the end of rendering the cell, so that contrast is solved
417 // against the real background.
418 static inline void
lock_in_highcontrast(notcurses * nc,const tinfo * ti,nccell * targc,struct crender * crender)419 lock_in_highcontrast(notcurses* nc, const tinfo* ti, nccell* targc, struct crender* crender){
420   if(nccell_fg_alpha(targc) == NCALPHA_TRANSPARENT){
421     nccell_set_fg_default(targc);
422   }
423   if(nccell_bg_alpha(targc) == NCALPHA_TRANSPARENT){
424     nccell_set_bg_default(targc);
425   }
426   if(crender->s.highcontrast){
427     // highcontrast weighs the original at 1/4 and the contrast at 3/4
428     if(!nccell_fg_default_p(targc)){
429       unsigned fgblends = 3;
430       uint32_t fchan = cell_fchannel(targc);
431       uint32_t bchan = cell_bchannel(targc);
432       uint32_t hchan = channels_blend(nc, highcontrast(ti, bchan), fchan, &fgblends);
433       cell_set_fchannel(targc, hchan);
434       fgblends = crender->s.hcfgblends;
435       hchan = channels_blend(nc, hchan, crender->hcfg, &fgblends);
436       cell_set_fchannel(targc, hchan);
437     }else{
438       nccell_set_fg_rgb(targc, highcontrast(ti, cell_bchannel(targc)));
439     }
440   }
441 }
442 
443 // Postpaint a single cell (multiple if it is a multicolumn EGC). This means
444 // checking for and locking in high-contrast, checking for damage, and updating
445 // 'lastframe' for any cells which are damaged.
446 static inline void
postpaint_cell(notcurses * nc,const tinfo * ti,nccell * lastframe,int dimx,struct crender * crender,egcpool * pool,int y,int * x)447 postpaint_cell(notcurses* nc, const tinfo* ti, nccell* lastframe, int dimx,
448                struct crender* crender, egcpool* pool, int y, int* x){
449   nccell* targc = &crender->c;
450   lock_in_highcontrast(nc, ti, targc, crender);
451   nccell* prevcell = &lastframe[fbcellidx(y, dimx, *x)];
452   if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc) > 0){
453 //fprintf(stderr, "damaging due to cmp [%s] %d %d\n", nccell_extended_gcluster(crender->p, &crender->c), y, *x);
454     if(crender->sprixel){
455       sprixcell_e state = sprixel_state(crender->sprixel, y, *x);
456 //fprintf(stderr, "state under candidate sprixel: %d %d/%d\n", state, y, *x);
457       // we don't need to change it when under an opaque cell, because
458       // that's always printed on top.
459       if(!crender->s.p_beats_sprixel){
460         if(state != SPRIXCELL_OPAQUE_SIXEL){
461 //fprintf(stderr, "damaged due to opaque %d/%d\n", y, *x);
462           crender->s.damaged = 1;
463         }
464       }
465     }else{
466 //fprintf(stderr, "damaged due to lastframe disagree %d/%d\n", y, *x);
467       crender->s.damaged = 1;
468     }
469     assert(!nccell_wide_right_p(targc));
470     const int width = targc->width;
471     for(int i = 1 ; i < width ; ++i){
472       const ncplane* tmpp = crender->p;
473       ++crender;
474       crender->p = tmpp;
475       ++*x;
476       ++prevcell;
477       targc = &crender->c;
478       targc->gcluster = 0;
479       targc->channels = crender[-i].c.channels;
480       targc->stylemask = crender[-i].c.stylemask;
481       if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc) > 0){
482 //fprintf(stderr, "damaging due to cmp2 %d/%d\n", y, *x);
483         crender->s.damaged = 1;
484       }
485     }
486   }
487 }
488 
489 
490 // iterate over the rendered frame, adjusting the foreground colors for any
491 // cells marked NCALPHA_HIGHCONTRAST, and clearing any cell covered by a
492 // wide glyph to its left.
493 //
494 // FIXME this cannot be performed at render time (we don't yet know the
495 //       lastframe, and thus can't compute damage), but we *could* unite it
496 //       with rasterization--factor out the single cell iteration...
497 // FIXME can we not do the blend a single time here, if we track sums in
498 //       paint()? tried this before and didn't get a win...
499 static void
postpaint(notcurses * nc,const tinfo * ti,nccell * lastframe,int dimy,int dimx,struct crender * rvec,egcpool * pool)500 postpaint(notcurses* nc, const tinfo* ti, nccell* lastframe, int dimy, int dimx,
501           struct crender* rvec, egcpool* pool){
502   for(int y = 0 ; y < dimy ; ++y){
503     for(int x = 0 ; x < dimx ; ++x){
504       struct crender* crender = &rvec[fbcellidx(y, dimx, x)];
505       postpaint_cell(nc, ti, lastframe, dimx, crender, pool, y, &x);
506     }
507   }
508 }
509 
510 // merging one plane down onto another is basically just performing a render
511 // using only these two planes, with the result written to the lower plane.
ncplane_mergedown(ncplane * restrict src,ncplane * restrict dst,int begsrcy,int begsrcx,unsigned leny,unsigned lenx,int dsty,int dstx)512 int ncplane_mergedown(ncplane* restrict src, ncplane* restrict dst,
513                       int begsrcy, int begsrcx, unsigned leny, unsigned lenx,
514                       int dsty, int dstx){
515 //fprintf(stderr, "Merging down %d/%d @ %d/%d to %d/%d\n", leny, lenx, begsrcy, begsrcx, dsty, dstx);
516   if(dsty < 0){
517     if(dsty != -1){
518       logerror("invalid dsty %d\n", dsty);
519       return -1;
520     }
521     dsty = dst->y;
522   }
523   if(dstx < 0){
524     if(dstx != -1){
525       logerror("invalid dstx %d\n", dstx);
526       return -1;
527     }
528     dstx = dst->x;
529   }
530   if((unsigned)dsty >= dst->leny || (unsigned)dstx >= dst->lenx){
531     logerror("dest origin %u/%u ≥ dest dimensions %d/%d\n",
532              dsty, dstx, dst->leny, dst->lenx);
533     return -1;
534   }
535   if(begsrcy < 0){
536     if(begsrcy != -1){
537       logerror("invalid begsrcy %d\n", begsrcy);
538       return -1;
539     }
540     begsrcy = src->y;
541   }
542   if(begsrcx < 0){
543     if(begsrcx != -1){
544       logerror("invalid begsrcx %d\n", begsrcx);
545       return -1;
546     }
547     begsrcx = src->x;
548   }
549   if((unsigned)begsrcy >= src->leny || (unsigned)begsrcx >= src->lenx){
550     logerror("source origin %u/%u ≥ source dimensions %d/%d\n",
551              begsrcy, begsrcx, src->leny, src->lenx);
552     return -1;
553   }
554   if(leny == 0){
555     if((leny = src->leny - begsrcy) == 0){
556       logerror("source area was zero height\n");
557       return -1;
558     }
559   }
560   if(lenx == 0){
561     if((lenx = src->lenx - begsrcx) == 0){
562       logerror("source area was zero width\n");
563       return -1;
564     }
565   }
566   if(dst->leny - leny < (unsigned)dsty || dst->lenx - lenx < (unsigned)dstx){
567     logerror("dest len %u/%u ≥ dest dimensions %d/%d\n",
568              leny, lenx, dst->leny, dst->lenx);
569     return -1;
570   }
571   if(src->leny - leny < (unsigned)begsrcy || src->lenx - lenx < (unsigned)begsrcx){
572     logerror("source len %u/%u ≥ source dimensions %d/%d\n",
573              leny, lenx, src->leny, src->lenx);
574     return -1;
575   }
576   if(src->sprite || dst->sprite){
577     logerror("can't merge sprixel planes\n");
578     return -1;
579   }
580   const int totalcells = dst->leny * dst->lenx;
581   nccell* rendfb = calloc(sizeof(*rendfb), totalcells);
582   const size_t crenderlen = sizeof(struct crender) * totalcells;
583   struct crender* rvec = malloc(crenderlen);
584   if(!rendfb || !rvec){
585     logerror("error allocating render state for %ux%u\n", leny, lenx);
586     free(rendfb);
587     free(rvec);
588     return -1;
589   }
590   init_rvec(rvec, totalcells);
591   sprixel* s = NULL;
592   paint(src, rvec, dst->leny, dst->lenx, dst->absy, dst->absx, &s, 0);
593   assert(NULL == s);
594   paint(dst, rvec, dst->leny, dst->lenx, dst->absy, dst->absx, &s, 0);
595   assert(NULL == s);
596 //fprintf(stderr, "Postpaint start (%dx%d)\n", dst->leny, dst->lenx);
597   const struct tinfo* ti = &ncplane_notcurses_const(dst)->tcache;
598   postpaint(ncplane_notcurses(dst), ti, rendfb, dst->leny, dst->lenx, rvec, &dst->pool);
599 //fprintf(stderr, "Postpaint done (%dx%d)\n", dst->leny, dst->lenx);
600   free(dst->fb);
601   dst->fb = rendfb;
602   free(rvec);
603   return 0;
604 }
605 
ncplane_mergedown_simple(ncplane * restrict src,ncplane * restrict dst)606 int ncplane_mergedown_simple(ncplane* restrict src, ncplane* restrict dst){
607   return ncplane_mergedown(src, dst, 0, 0, 0, 0, 0, 0);
608 }
609 
610 // write the nccell's UTF-8 extended grapheme cluster to the provided fbuf.
611 static inline int
term_putc(fbuf * f,const egcpool * e,const nccell * c)612 term_putc(fbuf* f, const egcpool* e, const nccell* c){
613   if(cell_simple_p(c)){
614 //fprintf(stderr, "[%.4s] %08x\n", (const char*)&c->gcluster, c->gcluster); }
615     // we must not have any 'cntrl' characters at this point, except for
616     // nil or newline
617     if(c->gcluster == 0 || c->gcluster == '\n'){
618       if(fbuf_putc(f, ' ') < 0){
619         return -1;
620       }
621     }else if(fbuf_puts(f, (const char*)&c->gcluster) == EOF){
622       return -1;
623     }
624   }else{
625     if(fbuf_puts(f, egcpool_extended_gcluster(e, c)) == EOF){
626       return -1;
627     }
628   }
629   return 0;
630 }
631 
632 // write any escape sequences necessary to set the desired style
633 static inline int
term_setstyles(fbuf * f,notcurses * nc,const nccell * c)634 term_setstyles(fbuf* f, notcurses* nc, const nccell* c){
635   unsigned normalized = false;
636   int ret = coerce_styles(f, &nc->tcache, &nc->rstate.curattr,
637                           nccell_styles(c), &normalized);
638   if(normalized){
639     nc->rstate.fgdefelidable = true;
640     nc->rstate.bgdefelidable = true;
641     nc->rstate.bgelidable = false;
642     nc->rstate.fgelidable = false;
643     nc->rstate.bgpalelidable = false;
644     nc->rstate.fgpalelidable = false;
645   }
646   return ret;
647 }
648 
649 // u8->str lookup table used in term_esc_rgb below
650 static const char* const NUMBERS[] = {
651 "0;", "1;", "2;", "3;", "4;", "5;", "6;", "7;", "8;", "9;", "10;", "11;", "12;", "13;", "14;", "15;", "16;",
652 "17;", "18;", "19;", "20;", "21;", "22;", "23;", "24;", "25;", "26;", "27;", "28;", "29;", "30;", "31;", "32;",
653 "33;", "34;", "35;", "36;", "37;", "38;", "39;", "40;", "41;", "42;", "43;", "44;", "45;", "46;", "47;", "48;",
654 "49;", "50;", "51;", "52;", "53;", "54;", "55;", "56;", "57;", "58;", "59;", "60;", "61;", "62;", "63;", "64;",
655 "65;", "66;", "67;", "68;", "69;", "70;", "71;", "72;", "73;", "74;", "75;", "76;", "77;", "78;", "79;", "80;",
656 "81;", "82;", "83;", "84;", "85;", "86;", "87;", "88;", "89;", "90;", "91;", "92;", "93;", "94;", "95;", "96;",
657 "97;", "98;", "99;", "100;", "101;", "102;", "103;", "104;", "105;", "106;", "107;", "108;", "109;", "110;", "111;", "112;",
658 "113;", "114;", "115;", "116;", "117;", "118;", "119;", "120;", "121;", "122;", "123;", "124;", "125;", "126;", "127;", "128;",
659 "129;", "130;", "131;", "132;", "133;", "134;", "135;", "136;", "137;", "138;", "139;", "140;", "141;", "142;", "143;", "144;",
660 "145;", "146;", "147;", "148;", "149;", "150;", "151;", "152;", "153;", "154;", "155;", "156;", "157;", "158;", "159;", "160;",
661 "161;", "162;", "163;", "164;", "165;", "166;", "167;", "168;", "169;", "170;", "171;", "172;", "173;", "174;", "175;", "176;",
662 "177;", "178;", "179;", "180;", "181;", "182;", "183;", "184;", "185;", "186;", "187;", "188;", "189;", "190;", "191;", "192;",
663 "193;", "194;", "195;", "196;", "197;", "198;", "199;", "200;", "201;", "202;", "203;", "204;", "205;", "206;", "207;", "208;",
664 "209;", "210;", "211;", "212;", "213;", "214;", "215;", "216;", "217;", "218;", "219;", "220;", "221;", "222;", "223;", "224;",
665 "225;", "226;", "227;", "228;", "229;", "230;", "231;", "232;", "233;", "234;", "235;", "236;", "237;", "238;", "239;", "240;",
666 "241;", "242;", "243;", "244;", "245;", "246;", "247;", "248;", "249;", "250;", "251;", "252;", "253;", "254;", "255;", };
667 
668 static inline int
term_esc_rgb(fbuf * f,bool foreground,unsigned r,unsigned g,unsigned b)669 term_esc_rgb(fbuf* f, bool foreground, unsigned r, unsigned g, unsigned b){
670   // The correct way to do this is using tiparm+tputs, but doing so (at least
671   // as of terminfo 6.1.20191019) both emits ~3% more bytes for a run of 'rgb'
672   // and gives rise to some inaccurate colors (possibly due to special handling
673   // of values < 256; I'm not at this time sure). So we just cons up our own.
674   /*if(esc == 4){
675     return fbuf_emit(f, "setab", tiparm(nc->setab, (int)((r << 16u) | (g << 8u) | b)));
676   }else if(esc == 3){
677     return fbuf_emit(f, "setaf", tiparm(nc->setaf, (int)((r << 16u) | (g << 8u) | b)));
678   }else{
679     return -1;
680   }*/
681   #define RGBESC1 "\x1b" "["
682   // we'd like to use the proper ITU T.416 colon syntax i.e. "8:2::", but it is
683   // not supported by several terminal emulators :/.
684   #define RGBESC2 "8;2;"
685   // fprintf() was sitting atop our profiles, so we put the effort into a fast solution
686   // here. assemble a buffer using constants and a lookup table.
687   fbuf_putn(f, RGBESC1, strlen(RGBESC1));
688   fbuf_putc(f, foreground ? '3' : '4');
689   fbuf_putn(f, RGBESC2, strlen(RGBESC2));
690   const char* s = NUMBERS[r];
691   while(*s){
692     fbuf_putc(f, *s++);
693   }
694   s = NUMBERS[g];
695   while(*s){
696     fbuf_putc(f, *s++);
697   }
698   s = NUMBERS[b];
699   while(*s != ';'){
700     fbuf_putc(f, *s++);
701   }
702   fbuf_putc(f, 'm');
703   return 0;
704 }
705 
706 static inline int
term_bg_rgb8(const tinfo * ti,fbuf * f,unsigned r,unsigned g,unsigned b)707 term_bg_rgb8(const tinfo* ti, fbuf* f, unsigned r, unsigned g, unsigned b){
708   // We typically want to use tputs() and tiperm() to acquire and write the
709   // escapes, as these take into account terminal-specific delays, padding,
710   // etc. For the case of DirectColor, there is no suitable terminfo entry, but
711   // we're also in that case working with hopefully more robust terminals.
712   // If it doesn't work, eh, it doesn't work. Fuck the world; save yourself.
713   if(ti->caps.rgb){
714     if((ti->bg_collides_default & 0xff000000) == 0x01000000){
715       if((r == ncchannel_r(ti->bg_collides_default)) &&
716          (g == ncchannel_g(ti->bg_collides_default)) &&
717          (b == ncchannel_b(ti->bg_collides_default))){
718         // the human eye has fewer blue cones than red or green. toggle
719         // the last bit in the blue component to avoid a collision.
720         b ^= 0x00000001;
721       }
722     }
723     return term_esc_rgb(f, false, r, g, b);
724   }else{
725     const char* setab = get_escape(ti, ESCAPE_SETAB);
726     if(setab){
727       // For 256-color indexed mode, start constructing a palette based off
728       // the inputs *if we can change the palette*. If more than 256 are used on
729       // a single screen, start... combining close ones? For 8-color mode, simple
730       // interpolation. I have no idea what to do for 88 colors. FIXME
731       if(ti->caps.colors >= 256){
732         return fbuf_emit(f, tiparm(setab, rgb_quantize_256(r, g, b)));
733       }else if(ti->caps.colors >= 8){
734         return fbuf_emit(f, tiparm(setab, rgb_quantize_8(r, g, b)));
735       }
736     }
737   }
738   return 0;
739 }
740 
term_fg_rgb8(const tinfo * ti,fbuf * f,unsigned r,unsigned g,unsigned b)741 int term_fg_rgb8(const tinfo* ti, fbuf* f, unsigned r, unsigned g, unsigned b){
742   // We typically want to use tputs() and tiperm() to acquire and write the
743   // escapes, as these take into account terminal-specific delays, padding,
744   // etc. For the case of DirectColor, there is no suitable terminfo entry, but
745   // we're also in that case working with hopefully more robust terminals.
746   // If it doesn't work, eh, it doesn't work. Fuck the world; save yourself.
747   if(ti->caps.rgb){
748     return term_esc_rgb(f, true, r, g, b);
749   }else{
750     const char* setaf = get_escape(ti, ESCAPE_SETAF);
751     if(setaf){
752       // For 256-color indexed mode, start constructing a palette based off
753       // the inputs *if we can change the palette*. If more than 256 are used on
754       // a single screen, start... combining close ones? For 8-color mode, simple
755       // interpolation. I have no idea what to do for 88 colors. FIXME
756       if(ti->caps.colors >= 256){
757         return fbuf_emit(f, tiparm(setaf, rgb_quantize_256(r, g, b)));
758       }else if(ti->caps.colors >= 8){
759         return fbuf_emit(f, tiparm(setaf, rgb_quantize_8(r, g, b)));
760       }
761     }
762   }
763   return 0;
764 }
765 
766 static inline int
update_palette(notcurses * nc,fbuf * f)767 update_palette(notcurses* nc, fbuf* f){
768   if(nc->tcache.caps.can_change_colors){
769     const char* initc = get_escape(&nc->tcache, ESCAPE_INITC);
770     for(size_t damageidx = 0 ; damageidx < sizeof(nc->palette.chans) / sizeof(*nc->palette.chans) ; ++damageidx){
771       unsigned r, g, b;
772       if(nc->palette_damage[damageidx]){
773         nc->touched_palette = true;
774         ncchannel_rgb8(nc->palette.chans[damageidx], &r, &g, &b);
775         // Need convert RGB values [0..256) to [0..1000], ugh
776         // FIXME need handle HSL case also
777         r = r * 1000 / 255;
778         g = g * 1000 / 255;
779         b = b * 1000 / 255;
780         if(fbuf_emit(f, tiparm(initc, damageidx, r, g, b)) < 0){
781           return -1;
782         }
783         nc->palette_damage[damageidx] = false;
784       }
785     }
786   }
787   return 0;
788 }
789 
790 // at least one of the foreground and background are the default. emit the
791 // necessary return to default (if one is necessary), and update rstate.
792 static inline int
raster_defaults(notcurses * nc,bool fgdef,bool bgdef,fbuf * f)793 raster_defaults(notcurses* nc, bool fgdef, bool bgdef, fbuf* f){
794   const char* op = get_escape(&nc->tcache, ESCAPE_OP);
795   if(op == NULL){ // if we don't have op, we don't have fgop/bgop
796     return 0;
797   }
798   const char* fgop = get_escape(&nc->tcache, ESCAPE_FGOP);
799   const char* bgop = get_escape(&nc->tcache, ESCAPE_BGOP);
800   bool mustsetfg = fgdef && !nc->rstate.fgdefelidable;
801   bool mustsetbg = bgdef && !nc->rstate.bgdefelidable;
802   if(!mustsetfg && !mustsetbg){ // needn't emit anything
803     ++nc->stats.s.defaultelisions;
804     return 0;
805   }else if((mustsetfg && mustsetbg) || !fgop || !bgop){
806     if(fbuf_emit(f, op)){
807       return -1;
808     }
809     nc->rstate.fgdefelidable = true;
810     nc->rstate.bgdefelidable = true;
811     nc->rstate.fgelidable = false;
812     nc->rstate.bgelidable = false;
813     nc->rstate.fgpalelidable = false;
814     nc->rstate.bgpalelidable = false;
815   }else if(mustsetfg){ // if we reach here, we must have fgop
816     if(fbuf_emit(f, fgop)){
817       return -1;
818     }
819     nc->rstate.fgdefelidable = true;
820     nc->rstate.fgelidable = false;
821     nc->rstate.fgpalelidable = false;
822   }else{ // mustsetbg and !mustsetfg and bgop != NULL
823     if(fbuf_emit(f, bgop)){
824       return -1;
825     }
826     nc->rstate.bgdefelidable = true;
827     nc->rstate.bgelidable = false;
828     nc->rstate.bgpalelidable = false;
829   }
830   ++nc->stats.s.defaultemissions;
831   return 0;
832 }
833 
834 // these are unlikely, so we leave it uninlined
835 static int
emit_fg_palindex(notcurses * nc,fbuf * f,const nccell * srccell)836 emit_fg_palindex(notcurses* nc, fbuf* f, const nccell* srccell){
837   unsigned palfg = nccell_fg_palindex(srccell);
838   // we overload lastr for the palette index; both are 8 bits
839   if(nc->rstate.fgpalelidable && nc->rstate.lastr == palfg){
840     ++nc->stats.s.fgelisions;
841   }else{
842     if(term_fg_palindex(nc, f, palfg)){
843       return -1;
844     }
845     ++nc->stats.s.fgemissions;
846     nc->rstate.fgpalelidable = true;
847   }
848   nc->rstate.lastr = palfg;
849   nc->rstate.fgdefelidable = false;
850   nc->rstate.fgelidable = false;
851   return 0;
852 }
853 
854 static int
emit_bg_palindex(notcurses * nc,fbuf * f,const nccell * srccell)855 emit_bg_palindex(notcurses* nc, fbuf* f, const nccell* srccell){
856   unsigned palbg = nccell_bg_palindex(srccell);
857   if(nc->rstate.bgpalelidable && nc->rstate.lastbr == palbg){
858     ++nc->stats.s.bgelisions;
859   }else{
860     if(term_bg_palindex(nc, f, palbg)){
861       return -1;
862     }
863     ++nc->stats.s.bgemissions;
864     nc->rstate.bgpalelidable = true;
865   }
866   nc->rstate.lastr = palbg;
867   nc->rstate.bgdefelidable = false;
868   nc->rstate.bgelidable = false;
869   return 0;
870 }
871 
872 // this first phase of sprixel rasterization is responsible for:
873 //  1) invalidating all QUIESCENT sprixels if the pile has changed (because
874 //      it would have been destroyed when switching away from our pile).
875 //      for the same reason, invalidate all MOVE sprixels in this case.
876 //  2) damaging all cells under a HIDE sixel, so text phase 1 consumes it
877 //      (not necessary for kitty graphics)
878 //  3) damaging uncovered cells under a MOVE (not necessary for kitty)
879 //  4) drawing invalidated sixels and loading invalidated kitty graphics
880 //      (new kitty graphics are *not* yet made visible)
881 // by the end of this pass, all sixels are *complete*. all kitty graphics
882 // are loaded, but old kitty graphics remain visible, and new/updated kitty
883 // graphics are not yet visible, and they have not moved.
884 static int64_t
clean_sprixels(notcurses * nc,ncpile * p,fbuf * f,int scrolls)885 clean_sprixels(notcurses* nc, ncpile* p, fbuf* f, int scrolls){
886   sprixel* s;
887   sprixel** parent = &p->sprixelcache;
888   int64_t bytesemitted = 0;
889   while( (s = *parent) ){
890     loginfo("Phase 1 sprixel %u state %d loc %d/%d\n", s->id,
891             s->invalidated, s->n ? s->n->absy : -1, s->n ? s->n->absx : -1);
892     if(s->invalidated == SPRIXEL_QUIESCENT){
893       if(p != nc->last_pile){
894         s->invalidated = SPRIXEL_UNSEEN;
895       }
896     }else if(s->invalidated == SPRIXEL_HIDE){
897 //fprintf(stderr, "OUGHT HIDE %d [%dx%d] %p\n", s->id, s->dimy, s->dimx, s);
898       int r = sprite_scrub(nc, p, s);
899       if(r < 0){
900         return -1;
901       }else if(r > 0){
902         if( (*parent = s->next) ){
903           s->next->prev = s->prev;
904         }
905         sprixel_free(s);
906         // need to avoid the rest of the iteration, as s is dead
907         continue; // don't account as an elision
908       }
909     }
910     if(s->invalidated == SPRIXEL_INVALIDATED && nc->tcache.pixel_refresh){
911       nc->tcache.pixel_refresh(p, s);
912     }else if(s->invalidated == SPRIXEL_MOVED ||
913              s->invalidated == SPRIXEL_UNSEEN ||
914              s->invalidated == SPRIXEL_INVALIDATED){
915 //fprintf(stderr, "1 MOVING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, y + nc->margin_t, x + nc->margin_l, s->n);
916       if(s->invalidated == SPRIXEL_MOVED){
917         if(p != nc->last_pile){
918           s->invalidated = SPRIXEL_UNSEEN;
919         }else{
920           if(s->n->absx == s->movedfromx){
921             if(s->movedfromy - s->n->absy == scrolls){
922               s->invalidated = SPRIXEL_INVALIDATED;
923               continue;
924             }
925           }
926         }
927       }
928       // otherwise it's a new pile, so we couldn't have been on-screen
929       int r = sprite_redraw(nc, p, s, f, nc->margin_t, nc->margin_l);
930       if(r < 0){
931         return -1;
932       }
933       bytesemitted += r;
934       ++nc->stats.s.sprixelemissions;
935     }else{
936       ++nc->stats.s.sprixelelisions;
937     }
938     parent = &s->next;
939 //fprintf(stderr, "SPRIXEL STATE: %d\n", s->invalidated);
940   }
941   return bytesemitted;
942 }
943 
944 // scroll the lastframe data |rows| up, to reflect scrolling reality
945 static void
scroll_lastframe(notcurses * nc,unsigned rows)946 scroll_lastframe(notcurses* nc, unsigned rows){
947   // the top |rows| rows need be released (though not more than the actual
948   // number of rows!)
949   if(rows > nc->lfdimy){
950     rows = nc->lfdimy;
951   }
952   for(unsigned targy = 0 ; targy < rows ; ++targy){
953     for(unsigned targx = 0 ; targx < nc->lfdimx ; ++targx){
954       const size_t damageidx = targy * nc->lfdimx + targx;
955       nccell* c = &nc->lastframe[damageidx];
956       pool_release(&nc->pool, c);
957     }
958   }
959   // now for all rows subsequent, up through lfdimy - rows, move them back.
960   // if we scrolled all rows, we will not move anything (and we just
961   // released everything).
962   for(unsigned targy = 0 ; targy < nc->lfdimy - rows ; ++targy){
963     const size_t dstidx = targy * nc->lfdimx;
964     nccell* dst = &nc->lastframe[dstidx];
965     const size_t srcidx = dstidx + rows * nc->lfdimx;
966     const nccell* src = &nc->lastframe[srcidx];
967     memcpy(dst, src, sizeof(*dst) * nc->lfdimx);
968   }
969   // now for the last |rows| rows, initialize them to 0.
970   unsigned targy = nc->lfdimy - rows;
971   while(targy < nc->lfdimy){
972     const size_t dstidx = targy * nc->lfdimx;
973     nccell* dst = &nc->lastframe[dstidx];
974     memset(dst, 0, sizeof(*dst) * nc->lfdimx);
975     ++targy;
976   }
977 }
978 
979 // "%d tardies to work off, by far the most in the class!\n", p->scrolls
980 static int
rasterize_scrolls(const ncpile * p,fbuf * f)981 rasterize_scrolls(const ncpile* p, fbuf* f){
982   int scrolls = p->scrolls;
983   if(scrolls == 0){
984     return 0;
985   }
986   logdebug("order-%d scroll\n", scrolls);
987   /*if(p->nc->rstate.logendy >= 0){
988     p->nc->rstate.logendy -= scrolls;
989     if(p->nc->rstate.logendy < 0){
990       p->nc->rstate.logendy = 0;
991       p->nc->rstate.logendx = 0;
992     }
993   }*/
994   // FIXME probably need this to take place at the end of cycle...
995   if(p->nc->tcache.pixel_scroll){
996     p->nc->tcache.pixel_scroll(p, &p->nc->tcache, scrolls);
997   }
998   if(goto_location(p->nc, f, p->dimy, 0, NULL)){
999     return -1;
1000   }
1001   // terminals advertising 'bce' will scroll in the current background color;
1002   // switch back to the default explicitly.
1003   if(p->nc->tcache.bce){
1004     if(raster_defaults(p->nc, false, true, f)){
1005       return -1;
1006     }
1007   }
1008   return emit_scrolls_track(p->nc, scrolls, f);
1009 }
1010 
1011 // second sprixel pass in rasterization. by this time, all sixels are handled
1012 // (and in the QUIESCENT state); only persistent kitty graphics still require
1013 // operation. responsibilities of this second pass include:
1014 //
1015 // 1) if we're a different pile, issue the kitty universal clear
1016 // 2) first, hide all sprixels in the HIDE state
1017 // 3) then, make allo LOADED sprixels visible
1018 //
1019 // don't account for sprixelemissions here, as they were already counted.
1020 static int64_t
rasterize_sprixels(notcurses * nc,ncpile * p,fbuf * f)1021 rasterize_sprixels(notcurses* nc, ncpile* p, fbuf* f){
1022   int64_t bytesemitted = 0;
1023   sprixel* s;
1024   sprixel** parent = &p->sprixelcache;
1025   while( (s = *parent) ){
1026 //fprintf(stderr, "raster YARR HARR HARR SPIRXLE %u STATE %d\n", s->id, s->invalidated);
1027     if(s->invalidated == SPRIXEL_INVALIDATED){
1028 //fprintf(stderr, "3 DRAWING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, nc->margin_t, nc->margin_l, s->n);
1029       int r = sprite_draw(&nc->tcache, p, s, f, nc->margin_t, nc->margin_l);
1030       if(r < 0){
1031         return -1;
1032       }else if(r > 0){
1033         bytesemitted += r;
1034         nc->rstate.y = -1;
1035         nc->rstate.x = -1;
1036       }
1037     }else if(s->invalidated == SPRIXEL_LOADED){
1038       if(nc->tcache.pixel_commit){
1039         int y, x;
1040         ncplane_abs_yx(s->n, &y, &x);
1041         if(goto_location(nc, f, y + nc->margin_t, x + nc->margin_l, NULL)){
1042           return -1;
1043         }
1044         if(sprite_commit(&nc->tcache, f, s, false)){
1045           return -1;
1046         }
1047         nc->rstate.y = -1;
1048         nc->rstate.x = -1;
1049       }
1050     }else if(s->invalidated == SPRIXEL_HIDE){
1051       if(nc->tcache.pixel_remove){
1052         if(nc->tcache.pixel_remove(s->id, f) < 0){
1053           return -1;
1054         }
1055         if( (*parent = s->next) ){
1056           s->next->prev = s->prev;
1057         }
1058         sprixel_free(s);
1059         continue;
1060       }
1061     }
1062     parent = &s->next;
1063   }
1064   return bytesemitted;
1065 }
1066 
1067 // bitmap backends which don't use the bytestream (currently only fbcon)
1068 // need go at the very end, following writeout. pass again, invoking
1069 // pixel_draw_late if defined.
1070 static int64_t
rasterize_sprixels_post(notcurses * nc,ncpile * p)1071 rasterize_sprixels_post(notcurses* nc, ncpile* p){
1072   if(!nc->tcache.pixel_draw_late){
1073     return 0;
1074   }
1075   int64_t bytesemitted = 0;
1076   sprixel* s;
1077   sprixel** parent = &p->sprixelcache;
1078   while( (s = *parent) ){
1079 //fprintf(stderr, "YARR HARR HARR SPIRXLE %u STATE %d\n", s->id, s->invalidated);
1080     if(s->invalidated == SPRIXEL_INVALIDATED || s->invalidated == SPRIXEL_UNSEEN){
1081       int offy, offx;
1082       ncplane_abs_yx(s->n, &offy, &offx);
1083 //fprintf(stderr, "5 DRAWING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, nc->margin_t + offy, nc->margin_l + offx, s->n);
1084       int r = nc->tcache.pixel_draw_late(&nc->tcache, s, nc->margin_t + offy, nc->margin_l + offx);
1085       if(r < 0){
1086         return -1;
1087       }
1088       bytesemitted += r;
1089     }
1090     parent = &s->next;
1091   }
1092   return bytesemitted;
1093 }
1094 
1095 // Producing the frame requires three steps:
1096 //  * render -- build up a flat framebuffer from a set of ncplanes
1097 //  * rasterize -- build up a UTF-8/ASCII stream of escapes and EGCs
1098 //  * refresh -- write the stream to the emulator
1099 
1100 // Takes a rendered frame (a flat framebuffer, where each cell has the desired
1101 // EGC, attribute, and channels), which has been written to nc->lastframe, and
1102 // spits out an optimal sequence of terminal-appropriate escapes and EGCs. There
1103 // should be an rvec entry for each cell, but only the 'damaged' field is used.
1104 // lastframe has *not yet been written to the screen*, i.e. it's only about to
1105 // *become* the last frame rasterized.
1106 static int
rasterize_core(notcurses * nc,const ncpile * p,fbuf * f,unsigned phase)1107 rasterize_core(notcurses* nc, const ncpile* p, fbuf* f, unsigned phase){
1108   struct crender* rvec = p->crender;
1109   // we only need to emit a coordinate if it was damaged. the damagemap is a
1110   // bit per coordinate, one per struct crender.
1111   for(unsigned y = nc->margin_t; y < p->dimy + nc->margin_t ; ++y){
1112     const int innery = y - nc->margin_t;
1113     bool saw_linefeed = 0;
1114     for(unsigned x = nc->margin_l ; x < p->dimx + nc->margin_l ; ++x){
1115       const int innerx = x - nc->margin_l;
1116       const size_t damageidx = innery * nc->lfdimx + innerx;
1117       unsigned r, g, b, br, bg, bb;
1118       nccell* srccell = &nc->lastframe[damageidx];
1119       if(!rvec[damageidx].s.damaged){
1120         // no need to emit a cell; what we rendered appears to already be
1121         // here. no updates are performed to elision state nor lastframe.
1122         ++nc->stats.s.cellelisions;
1123         if(nccell_wide_left_p(srccell)){
1124           ++x;
1125         }
1126       }else if(phase != 0 || !rvec[damageidx].s.p_beats_sprixel){
1127 //fprintf(stderr, "phase %u damaged at %d/%d %d\n", phase, innery, innerx, x);
1128         // in the first text phase, we draw only those glyphs where the glyph
1129         // was not above a sprixel (and the cell is damaged). in the second
1130         // phase, we draw everything that remains damaged.
1131         ++nc->stats.s.cellemissions;
1132         if(goto_location(nc, f, y, x, rvec[damageidx].p)){
1133           return -1;
1134         }
1135         // set the style. this can change the color back to the default; if it
1136         // does, we need update our elision possibilities.
1137         if(term_setstyles(f, nc, srccell)){
1138           return -1;
1139         }
1140         // if our cell has a default foreground *or* background, we can elide
1141         // the default set iff one of:
1142         //  * we are a partial glyph, and the previous was default on both, or
1143         //  * we are a no-foreground glyph, and the previous was default background, or
1144         //  * we are a no-background glyph, and the previous was default foreground
1145         bool nobackground = nccell_nobackground_p(srccell);
1146         bool rgbequal = nccell_rgbequal_p(srccell);
1147         if((nccell_fg_default_p(srccell)) || (!nobackground && nccell_bg_default_p(srccell))){
1148           if(raster_defaults(nc, nccell_fg_default_p(srccell),
1149                              !nobackground && nccell_bg_default_p(srccell), f)){
1150             return -1;
1151           }
1152         }
1153         if(nccell_fg_palindex_p(srccell)){ // palette-indexed foreground
1154           if(emit_fg_palindex(nc, f, srccell)){
1155             return -1;
1156           }
1157         }else if(!nccell_fg_default_p(srccell)){ // rgb foreground
1158           // if our cell has a non-default foreground, we can elide the
1159           // non-default foreground set iff either:
1160           //  * the previous was non-default, and matches what we have now, or
1161           //  * we are a no-foreground glyph (iswspace() is true) FIXME
1162           nccell_fg_rgb8(srccell, &r, &g, &b);
1163           if(nc->rstate.fgelidable && nc->rstate.lastr == r && nc->rstate.lastg == g && nc->rstate.lastb == b){
1164             ++nc->stats.s.fgelisions;
1165           }else{
1166             if(!rgbequal){ // if rgbequal, no need to set fg
1167               if(term_fg_rgb8(&nc->tcache, f, r, g, b)){
1168                 return -1;
1169               }
1170               ++nc->stats.s.fgemissions;
1171               nc->rstate.fgelidable = true;
1172             }else{
1173               r = nc->rstate.lastr; g = nc->rstate.lastg; b = nc->rstate.lastb;
1174             }
1175           }
1176           nc->rstate.lastr = r; nc->rstate.lastg = g; nc->rstate.lastb = b;
1177           nc->rstate.fgdefelidable = false;
1178           nc->rstate.fgpalelidable = false;
1179         }
1180         if(nobackground){
1181           ++nc->stats.s.bgelisions;
1182         }else if(nccell_bg_palindex_p(srccell)){ // palette-indexed background
1183           if(emit_bg_palindex(nc, f, srccell)){
1184             return -1;
1185           }
1186         }else if(!nccell_bg_default_p(srccell)){ // rgb background
1187           // if our cell has a non-default background, we can elide the
1188           // non-default background set iff either:
1189           //  * we do not use the background, because the cell is all-foreground,
1190           //  * the previous was non-default, and matches what we have now, or
1191           nccell_bg_rgb8(srccell, &br, &bg, &bb);
1192           if(nc->rstate.bgelidable && nc->rstate.lastbr == br && nc->rstate.lastbg == bg && nc->rstate.lastbb == bb){
1193             ++nc->stats.s.bgelisions;
1194           }else{
1195             if(term_bg_rgb8(&nc->tcache, f, br, bg, bb)){
1196               return -1;
1197             }
1198             ++nc->stats.s.bgemissions;
1199             nc->rstate.bgelidable = true;
1200           }
1201           nc->rstate.lastbr = br; nc->rstate.lastbg = bg; nc->rstate.lastbb = bb;
1202           nc->rstate.bgdefelidable = false;
1203           nc->rstate.bgpalelidable = false;
1204           // FIXME see above -- if we don't have to change the fg, but do need
1205           // to change the bg, emit full block and no rgb at all
1206           if(rgbequal){
1207             // FIXME need one per column of original glyph
1208             pool_load_direct(&nc->pool, srccell, " ", 1, 1);
1209           }
1210         }
1211 //fprintf(stderr, "RAST %08x [%s] to %d/%d cols: %u %016" PRIx64 "\n", srccell->gcluster, pool_extended_gcluster(&nc->pool, srccell), y, x, srccell->width, srccell->channels);
1212         // this is used to invalidate the sprixel in the first text round,
1213         // which is only necessary for sixel, not kitty.
1214         if(rvec[damageidx].sprixel){
1215           sprixcell_e scstate = sprixel_state(rvec[damageidx].sprixel, y - nc->margin_t, x - nc->margin_l);
1216           if((scstate == SPRIXCELL_MIXED_SIXEL || scstate == SPRIXCELL_OPAQUE_SIXEL)
1217              && !rvec[damageidx].s.p_beats_sprixel){
1218 //fprintf(stderr, "INVALIDATING at %d/%d (%u)\n", y, x, rvec[damageidx].s.p_beats_sprixel);
1219             sprixel_invalidate(rvec[damageidx].sprixel, y, x);
1220           }
1221         }
1222         if(term_putc(f, &nc->pool, srccell)){
1223           return -1;
1224         }
1225         if(srccell->gcluster == '\n'){
1226           saw_linefeed = true;
1227         }
1228         rvec[damageidx].s.damaged = 0;
1229         rvec[damageidx].s.p_beats_sprixel = 0;
1230         nc->rstate.x += srccell->width;
1231         if(srccell->width){ // check only necessary when undamaged; be safe
1232           x += srccell->width - 1;
1233         }else{
1234           ++nc->rstate.x;
1235         }
1236         if((int)y > nc->rstate.logendy || ((int)y == nc->rstate.logendy && (int)x > nc->rstate.logendx)){
1237           if((int)y > nc->rstate.logendy){
1238 //fprintf(stderr, "**************8NATURAL PLACEMENT AT %u/ %u\n", y, x);
1239             nc->rstate.logendy = y;
1240             nc->rstate.logendx = 0;
1241           }
1242           if(x >= p->dimx + nc->margin_l - 1){
1243             if(nc->rstate.logendy < (int)(p->dimy + nc->margin_t - 1)){
1244               ++nc->rstate.logendy;
1245             }
1246             nc->rstate.logendx = 0;
1247             saw_linefeed = false;
1248 //fprintf(stderr, "**********8SCROLLING PLACEMENT AT %u %u\n", nc->rstate.logendy, nc->rstate.logendx);
1249           }else if((int)x >= nc->rstate.logendx){
1250             nc->rstate.logendx = x;
1251 //fprintf(stderr, "**********HORIZ PLACEMENT AT %u %u\n", nc->rstate.logendy, nc->rstate.logendx);
1252           }
1253         }
1254       }
1255 //fprintf(stderr, "damageidx: %ld\n", damageidx);
1256     }
1257     // shouldn't this happen only if the linefeed was on the last line? FIXME
1258     if(saw_linefeed){
1259       nc->rstate.logendx = 0;
1260     }
1261   }
1262   return 0;
1263 }
1264 
1265 // 'asu' on input is non-0 if application-synchronized updates are permitted
1266 // (they are not, for instance, when rendering to a non-tty). on output,
1267 // assuming success, it is non-0 if application-synchronized updates are
1268 // desired; in this case, a SUM footer is present at the end of the buffer.
1269 static int
notcurses_rasterize_inner(notcurses * nc,ncpile * p,fbuf * f,unsigned * asu)1270 notcurses_rasterize_inner(notcurses* nc, ncpile* p, fbuf* f, unsigned* asu){
1271   logdebug("pile %p ymax: %d xmax: %d\n", p, p->dimy + nc->margin_t, p->dimx + nc->margin_l);
1272   // don't write a clearscreen. we only update things that have been changed.
1273   // we explicitly move the cursor at the beginning of each output line, so no
1274   // need to home it expliticly.
1275   update_palette(nc, f);
1276   if(rasterize_scrolls(p, f)){
1277     return -1;
1278   }
1279   int scrolls = p->scrolls;
1280   p->scrolls = 0;
1281   logdebug("Sprixel phase 1\n");
1282   int64_t sprixelbytes = clean_sprixels(nc, p, f, scrolls);
1283   if(sprixelbytes < 0){
1284     return -1;
1285   }
1286   logdebug("Glyph phase 1\n");
1287   if(rasterize_core(nc, p, f, 0)){
1288     return -1;
1289   }
1290   logdebug("Sprixel phase 2\n");
1291   int64_t rasprixelbytes = rasterize_sprixels(nc, p, f);
1292   if(rasprixelbytes < 0){
1293     return -1;
1294   }
1295   sprixelbytes += rasprixelbytes;
1296   pthread_mutex_lock(&nc->stats.lock);
1297     nc->stats.s.sprixelbytes += sprixelbytes;
1298   pthread_mutex_unlock(&nc->stats.lock);
1299   logdebug("Glyph phase 2\n");
1300   if(rasterize_core(nc, p, f, 1)){
1301     return -1;
1302   }
1303 #define MIN_SUMODE_SIZE BUFSIZ
1304   if(*asu){
1305     if(nc->rstate.f.used >= MIN_SUMODE_SIZE){
1306       const char* endasu = get_escape(&nc->tcache, ESCAPE_ESUM);
1307       if(endasu){
1308         if(fbuf_puts(f, endasu) < 0){
1309           *asu = 0;
1310         }
1311       }else{
1312         *asu = 0;
1313       }
1314     }else{
1315       *asu = 0;
1316     }
1317   }
1318 #undef MIN_SUMODE_SIZE
1319   return nc->rstate.f.used;
1320 }
1321 
1322 // rasterize the rendered frame, and blockingly write it out to the terminal.
1323 static int
raster_and_write(notcurses * nc,ncpile * p,fbuf * f)1324 raster_and_write(notcurses* nc, ncpile* p, fbuf* f){
1325   fbuf_reset(f);
1326   // will we be using application-synchronized updates? if this comes back as
1327   // non-zero, we are, and must emit the header. no SUM without a tty, and we
1328   // can't have the escape without being connected to one...
1329   const char* basu = get_escape(&nc->tcache, ESCAPE_BSUM);
1330   unsigned useasu = basu ? 1 : 0;
1331   // if we have SUM support, emit a BSU speculatively. if we do so, but don't
1332   // actually use an ESU, this BSUM must be skipped on write.
1333   if(useasu){
1334     if(fbuf_puts(f, basu) < 0){
1335       return -1;
1336     }
1337   }
1338   if(notcurses_rasterize_inner(nc, p, f, &useasu) < 0){
1339     return -1;
1340   }
1341   // if we loaded a BSU into the front, but don't actually want to use it,
1342   // we start printing after the BSU.
1343   size_t moffset = 0;
1344   if(basu){
1345     if(useasu){
1346       ++nc->stats.s.appsync_updates;
1347     }else{
1348       moffset = strlen(basu);
1349     }
1350   }
1351   int ret = 0;
1352   sigset_t oldmask;
1353   block_signals(&oldmask);
1354   if(blocking_write(fileno(nc->ttyfp), nc->rstate.f.buf + moffset,
1355                     nc->rstate.f.used - moffset)){
1356     ret = -1;
1357   }
1358   unblock_signals(&oldmask);
1359   rasterize_sprixels_post(nc, p);
1360 //fprintf(stderr, "%lu/%lu %lu/%lu %lu/%lu %d\n", nc->stats.defaultelisions, nc->stats.defaultemissions, nc->stats.fgelisions, nc->stats.fgemissions, nc->stats.bgelisions, nc->stats.bgemissions, ret);
1361   if(ret < 0){
1362     return ret;
1363   }
1364   return nc->rstate.f.used;
1365 }
1366 
1367 // if the cursor is enabled, store its location and disable it. then, once done
1368 // rasterizing, enable it afresh, moving it to the stored location. if left on
1369 // during rasterization, we'll get grotesque flicker. 'out' is a memstream
1370 // used to collect a buffer.
1371 static inline int
notcurses_rasterize(notcurses * nc,ncpile * p,fbuf * f)1372 notcurses_rasterize(notcurses* nc, ncpile* p, fbuf* f){
1373   const int cursory = nc->cursory;
1374   const int cursorx = nc->cursorx;
1375   if(cursory >= 0){ // either both are good, or neither is
1376     notcurses_cursor_disable(nc);
1377   }
1378   int ret = raster_and_write(nc, p, f);
1379   fbuf_reset(f);
1380   if(cursory >= 0){
1381     notcurses_cursor_enable(nc, cursory, cursorx);
1382   }else if(nc->rstate.logendy >= 0){
1383     goto_location(nc, f, nc->rstate.logendy, nc->rstate.logendx, nc->rstate.lastsrcp);
1384     if(fbuf_flush(f, nc->ttyfp)){
1385       ret = -1;
1386     }
1387   }
1388   nc->last_pile = p;
1389   return ret;
1390 }
1391 
1392 // get the cursor to the upper-left corner by one means or another, clearing
1393 // the screen while doing so.
clear_and_home(notcurses * nc,tinfo * ti,fbuf * f)1394 int clear_and_home(notcurses* nc, tinfo* ti, fbuf* f){
1395   // clear clears the screen and homes the cursor by itself
1396   const char* clearscr = get_escape(ti, ESCAPE_CLEAR);
1397   if(clearscr){
1398     if(fbuf_emit(f, clearscr) == 0){
1399       nc->rstate.x = nc->rstate.y = 0;
1400       return 0;
1401     }
1402   }
1403   if(emit_scrolls_track(nc, ncplane_dim_y(notcurses_stdplane_const(nc)), f)){
1404     return -1;
1405   }
1406   if(goto_location(nc, f, 0, 0, NULL)){
1407     return -1;
1408   }
1409   return 0;
1410 }
1411 
notcurses_refresh(notcurses * nc,unsigned * restrict dimy,unsigned * restrict dimx)1412 int notcurses_refresh(notcurses* nc, unsigned* restrict dimy, unsigned* restrict dimx){
1413   if(notcurses_resize(nc, dimy, dimx)){
1414     return -1;
1415   }
1416   fbuf_reset(&nc->rstate.f);
1417   if(clear_and_home(nc, &nc->tcache, &nc->rstate.f)){
1418     return -1;
1419   }
1420   if(fbuf_flush(&nc->rstate.f, nc->ttyfp)){
1421     return -1;
1422   }
1423   if(nc->lfdimx == 0 || nc->lfdimy == 0){
1424     return 0;
1425   }
1426   ncpile p = {};
1427   p.dimy = nc->lfdimy;
1428   p.dimx = nc->lfdimx;
1429   const int count = p.dimy * p.dimx;
1430   p.crender = malloc(count * sizeof(*p.crender));
1431   if(p.crender == NULL){
1432     return -1;
1433   }
1434   init_rvec(p.crender, count);
1435   for(int i = 0 ; i < count ; ++i){
1436     p.crender[i].s.damaged = 1;
1437   }
1438   int ret = notcurses_rasterize(nc, &p, &nc->rstate.f);
1439   free(p.crender);
1440   if(ret < 0){
1441     return -1;
1442   }
1443   ++nc->stats.s.refreshes;
1444   return 0;
1445 }
1446 
ncpile_render_to_file(ncplane * n,FILE * fp)1447 int ncpile_render_to_file(ncplane* n, FILE* fp){
1448   notcurses* nc = ncplane_notcurses(n);
1449   ncpile* p = ncplane_pile(n);
1450   if(nc->lfdimx == 0 || nc->lfdimy == 0){
1451     return 0;
1452   }
1453   fbuf f = {};
1454   if(fbuf_init(&f)){
1455     return -1;
1456   }
1457   const unsigned count = (nc->lfdimx > p->dimx ? nc->lfdimx : p->dimx) *
1458                          (nc->lfdimy > p->dimy ? nc->lfdimy : p->dimy);
1459   p->crender = malloc(count * sizeof(*p->crender));
1460   if(p->crender == NULL){
1461     fbuf_free(&f);
1462     return -1;
1463   }
1464   init_rvec(p->crender, count);
1465   for(unsigned i = 0 ; i < count ; ++i){
1466     p->crender[i].s.damaged = 1;
1467   }
1468   int ret = raster_and_write(nc, p, &f);
1469   free(p->crender);
1470   if(ret > 0){
1471     if(fwrite(f.buf, f.used, 1, fp) == 1){
1472       ret = 0;
1473     }else{
1474       ret = -1;
1475     }
1476   }
1477   fbuf_free(&f);
1478   return ret;
1479 }
1480 
1481 // We execute the painter's algorithm, starting from our topmost plane. The
1482 // damagevector should be all zeros on input. On success, it will reflect
1483 // which cells were changed. We solve for each coordinate's cell by walking
1484 // down the z-buffer, looking at intersections with ncplanes. This implies
1485 // locking down the EGC, the attributes, and the channels for each cell.
1486 // if |pgeo_changed|, the cell-pixel geometry for the pile has changed
1487 // since the last render, and thus all sprixels need be rescaled.
1488 static void
ncpile_render_internal(ncplane * n,struct crender * rvec,int leny,int lenx,unsigned pgeo_changed)1489 ncpile_render_internal(ncplane* n, struct crender* rvec, int leny, int lenx,
1490                        unsigned pgeo_changed){
1491 //fprintf(stderr, "rendering %dx%d\n", leny, lenx);
1492   ncpile* np = ncplane_pile(n);
1493   ncplane* p = np->top;
1494   sprixel* sprixel_list = NULL;
1495   while(p){
1496     paint(p, rvec, leny, lenx, 0, 0, &sprixel_list, pgeo_changed);
1497     p = p->below;
1498   }
1499   if(sprixel_list){
1500     if(np->sprixelcache){
1501       sprixel* s = sprixel_list;
1502       while(s->next){
1503         s = s->next;
1504       }
1505       if( (s->next = np->sprixelcache) ){
1506         np->sprixelcache->prev = s;
1507       }
1508     }
1509     np->sprixelcache = sprixel_list;
1510   }
1511 }
1512 
ncpile_rasterize(ncplane * n)1513 int ncpile_rasterize(ncplane* n){
1514   if(sigcont_seen_for_render){
1515     sigcont_seen_for_render = 0;
1516     notcurses_refresh(ncplane_notcurses(n), NULL, NULL);
1517   }
1518   struct timespec start, rasterdone, writedone;
1519   clock_gettime(CLOCK_MONOTONIC, &start);
1520   ncpile* pile = ncplane_pile(n);
1521   struct notcurses* nc = ncplane_notcurses(n);
1522   const int miny = pile->dimy < nc->lfdimy ? pile->dimy : nc->lfdimy;
1523   const int minx = pile->dimx < nc->lfdimx ? pile->dimx : nc->lfdimx;
1524   const struct tinfo* ti = &ncplane_notcurses_const(n)->tcache;
1525   postpaint(nc, ti, nc->lastframe, miny, minx, pile->crender, &nc->pool);
1526   clock_gettime(CLOCK_MONOTONIC, &rasterdone);
1527   int bytes = notcurses_rasterize(nc, pile, &nc->rstate.f);
1528   // accepts -1 as an indication of failure
1529   clock_gettime(CLOCK_MONOTONIC, &writedone);
1530   pthread_mutex_lock(&nc->stats.lock);
1531     update_raster_bytes(&nc->stats.s, bytes);
1532     update_raster_stats(&rasterdone, &start, &nc->stats.s);
1533     update_write_stats(&writedone, &rasterdone, &nc->stats.s, bytes);
1534   pthread_mutex_unlock(&nc->stats.lock);
1535   if(bytes < 0){
1536     return -1;
1537   }
1538   return 0;
1539 }
1540 
1541 // ensure the crender vector of 'n' is properly sized for 'n'->dimy x 'n'->dimx,
1542 // and initialize the rvec afresh for a new render.
1543 static int
engorge_crender_vector(ncpile * n)1544 engorge_crender_vector(ncpile* n){
1545   if(n->dimy <= 0 || n->dimx <= 0){
1546     return -1;
1547   }
1548   const size_t crenderlen = n->dimy * n->dimx; // desired size
1549 //fprintf(stderr, "crlen: %d y: %d x:%d\n", crenderlen, dimy, dimx);
1550   if(crenderlen != n->crenderlen){
1551     loginfo("Resizing rvec (%lu) for %p to %lu\n", (long unsigned)n->crenderlen, n, (long unsigned)crenderlen);
1552     struct crender* tmp = realloc(n->crender, sizeof(*tmp) * crenderlen);
1553     if(tmp == NULL){
1554       return -1;
1555     }
1556     n->crender = tmp;
1557     n->crenderlen = crenderlen;
1558   }
1559   init_rvec(n->crender, crenderlen);
1560   return 0;
1561 }
1562 
ncpile_render(ncplane * n)1563 int ncpile_render(ncplane* n){
1564   scroll_lastframe(ncplane_notcurses(n), ncplane_pile(n)->scrolls);
1565   struct timespec start, renderdone;
1566   clock_gettime(CLOCK_MONOTONIC, &start);
1567   notcurses* nc = ncplane_notcurses(n);
1568   ncpile* pile = ncplane_pile(n);
1569   // update our notion of screen geometry, and render against that
1570   unsigned pgeo_changed = 0;
1571   notcurses_resize_internal(n, NULL, NULL);
1572   if(pile->cellpxy != nc->tcache.cellpxy || pile->cellpxx != nc->tcache.cellpxx){
1573     pile->cellpxy = nc->tcache.cellpxy;
1574     pile->cellpxx = nc->tcache.cellpxx;
1575     pgeo_changed = 1;
1576   }
1577   if(engorge_crender_vector(pile)){
1578     return -1;
1579   }
1580   ncpile_render_internal(n, pile->crender, pile->dimy, pile->dimx, pgeo_changed);
1581   clock_gettime(CLOCK_MONOTONIC, &renderdone);
1582   pthread_mutex_lock(&nc->stats.lock);
1583     update_render_stats(&renderdone, &start, &nc->stats.s);
1584   pthread_mutex_unlock(&nc->stats.lock);
1585   return 0;
1586 }
1587 
1588 // run the top half of notcurses_render(), and steal the buffer from rstate.
ncpile_render_to_buffer(ncplane * p,char ** buf,size_t * buflen)1589 int ncpile_render_to_buffer(ncplane* p, char** buf, size_t* buflen){
1590   if(ncpile_render(p)){
1591     return -1;
1592   }
1593   notcurses* nc = ncplane_notcurses(p);
1594   unsigned useasu = false; // no SUM with file
1595   fbuf_reset(&nc->rstate.f);
1596   int bytes = notcurses_rasterize_inner(nc, ncplane_pile(p), &nc->rstate.f, &useasu);
1597   pthread_mutex_lock(&nc->stats.lock);
1598     update_raster_bytes(&nc->stats.s, bytes);
1599   pthread_mutex_unlock(&nc->stats.lock);
1600   if(bytes < 0){
1601     return -1;
1602   }
1603   *buf = nc->rstate.f.buf;
1604   *buflen = nc->rstate.f.used;
1605   fbuf_reset(&nc->rstate.f);
1606   return 0;
1607 }
1608 
1609 // copy the UTF8-encoded EGC out of the cell, whether simple or complex. the
1610 // result is not tied to the ncplane, and persists across erases / destruction.
1611 static inline char*
pool_egc_copy(const egcpool * e,const nccell * c)1612 pool_egc_copy(const egcpool* e, const nccell* c){
1613   if(cell_simple_p(c)){
1614     return strdup((const char*)&c->gcluster);
1615   }
1616   return strdup(egcpool_extended_gcluster(e, c));
1617 }
1618 
notcurses_at_yx(notcurses * nc,unsigned yoff,unsigned xoff,uint16_t * stylemask,uint64_t * channels)1619 char* notcurses_at_yx(notcurses* nc, unsigned yoff, unsigned xoff, uint16_t* stylemask, uint64_t* channels){
1620   if(nc->lastframe == NULL){
1621     logerror("haven't yet rendered\n");
1622     return NULL;
1623   }
1624   if(yoff >= nc->lfdimy){
1625     logerror("invalid coordinates: %u/%u\n", yoff, xoff);
1626     return NULL;
1627   }
1628   if(xoff >= nc->lfdimx){
1629     logerror("invalid coordinates: %u/%u\n", yoff, xoff);
1630     return NULL;
1631   }
1632   const nccell* srccell = &nc->lastframe[yoff * nc->lfdimx + xoff];
1633   if(nccell_wide_right_p(srccell)){
1634     return notcurses_at_yx(nc, yoff, xoff - 1, stylemask, channels);
1635   }
1636   if(stylemask){
1637     *stylemask = srccell->stylemask;
1638   }
1639   if(channels){
1640     *channels = srccell->channels;
1641   }
1642 //fprintf(stderr, "COPYING: %d from %p\n", srccell->gcluster, &nc->pool);
1643   return pool_egc_copy(&nc->pool, srccell);
1644 }
1645 
ncdirect_set_bg_rgb_f(ncdirect * nc,unsigned rgb,fbuf * f)1646 int ncdirect_set_bg_rgb_f(ncdirect* nc, unsigned rgb, fbuf* f){
1647   if(rgb > 0xffffffu){
1648     return -1;
1649   }
1650   if(!ncdirect_bg_default_p(nc) && !ncdirect_bg_palindex_p(nc)
1651      && ncchannels_bg_rgb(nc->channels) == rgb){
1652     return 0;
1653   }
1654   if(term_bg_rgb8(&nc->tcache, f, (rgb & 0xff0000u) >> 16u, (rgb & 0xff00u) >> 8u, rgb & 0xffu)){
1655     return -1;
1656   }
1657   ncchannels_set_bg_rgb(&nc->channels, rgb);
1658   return 0;
1659 }
1660 
ncdirect_set_bg_rgb(ncdirect * nc,unsigned rgb)1661 int ncdirect_set_bg_rgb(ncdirect* nc, unsigned rgb){
1662   fbuf f = {};
1663   if(fbuf_init_small(&f)){
1664     return -1;
1665   }
1666   if(ncdirect_set_bg_rgb_f(nc, rgb, &f)){
1667     fbuf_free(&f);
1668     return -1;
1669   }
1670   if(fbuf_finalize(&f, nc->ttyfp) < 0){
1671     return -1;
1672   }
1673   return 0;
1674 }
1675 
ncdirect_set_fg_rgb_f(ncdirect * nc,unsigned rgb,fbuf * f)1676 int ncdirect_set_fg_rgb_f(ncdirect* nc, unsigned rgb, fbuf* f){
1677   if(rgb > 0xffffffu){
1678     return -1;
1679   }
1680   if(!ncdirect_fg_default_p(nc) && !ncdirect_fg_palindex_p(nc)
1681      && ncchannels_fg_rgb(nc->channels) == rgb){
1682     return 0;
1683   }
1684   if(term_fg_rgb8(&nc->tcache, f, (rgb & 0xff0000u) >> 16u, (rgb & 0xff00u) >> 8u, rgb & 0xffu)){
1685     return -1;
1686   }
1687   ncchannels_set_fg_rgb(&nc->channels, rgb);
1688   return 0;
1689 }
1690 
ncdirect_set_fg_rgb(ncdirect * nc,unsigned rgb)1691 int ncdirect_set_fg_rgb(ncdirect* nc, unsigned rgb){
1692   fbuf f = {};
1693   if(fbuf_init_small(&f)){
1694     return -1;
1695   }
1696   if(ncdirect_set_fg_rgb_f(nc, rgb, &f)){
1697     fbuf_free(&f);
1698     return -1;
1699   }
1700   if(fbuf_finalize(&f, nc->ttyfp) < 0){
1701     return -1;
1702   }
1703   return 0;
1704 }
1705 
notcurses_default_foreground(const struct notcurses * nc,uint32_t * fg)1706 int notcurses_default_foreground(const struct notcurses* nc, uint32_t* fg){
1707   const tinfo* ti = &nc->tcache;
1708   if(ti->fg_default & 0x80000000){
1709     logerror("default foreground could not be determined\n");
1710     return -1;
1711   }
1712   *fg = ti->fg_default & NC_BG_RGB_MASK;
1713   return 0;
1714 }
1715 
notcurses_default_background(const struct notcurses * nc,uint32_t * bg)1716 int notcurses_default_background(const struct notcurses* nc, uint32_t* bg){
1717   const tinfo* ti = &nc->tcache;
1718   if(ti->bg_collides_default & 0x80000000){
1719     logerror("default background could not be determined\n");
1720     return -1;
1721   }
1722   *bg = ti->bg_collides_default & NC_BG_RGB_MASK;
1723   return 0;
1724 }
1725 
notcurses_cursor_yx(const notcurses * nc,int * y,int * x)1726 int notcurses_cursor_yx(const notcurses* nc, int* y, int* x){
1727   *y = nc->rstate.y;
1728   *x = nc->rstate.x;
1729   return 0;
1730 }
1731 
notcurses_cursor_enable(notcurses * nc,int y,int x)1732 int notcurses_cursor_enable(notcurses* nc, int y, int x){
1733   if(y < 0 || x < 0){
1734     logerror("Illegal cursor placement: %d, %d\n", y, x);
1735     return -1;
1736   }
1737   // if we're already at the demanded location, we must already be visible, and
1738   // we needn't move the cursor -- return success immediately.
1739   if(nc->cursory == y && nc->cursorx == x){
1740     return 0;
1741   }
1742   fbuf f = {};
1743   if(fbuf_init_small(&f)){
1744     return -1;
1745   }
1746   // updates nc->rstate.cursor{y,x}
1747   if(goto_location(nc, &f, y + nc->margin_t, x + nc->margin_l, nc->rstate.lastsrcp)){
1748     fbuf_free(&f);
1749     return -1;
1750   }
1751   if(nc->cursory < 0){ // we weren't visible before, need cnorm
1752     const char* cnorm = get_escape(&nc->tcache, ESCAPE_CNORM);
1753     if(!cnorm || fbuf_emit(&f, cnorm)){
1754       fbuf_free(&f);
1755       return -1;
1756     }
1757   }
1758   if(fbuf_finalize(&f, nc->ttyfp)){
1759     return -1;
1760   }
1761   nc->cursory = y;
1762   nc->cursorx = x;
1763   return 0;
1764 }
1765 
notcurses_cursor_disable(notcurses * nc)1766 int notcurses_cursor_disable(notcurses* nc){
1767   if(nc->cursorx < 0 || nc->cursory < 0){
1768     logerror("Cursor is not enabled\n");
1769     return -1;
1770   }
1771   const char* cinvis = get_escape(&nc->tcache, ESCAPE_CIVIS);
1772   if(cinvis){
1773     if(!tty_emit(cinvis, nc->tcache.ttyfd) && !ncflush(nc->ttyfp)){
1774       nc->cursory = -1;
1775       nc->cursorx = -1;
1776       return 0;
1777     }
1778   }
1779   return -1;
1780 }
1781