diff --git a/NEWS.md b/NEWS.md index 022f1af8f..0712f9ebd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,9 @@ This document attempts to list user-visible changes and any major internal rearrangements of Notcurses. +* 1.6.1 (not yet released) + * Added `notcurses_render_file()` to dump last rendered frame to a `FILE*`. + * 1.6.0 (2020-07-04) * Behavior has changed regarding use of the provided `FILE*` (which, when `NULL`, is assumed to be `stdout`). Both Notcurses and `ncdirect` now diff --git a/USAGE.md b/USAGE.md index f6995e69c..c1b5948cb 100644 --- a/USAGE.md +++ b/USAGE.md @@ -170,6 +170,10 @@ updated to reflect the changes: // successful call to notcurses_render(). int notcurses_render(struct notcurses* nc); +// Write the last rendered frame, in its entirety, to 'fp'. This is not valid +// until notcurses_render() has been successfully called at least once. +int notcurses_render_to_file(struct notcurses* nc, FILE* fp); + // Retrieve the contents of the specified cell as last rendered. The EGC is // returned, or NULL on error. This EGC must be free()d by the caller. The // attrword and channels are written to 'attrword' and 'channels', respectively. diff --git a/doc/man/man3/notcurses_render.3.md b/doc/man/man3/notcurses_render.3.md index e8f5ee7c6..cd0ff7944 100644 --- a/doc/man/man3/notcurses_render.3.md +++ b/doc/man/man3/notcurses_render.3.md @@ -14,6 +14,8 @@ notcurses_render - sync the physical display to the virtual ncplanes **char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff, uint32_t* attrword, uint64_t* channels);** +**int notcurses_render_to_file(struct notcurses* nc, FILE* fp);** + # DESCRIPTION **notcurses_render** syncs the physical display to the context's prepared @@ -25,7 +27,7 @@ render (see notcurses_stats(3)), and screen geometry is refreshed (similarly to While **notcurses_render** is called, you **must not call any other functions on the same notcurses context**, with the one exception of **notcurses_getc** -(and its input-related helpers). +(and its input-related helpers; see **notcurses_input(3)**.). A render operation consists of two logical phases: generation of the rendered scene, and blitting this scene to the terminal (these two phases might actually @@ -86,8 +88,9 @@ purposes of color blending. **notcurses(3)**, **notcurses_cell(3)**, -**notcurses_plane(3)**, +**notcurses_input(3)**, **notcurses_output(3)**, +**notcurses_plane(3)**, **notcurses_refresh(3)**, **notcurses_stats(3)**, **console_codes(4)**, diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index 7de08bc34..b72c20ba2 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -993,6 +993,10 @@ API int notcurses_stop(struct notcurses* nc); // successful call to notcurses_render(). API int notcurses_render(struct notcurses* nc); +// Write the last rendered frame, in its entirety, to 'fp'. This is not valid +// until notcurses_render() has been successfully called at least once. +API int notcurses_render_to_file(struct notcurses* nc, FILE* fp); + // Return the topmost ncplane, of which there is always at least one. API struct ncplane* notcurses_top(struct notcurses* n); diff --git a/python/src/notcurses/build_notcurses.py b/python/src/notcurses/build_notcurses.py index 792e6c2ef..106abc929 100644 --- a/python/src/notcurses/build_notcurses.py +++ b/python/src/notcurses/build_notcurses.py @@ -85,6 +85,7 @@ struct notcurses* notcurses_init(const notcurses_options*, FILE*); int notcurses_lex_margins(const char* op, notcurses_options* opts); int notcurses_stop(struct notcurses*); int notcurses_render(struct notcurses*); +int notcurses_render_to_file(struct notcurses* nc, FILE* fp); struct ncplane* notcurses_stdplane(struct notcurses*); const struct ncplane* notcurses_stdplane_const(const struct notcurses* nc); int ncplane_set_base_cell(struct ncplane* ncp, const cell* c); diff --git a/src/demo/mojibake.c b/src/demo/mojibake.c index b6acf2cec..6e40975ab 100644 --- a/src/demo/mojibake.c +++ b/src/demo/mojibake.c @@ -35,14 +35,14 @@ unicode1emoji1(struct ncplane* title, int y){ if(n == NULL){ return NULL; } - ncplane_putstr_yx(n, 1, 1, "\u2764 (\u2764\ufe0f) \u2709 (\u2709\ufe0f)" - "\u270f (\u270f\ufe0f) \u2712 (\u2712\ufe0f)" - "\u2195 (\u2195\ufe0f) \u2194 (\u2194\ufe0f)" - "\u2716 (\u2716\ufe0f) \u2733 (\u2733\ufe0f)" - "\u2734 (\u2734\ufe0f) \u2747 (\u2747\ufe0f)"); - ncplane_putstr_yx(n, 2, 1, "\u2660 (\u2660\ufe0f) \u2665 (\u2665\ufe0f)" - "\u2666 (\u2666\ufe0f) \u2663 (\u2663\ufe0f)" - "\u260e (\u260e\ufe0f) \u27a1 (\u27a1\ufe0f)"); + ncplane_putstr_yx(n, 1, 1, "\u2764 \u2764\ufe0f \u2709 \u2709\ufe0f" + "\u270f \u270f\ufe0f \u2712 \u2712\ufe0f" + "\u2195 \u2195\ufe0f \u2194 \u2194\ufe0f" + "\u2716 \u2716\ufe0f \u2733 \u2733\ufe0f" + "\u2734 \u2734\ufe0f \u2747 \u2747\ufe0f"); + ncplane_putstr_yx(n, 2, 1, "\u2660 \u2660\ufe0f \u2665 \u2665\ufe0f" + "\u2666 \u2666\ufe0f \u2663 \u2663\ufe0f" + "\u260e \u260e\ufe0f \u27a1 \u27a1\ufe0f"); return n; } @@ -54,20 +54,20 @@ unicode52(struct ncplane* title, int y){ if(n == NULL){ return NULL; } - ncplane_putstr_yx(n, 1, 1, "\u26ea (\u26ea\ufe0f) \u26f2 (\u26f2\ufe0f)" - "\u26fa (\u26fa\ufe0f) \u2668 (\u2668\ufe0f)" - "\u26fd (\u26fd\ufe0f) \u2693 (\u2693\ufe0f)" - "\u26f5 (\u26f5\ufe0f) \u2600 (\u2600\ufe0f)"); - ncplane_putstr_yx(n, 2, 1, "\u26c5 (\u26c5\ufe0f) \u2614 (\u2614\ufe0f)" - "\u26a1 (\u26a1\ufe0f) \u26c4 (\u26c4\ufe0f)" - "\u26be (\u26b3\ufe0f) \u26d4 (\u26d4\ufe0f)" - "\u2b06 (\u2b06\ufe0f) \u2b07 (\u2b07\ufe0f)"); - ncplane_putstr_yx(n, 3, 1, "\u2b05 (\u2b05\ufe0f) \u26ce (\u26ce\ufe0f)" - "\u203c (\u203c\ufe0f) \u2049 (\u2049\ufe0f)" - "\xf0\x9f\x85\xbf (\xf0\x9f\x85\xbf\ufe0f)" - "\xf0\x9f\x88\xaf (\xf0\x9f\x88\xaf\ufe0f)" - "\xf0\x9f\x88\x9a (\xf0\x9f\x88\x9a\ufe0f)" - "\u3299 (\u3299\ufe0f)"); + ncplane_putstr_yx(n, 1, 1, "\u26ea \u26ea\ufe0f \u26f2 \u26f2\ufe0f" + "\u26fa \u26fa\ufe0f \u2668 \u2668\ufe0f" + "\u26fd \u26fd\ufe0f \u2693 \u2693\ufe0f" + "\u26f5 \u26f5\ufe0f \u2600 \u2600\ufe0f"); + ncplane_putstr_yx(n, 2, 1, "\u26c5 \u26c5\ufe0f \u2614 \u2614\ufe0f" + "\u26a1 \u26a1\ufe0f \u26c4 \u26c4\ufe0f" + "\u26be \u26b3\ufe0f \u26d4 \u26d4\ufe0f" + "\u2b06 \u2b06\ufe0f \u2b07 \u2b07\ufe0f"); + ncplane_putstr_yx(n, 3, 1, "\u2b05 \u2b05\ufe0f \u26ce \u26ce\ufe0f" + "\u203c \u203c\ufe0f \u2049 \u2049\ufe0f" + "\xf0\x9f\x85\xbf \xf0\x9f\x85\xbf\ufe0f" + "\xf0\x9f\x88\xaf \xf0\x9f\x88\xaf\ufe0f" + "\xf0\x9f\x88\x9a \xf0\x9f\x88\x9a\ufe0f" + "\u3299 \u3299\ufe0f"); return n; } diff --git a/src/lib/render.c b/src/lib/render.c index ac421be3e..7d818fffc 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -865,8 +865,7 @@ stage_cursor(notcurses* nc, FILE* out, int y, int x){ // lastframe has *not yet been written to the screen*, i.e. it's only about to // *become* the last frame rasterized. static int -notcurses_rasterize(notcurses* nc, const struct crender* rvec){ - FILE* out = nc->rstate.mstreamfp; +notcurses_rasterize(notcurses* nc, const struct crender* rvec, FILE* out){ int ret = 0; int y, x; fseeko(out, 0, SEEK_SET); @@ -1078,7 +1077,7 @@ int notcurses_refresh(notcurses* nc, int* restrict dimy, int* restrict dimx){ for(int i = 0 ; i < count ; ++i){ rvec[i].damaged = true; } - int ret = notcurses_rasterize(nc, rvec); + int ret = notcurses_rasterize(nc, rvec, nc->rstate.mstreamfp); free(rvec); if(ret < 0){ return -1; @@ -1086,6 +1085,43 @@ int notcurses_refresh(notcurses* nc, int* restrict dimy, int* restrict dimx){ return 0; } +int notcurses_render_to_file(struct notcurses* nc, FILE* fp){ + if(nc->lfdimx == 0 || nc->lfdimy == 0){ + return 0; + } + char* rastered = NULL; + size_t rastbytes = 0; + FILE* out = open_memstream(&rastered, &rastbytes); + if(out == NULL){ + return -1; + } + const int count = (nc->lfdimx > nc->stdscr->lenx ? nc->lfdimx : nc->stdscr->lenx) * + (nc->lfdimy > nc->stdscr->leny ? nc->lfdimy : nc->stdscr->leny); + struct crender* rvec = malloc(count * sizeof(*rvec)); + if(rvec == NULL){ + fclose(out); + free(rastered); + return -1; + } + memset(rvec, 0, count * sizeof(*rvec)); + for(int i = 0 ; i < count ; ++i){ + rvec[i].damaged = true; + } + int ret = notcurses_rasterize(nc, rvec, out); + free(rvec); + if(ret > 0){ + if(fprintf(fp, "%s", rastered) == ret){ + ret = 0; + }else{ + ret = -1; + } + } + fclose(out); + free(rastered); + return 0; +} + + // We execute the painter's algorithm, starting from our topmost plane. The // damagevector should be all zeros on input. On success, it will reflect // which cells were changed. We solve for each coordinate's cell by walking @@ -1123,7 +1159,7 @@ int notcurses_render(notcurses* nc){ struct crender* crender = malloc(crenderlen); memset(crender, 0, crenderlen); if(notcurses_render_internal(nc, crender) == 0){ - bytes = notcurses_rasterize(nc, crender); + bytes = notcurses_rasterize(nc, crender, nc->rstate.mstreamfp); } free(crender); clock_gettime(CLOCK_MONOTONIC, &done);