First go at ncplane_puttext() (#690)

Very simple take at ncplane_puttext(), a new function for linebroken text. Also some very basic unit tests. I doubt this works very well yet, but it handles the simplest cases #682. Added nclog(), internal function for logging. #520
This commit is contained in:
Nick Black 2020-06-08 03:01:58 -04:00 committed by GitHub
parent 7736e4a7dc
commit 28431914a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 335 additions and 99 deletions

View File

@ -25,22 +25,22 @@ steps:
- export LANG=en_US.UTF-8
- mkdir build
- cd build
- cmake .. -DCMAKE_BUILD_TYPE=Release
- cmake -DCMAKE_BUILD_TYPE=Release -DUSE_MULTIMEDIA=none ..
- make
- ./notcurses-tester -p ../data
---
kind: pipeline
type: docker
name: fedora-rawhide
steps:
- name: fedora-rawhide
image: dankamongmen/fedora33:2020-06-08a
commands:
- export LANG=en_US.UTF-8
- dnf install -y doctest-devel
- mkdir build
- cd build
- cmake -DCMAKE_BUILD_TYPE=Release -DUSE_MULTIMEDIA=oiio ..
- make
- ./notcurses-tester -p ../data
#---
#kind: pipeline
#type: docker
#name: fedora-rawhide
#
#steps:
#- name: fedora-rawhide
# image: dankamongmen/fedora33:2020-05-29a
# commands:
# - export LANG=en_US.UTF-8
# - mkdir build
# - cd build
# - ls
# - cmake -DCMAKE_BUILD_TYPE=Release -DUSE_MULTIMEDIA=oiio ..
# - make
# - ./notcurses-tester -p ../data

View File

@ -6,6 +6,9 @@ rearrangements of Notcurses.
that `struct`'s `flags` field. Each `bool` has its own `NCOPTION_`.
* Added a Pixel API for working directly with the contents of `ncvisual`s,
including `ncvisual_at_yx()` and `ncvisual_set_yx()`.
* Added `ncplane_puttext()` for writing multiline, line-broken text.
* Added `ncplane_putnstr()`, `ncplane_putnstr_yx()`, and
`ncplane_putnstr_aligned()` for byte-limited output of UTF-8.
* 1.4.5 (2020-06-04)
* `ncblit_rgba()` and `ncblit_bgrx()` have replaced most of their arguments

View File

@ -902,12 +902,11 @@ int ncplane_putwegc_stainable(struct ncplane* n, const wchar_t* gclust, int* sby
// Write a series of EGCs to the current location, using the current style.
// They will be interpreted as a series of columns (according to the definition
// of ncplane_putc()). Advances the cursor by some positive number of cells
// of ncplane_putc()). Advances the cursor by some positive number of columns
// (though not beyond the end of the plane); this number is returned on success.
// On error, a non-positive number is returned, indicating the number of cells
// On error, a non-positive number is returned, indicating the number of columns
// which were written before the error.
static inline int
ncplane_putstr_yx(struct ncplane* n, int y, int x, const char* gclusters);
int ncplane_putstr_yx(struct ncplane* n, int y, int x, const char* gclusters);
static inline int
ncplane_putstr(struct ncplane* n, const char* gclustarr){
@ -935,6 +934,22 @@ ncplane_putwstr_yx(struct ncplane* n, int y, int x, const wchar_t* gclustarr){
return ret;
}
// Write a series of EGCs to the current location, using the current style.
// They will be interpreted as a series of columns (according to the definition
// of ncplane_putc()). Advances the cursor by some positive number of columns
// (though not beyond the end of the plane); this number is returned on success.
// On error, a non-positive number is returned, indicating the number of columns
// which were written before the error. No more than 's' bytes will be written.
int ncplane_putnstr_yx(struct ncplane* n, int y, int x, size_t s, const char* gclusters);
static inline int
ncplane_putnstr(struct ncplane* n, size_t s, const char* gclustarr){
return ncplane_putnstr_yx(n, -1, -1, s, gclustarr);
}
int ncplane_putnstr_aligned(struct ncplane* n, int y, ncalign_e align,
size_t s, const char* s);
static inline int
ncplane_putwstr_aligned(struct ncplane* n, int y, ncalign_e align,
const wchar_t* gclustarr){
@ -1001,6 +1016,24 @@ ncplane_printf_aligned(struct ncplane* n, int y, ncalign_e align, const char* fo
}
```
Multiline chunks of human-readable text can be written with
`ncplane_puttext()` even if the plane does not have scrolling enabled. Such
text will be broken up across lines using the Unicode line-breaking algorithm
of [Unicode Annex #14](http://www.unicode.org/reports/tr14/tr14-34.html).
```c
// Write the specified text to the plane, breaking lines sensibly, beginning at
// the specified line. Returns the number of columns written. When breaking a
// line, the line will be cleared to the end of the plane (the last line will
// *not* be so cleared). The number of bytes written from the input is written
// to '*bytes' if it is not NULL. Cleared columns are included in the return
// value, but *not* included in the number of bytes written. Leaves the cursor
// at the end of output. A partial write will be accomplished as far as it can;
// determine whether the write completed by inspecting '*bytes'.
int ncplane_puttext(struct ncplane* n, int y, ncalign_e align,
const char* text, size_t* bytes);
```
Lines and boxes can be drawn, interpolating their colors between their two
endpoints. For a line of a single color, be sure to specify the same channels
on both sides. Boxes allow fairly detailed specification of how they're drawn.

View File

@ -10,93 +10,83 @@ notcurses_output - output to ncplanes
**#include <notcurses/notcurses.h>**
**static inline int
ncplane_putc(struct ncplane* n, const cell* c);**
**static inline int ncplane_putc(struct ncplane* n, const cell* c);**
**int ncplane_putc_yx(struct ncplane* n, int y, int x, const cell* c);**
**static inline int
ncplane_putsimple(struct ncplane* n, char c);**
**static inline int ncplane_putsimple(struct ncplane* n, char c);**
**static inline int
ncplane_putsimple_yx(struct ncplane* n, int y, int x, char c);**
**static inline int ncplane_putsimple_yx(struct ncplane* n, int y, int x, char c);**
**int ncplane_putsimple_stainable(struct ncplane* n, char c);**
**static inline int
ncplane_putwc(struct ncplane* n, wchar_t w);**
**static inline int ncplane_putwc(struct ncplane* n, wchar_t w);**
**int ncplane_putwc_yx(struct ncplane* n, int y, int x, wchar_t w);**
**static inline int
ncplane_putegc(struct ncplane* n, const char* gclust, int* sbytes);**
**static inline int ncplane_putegc(struct ncplane* n, const char* gclust, int* sbytes);**
**int ncplane_putegc_yx(struct ncplane* n, int y, int x, const char* gclust, int* sbytes);**
**int ncplane_putegc_stainable(struct ncplane* n, const char* gclust, int* sbytes);**
**static inline int
ncplane_putwegc(struct ncplane* n, const wchar_t* gclust, int* sbytes);**
**static inline int ncplane_putwegc(struct ncplane* n, const wchar_t* gclust, int* sbytes);**
**static inline int
ncplane_putwegc_yx(struct ncplane* n, int y, int x, const wchar_t* gclust, int* sbytes);**
**static inline int ncplane_putwegc_yx(struct ncplane* n, int y, int x, const wchar_t* gclust, int* sbytes);**
**int ncplane_putwegc_stainable(struct ncplane* n, const wchar_t* gclust, int* sbytes);**
**static inline int
ncplane_putstr_yx(struct ncplane* n, int y, int x, const char* gclustarr);**
**int ncplane_putstr_yx(struct ncplane* n, int y, int x, const char* gclustarr);**
**static inline int
ncplane_putstr(struct ncplane* n, const char* gclustarr);**
**static inline int ncplane_putstr(struct ncplane* n, const char* gclustarr);**
**int ncplane_putstr_aligned(struct ncplane* n, int y, ncalign_e align,
const char* s);**
**int ncplane_putstr_aligned(struct ncplane* n, int y, ncalign_e align, const char* s);**
**int ncplane_putwstr_yx(struct ncplane* n, int y, int x, const wchar_t* gclustarr);**
**static inline int
ncplane_putwstr_aligned(struct ncplane* n, int y, ncalign_e align,
const wchar_t* gclustarr);**
**static inline int ncplane_putwstr_aligned(struct ncplane* n, int y, ncalign_e align, const wchar_t* gclustarr);**
**static inline int
ncplane_putwstr(struct ncplane* n, const wchar_t* gclustarr);**
**static inline int ncplane_putwstr(struct ncplane* n, const wchar_t* gclustarr);**
**int ncplane_vprintf_aligned(struct ncplane* n, int y, ncalign_e align,
const char* format, va_list ap);**
**int ncplane_putnstr_yx(struct ncplane* n, int y, int x, size_t s, const char* gclusters);**
**int ncplane_vprintf_yx(struct ncplane* n, int y, int x,
const char* format, va_list ap);**
**static inline int ncplane_putnstr(struct ncplane* n, size_t s, const char* gclustarr);**
**static inline int
ncplane_vprintf(struct ncplane* n, const char* format, va_list ap);**
**int ncplane_putnstr_aligned(struct ncplane* n, int y, ncalign_e align, size_t s, const char* s);**
**static inline int
ncplane_printf(struct ncplane* n, const char* format, ...);**
**int ncplane_vprintf_aligned(struct ncplane* n, int y, ncalign_e align, const char* format, va_list ap);**
**static inline int
ncplane_printf_yx(struct ncplane* n, int y, int x, const char* format, ...);**
**int ncplane_vprintf_yx(struct ncplane* n, int y, int x, const char* format, va_list ap);**
**static inline int
ncplane_printf_aligned(struct ncplane* n, int y, ncalign_e align, const char* format, ...);**
**static inline int ncplane_vprintf(struct ncplane* n, const char* format, va_list ap);**
**static inline int ncplane_printf(struct ncplane* n, const char* format, ...);**
**static inline int ncplane_printf_yx(struct ncplane* n, int y, int x, const char* format, ...);**
**static inline int ncplane_printf_aligned(struct ncplane* n, int y, ncalign_e align, const char* format, ...);**
**int ncplane_cursor_move_yx(struct ncplane* n, int y, int x);**
**int ncplane_puttext(struct ncplane* n, int y, ncalign_e align, const char* text, size_t* bytes);**
# DESCRIPTION
These functions write EGCs (Extended Grapheme Clusters) to the specified
**struct ncplane**s. The following inputs are supported:
* **ncplane_putc(3)**: writes a single cell (see **notcurses_cell(3)**)
* **ncplane_put_simple(3)**: writes a single 7-bit ASCII character
* **ncplane_putwc(3)**: writes a single **wchar_t** (following UTF-8 conversion)
* **ncplane_putwegc(3)**: writes a single EGC from an array of **wchar_t**
* **ncplane_putegc(3)**: writes a single EGC from an array of UTF-8
* **ncplane_putstr(3)**: writes a set of EGCs from an array of UTF-8
* **ncplane_putwstr(3)**: writes a set of EGCs from an array of **wchar_t**
* **ncplane_vprintf(3)**: formatted output using **va_list**
* **ncplane_printf(3)**: formatted output using variadic arguments
* **ncplane_putc()**: writes a single cell (see **notcurses_cell(3)**)
* **ncplane_put_simple()**: writes a single 7-bit ASCII character
* **ncplane_putwc()**: writes a single **wchar_t** (following UTF-8 conversion)
* **ncplane_putwegc()**: writes a single EGC from an array of **wchar_t**
* **ncplane_putegc()**: writes a single EGC from an array of UTF-8
* **ncplane_putstr()**: writes a set of EGCs from an array of UTF-8
* **ncplane_putwstr()**: writes a set of EGCs from an array of **wchar_t**
* **ncplane_vprintf()**: formatted output using **va_list**
* **ncplane_printf()**: formatted output using variadic arguments
* **ncplane_puttext()**: multi-line, line-broken, aligned text
All of these use the **ncplane**'s active styling, save **notcurses_putc(3)**,
All of these use the **ncplane**'s active styling, save **notcurses_putc()**,
which uses the cell's styling. Functions accepting a single EGC expect a series
of **wchar_t** terminated by **L'\0'** or a series of UTF-8 **char** terminated
by **'\0'**. The EGC must be well-formed, and must not contain any cluster
@ -104,10 +94,10 @@ breaks. For more information, consult [Unicode® Standard Annex #29](https://uni
Functions accepting a set of EGCs must consist of a series of well-formed EGCs,
broken by cluster breaks, terminated by the appropriate NUL terminator.
These functions output to the `ncplane`'s current cursor location. They *do not*
move to the next line upon reaching the right extreme of the containing plane.
If the entirety of the content cannot be output, they will output as much as
possible.
These functions output to the `ncplane`'s current cursor location. Aside from
**ncplane_puttext()**, they *do not* move to the next line upon reaching the
right extreme of the containing plane. If the entirety of the content cannot be
output, they will output as much as possible.
Each of these base functions has two additional forms:

View File

@ -1,3 +1,4 @@
* Review the testing checklist (doc/testing-checklist.md)
* clang-tidy check with something like:
* `cmake "-DCMAKE_CXX_CLANG_TIDY=/usr/bin/clang-tidy-11;-checks=-*,clang-analyzer-*,modernize-*,performance-*" ..`
* `scan-build cmake .. && scan-build make`

22
doc/testing-checklist.md Normal file
View File

@ -0,0 +1,22 @@
# Release testing
## Unit tests
Run unit tests (`make && make test`):
* In each multimedia configuration (`ffmpeg`, `oiio`, `none`)
* With LANG set to `fr_FR.UTF-8` (to test comma as decimal separator)
* All must pass
## Manual tests
Run, using `valgrind --tool=memcheck --leak-check=full`:
* `notcurses-demo` in each of the three multimedia configurations
* `notcurses-demo` with `USE_QRCODEGEN=off`
* `notcurses-ncreel`
* `notcurses-input`
* `notcurses-view` with each scaling mode and an image + video, in three
terminal geometries: square, tall, wide
* `notcurses-demo` with margins
* `notcurses-demo` with FPS plot and HUD up
* Play a game of `notcurses-tetris`
* Run each PoC binary, including `ncpp_build` and `ncpp_build_exceptions`

View File

@ -1438,32 +1438,11 @@ API int ncplane_putwegc_stainable(struct ncplane* n, const wchar_t* gclust, int*
// Write a series of EGCs to the current location, using the current style.
// They will be interpreted as a series of columns (according to the definition
// of ncplane_putc()). Advances the cursor by some positive number of cells
// of ncplane_putc()). Advances the cursor by some positive number of columns
// (though not beyond the end of the plane); this number is returned on success.
// On error, a non-positive number is returned, indicating the number of cells
// On error, a non-positive number is returned, indicating the number of columns
// which were written before the error.
static inline int
ncplane_putstr_yx(struct ncplane* n, int y, int x, const char* gclusters){
int ret = 0;
// FIXME speed up this blissfully naive solution
while(*gclusters){
int wcs;
int cols = ncplane_putegc_yx(n, y, x, gclusters, &wcs);
if(cols < 0){
return -ret;
}
if(wcs == 0){
break;
}
// after the first iteration, just let the cursor code control where we
// print, so that scrolling is taken into account
y = -1;
x = -1;
gclusters += wcs;
ret += wcs;
}
return ret;
}
API int ncplane_putstr_yx(struct ncplane* n, int y, int x, const char* gclusters);
static inline int
ncplane_putstr(struct ncplane* n, const char* gclustarr){
@ -1473,6 +1452,22 @@ ncplane_putstr(struct ncplane* n, const char* gclustarr){
API int ncplane_putstr_aligned(struct ncplane* n, int y, ncalign_e align,
const char* s);
// Write a series of EGCs to the current location, using the current style.
// They will be interpreted as a series of columns (according to the definition
// of ncplane_putc()). Advances the cursor by some positive number of columns
// (though not beyond the end of the plane); this number is returned on success.
// On error, a non-positive number is returned, indicating the number of columns
// which were written before the error. No more than 's' bytes will be written.
API int ncplane_putnstr_yx(struct ncplane* n, int y, int x, size_t s, const char* gclusters);
static inline int
ncplane_putnstr(struct ncplane* n, size_t s, const char* gclustarr){
return ncplane_putnstr_yx(n, -1, -1, s, gclustarr);
}
API int ncplane_putnstr_aligned(struct ncplane* n, int y, ncalign_e align,
size_t s, const char* gclustarr);
// ncplane_putstr(), but following a conversion from wchar_t to UTF-8 multibyte.
static inline int
ncplane_putwstr_yx(struct ncplane* n, int y, int x, const wchar_t* gclustarr){
@ -1572,6 +1567,17 @@ ncplane_printf_aligned(struct ncplane* n, int y, ncalign_e align, const char* fo
return ret;
}
// Write the specified text to the plane, breaking lines sensibly, beginning at
// the specified line. Returns the number of columns written. When breaking a
// line, the line will be cleared to the end of the plane (the last line will
// *not* be so cleared). The number of bytes written from the input is written
// to '*bytes' if it is not NULL. Cleared columns are included in the return
// value, but *not* included in the number of bytes written. Leaves the cursor
// at the end of output. A partial write will be accomplished as far as it can;
// determine whether the write completed by inspecting '*bytes'.
API int ncplane_puttext(struct ncplane* n, int y, ncalign_e align,
const char* text, size_t* bytes);
// Draw horizontal or vertical lines using the specified cell, starting at the
// current cursor position. The cursor will end at the cell following the last
// cell output (even, perhaps counter-intuitively, when drawing vertical

View File

@ -497,6 +497,9 @@ struct ncplane* ncreader_plane(struct ncreader* n);
bool ncreader_offer_input(struct ncreader* n, const struct ncinput* ni);
char* ncreader_contents(const struct ncreader* n);
void ncreader_destroy(struct ncreader* n, char** contents);
int ncplane_puttext(struct ncplane* n, int y, ncalign_e align, const char* text, size_t* bytes);
int ncplane_putnstr_yx(struct ncplane* n, int y, int x, size_t s, const char* gclusters);
int ncplane_putnstr_aligned(struct ncplane* n, int y, ncalign_e align, size_t s, const char* gclustarr);
""")
if __name__ == "__main__":

View File

@ -315,7 +315,7 @@ typedef struct notcurses {
// desired margins (best-effort only), copied in from notcurses_options
int margin_t, margin_b, margin_r, margin_l;
int loglevel;
palette256 palette; // 256-indexed palette can be used instead of/with RGB
bool palette_damage[NCPALETTESIZE];
struct esctrie* inputescapes; // trie of input escapes -> ncspecial_keys
@ -757,6 +757,11 @@ calc_gradient_channels(uint64_t* channels, uint64_t ul, uint64_t ur,
}
}
void nclog(const char* fmt, ...);
// logging
#define logerror(nc, fmt, ...) do{ if(nc->loglevel >= NCLOGLEVEL_ERROR){ nclog(__FILE__ "%d" fmt, __LINE__, ##__VA_ARGS__); } }while(0);
#ifdef __cplusplus
}
#endif

View File

@ -854,7 +854,8 @@ notcurses* notcurses_init(const notcurses_options* opts, FILE* outfp){
term_verify_seq(&ret->tcache.rmcup, "rmcup");
}
ret->bottom = ret->top = ret->stdscr = NULL;
if(ncvisual_init(ffmpeg_log_level(opts->loglevel))){
ret->loglevel = opts->loglevel;
if(ncvisual_init(ffmpeg_log_level(ret->loglevel))){
goto err;
}
if((ret->stdscr = create_initial_ncplane(ret, dimy, dimx)) == NULL){
@ -1477,6 +1478,73 @@ int ncplane_hline_interp(ncplane* n, const cell* c, int len,
return ret;
}
int ncplane_puttext(ncplane* n, int y, ncalign_e align, const char* text, size_t* bytes){
int totalcols = 0;
// save the beginning for diagnostic
const char* beginning = text;
// text points to the text we have *not* yet output. at each step, we see
// how much space we have available, and begin iterating from text. remember
// the most recent linebreaker that we see. when we exhaust our line, print
// through the linebreaker, and advance text.
// FIXME what about a long word? do we want to leave the big gap?
const int dimx = ncplane_dim_x(n);
const char* linestart = text;
int x = 0; // number of columns consumed for this line
do{
const char* breaker = NULL;
// figure how much text to output on this line
mbstate_t mbstate = {};
int width;
// verified columns thus far (carried, and through breaker)
size_t verifiedcols = x;
while(*text && x < dimx - 1){
wchar_t w;
size_t consumed = mbrtowc(&w, text, MB_CUR_MAX, &mbstate);
if(consumed == (size_t)-2 || consumed == (size_t)-1){
logerror(n->nc, "Invalid UTF-8 after %zu bytes\n", text - beginning);
*bytes = text - beginning;
return -1;
}
width = wcwidth(w);
if(width < 0){
logerror(n->nc, "Non-printable UTF-8 after %zu bytes\n", text - beginning);
*bytes = text - beginning;
return -1;
}
if(x + width >= dimx){
break;
}
// FIXME use the more advanced unicode functionality to break lines
if(iswspace(w)){
breaker = text;
verifiedcols = x;
}
x += width;
text += consumed;
}
int carrycols = 0;
if(x >= dimx){
// the last character was one past the amount we can print. set linestart
// back to breaker, set carrycols to the amount since breaker
carrycols = x - verifiedcols;
}
totalcols += verifiedcols;
const int xpos = ncplane_align(n, align, x);
if(breaker == NULL){
breaker = text;
}
if(ncplane_putnstr_yx(n, y, xpos, breaker - linestart, linestart) < 0){
*bytes = linestart - beginning;
return -1;
}
x = carrycols;
linestart = breaker + 1;
++y; // FIXME scrolling!
}while(*text);
*bytes = text - beginning;
return totalcols;
}
int ncplane_vline_interp(ncplane* n, const cell* c, int len,
uint64_t c1, uint64_t c2){
unsigned ur, ug, ub;
@ -2097,3 +2165,54 @@ void ncplane_center_abs(const ncplane* n, int* RESTRICT y, int* RESTRICT x){
*x += n->absx;
}
}
void nclog(const char* fmt, ...){
va_list va;
va_start(va, fmt);
vfprintf(stderr, fmt, va);
va_end(va);
}
int ncplane_putstr_yx(struct ncplane* n, int y, int x, const char* gclusters){
int ret = 0;
// FIXME speed up this blissfully naive solution
while(*gclusters){
int wcs;
int cols = ncplane_putegc_yx(n, y, x, gclusters, &wcs);
if(cols < 0){
return -ret;
}
if(wcs == 0){
break;
}
// after the first iteration, just let the cursor code control where we
// print, so that scrolling is taken into account
y = -1;
x = -1;
gclusters += wcs;
ret += wcs;
}
return ret;
}
int ncplane_putnstr_yx(struct ncplane* n, int y, int x, size_t s, const char* gclusters){
size_t ret = 0;
// FIXME speed up this blissfully naive solution
while(ret < s && *gclusters){
int wcs;
int cols = ncplane_putegc_yx(n, y, x, gclusters, &wcs);
if(cols < 0){
return -ret;
}
if(wcs == 0){
break;
}
// after the first iteration, just let the cursor code control where we
// print, so that scrolling is taken into account
y = -1;
x = -1;
gclusters += wcs;
ret += wcs;
}
return ret;
}

View File

@ -120,7 +120,11 @@ void cell_release(ncplane* n, cell* c){
// Duplicate one cell onto another when they share a plane. Convenience wrapper.
int cell_duplicate(ncplane* n, cell* targ, const cell* c){
return cell_duplicate_far(&n->pool, targ, n, c);
int r = cell_duplicate_far(&n->pool, targ, n, c);
if(r < 0){
logerror(n->nc, "Failed duplicating cell");
}
return r;
}
// the heart of damage detection. compare two cells (from two different planes)

50
tests/layout.cpp Normal file
View File

@ -0,0 +1,50 @@
#include "main.h"
#include "internal.h"
TEST_CASE("TextLayout") {
notcurses_options nopts{};
notcurses* nc_ = notcurses_init(&nopts, nullptr);
if(!nc_){
return;
}
ncplane* ncp_ = notcurses_stdplane(nc_);
REQUIRE(ncp_);
const char str[] = "this is going to be broken up";
SUBCASE("LayoutLeft") {
auto sp = ncplane_new(nc_, 2, 20, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, str, &bytes));
CHECK(0 == notcurses_render(nc_));
CHECK(bytes == strlen(str));
// FIXME inspect layout
ncplane_destroy(sp);
}
SUBCASE("LayoutRight") {
auto sp = ncplane_new(nc_, 2, 20, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_RIGHT, str, &bytes));
CHECK(0 == notcurses_render(nc_));
CHECK(bytes == strlen(str));
// FIXME inspect layout
ncplane_destroy(sp);
}
SUBCASE("LayoutCenter") {
auto sp = ncplane_new(nc_, 2, 20, 0, 0, nullptr);
REQUIRE(sp);
size_t bytes;
CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, str, &bytes));
CHECK(0 == notcurses_render(nc_));
CHECK(bytes == strlen(str));
// FIXME inspect layout
ncplane_destroy(sp);
}
CHECK(0 == notcurses_stop(nc_));
}