take-no-prisoners overhaul of ncplane_puttext() #829
parent
df3dc7f8e7
commit
14d6129007
@ -0,0 +1,201 @@
|
||||
#include "internal.h"
|
||||
#include <unictype.h>
|
||||
|
||||
static bool
|
||||
islinebreak(wchar_t wchar){
|
||||
// UC_LINE_SEPARATOR + UC_PARAGRAPH_SEPARATOR
|
||||
if(wchar == '\n'){
|
||||
return true;
|
||||
}
|
||||
const uint32_t mask = UC_CATEGORY_MASK_Zl | UC_CATEGORY_MASK_Zp;
|
||||
return uc_is_general_category_withtable(wchar, mask);
|
||||
}
|
||||
|
||||
static bool
|
||||
iswordbreak(wchar_t wchar){
|
||||
const uint32_t mask = UC_CATEGORY_MASK_Z |
|
||||
UC_CATEGORY_MASK_Zs;
|
||||
return uc_is_general_category_withtable(wchar, mask);
|
||||
}
|
||||
|
||||
// print the first 'bytes' bytes of 'text' to 'n', using alignment 'align'
|
||||
// and requiring 'cols' columns, relative to the current cursor position.
|
||||
// it is an error to call ncplane_putline() with more data than can be printed
|
||||
// on the current row.
|
||||
static inline int
|
||||
ncplane_putline(ncplane* n, ncalign_e align, int cols, const char* text, size_t bytes){
|
||||
const int avail = ncplane_dim_x(n) - n->x - 1;
|
||||
const int offset = notcurses_align(avail, align, cols);
|
||||
return ncplane_putnstr_yx(n, -1, n->x + offset, bytes, text);
|
||||
}
|
||||
|
||||
static int
|
||||
puttext_advance_line(ncplane* n){
|
||||
//fprintf(stderr, "ADVANCING LINE FROM %d/%d\n", n->y, n->x);
|
||||
if(n->scrolling){
|
||||
scroll_down(n);
|
||||
/*if(ncplane_putsimple_yx(n, -1, -1, '\n') < 0){
|
||||
return -1;
|
||||
}*/
|
||||
}else{
|
||||
// will fail on last line in the absence of scrolling, which is proper
|
||||
if(ncplane_cursor_move_yx(n, n->y + 1, 0)){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// put up to a line of text down at the current cursor position. returns the
|
||||
// number of columns consumed, or -1 on error. the number of bytes consumed is
|
||||
// added to '*bytes', if 'bytes' is not NULL. any alignment is done relative to
|
||||
// the current cursor position. any line-breaking character will immediately
|
||||
// end the output, and move the cursor to the beginning of the next row. on an
|
||||
// error, '*bytes' is not updated, and nothing is printed.
|
||||
//
|
||||
// an input with C columns available on the row can be one of a few things:
|
||||
// * text wholly within C columns -- print it, advance x
|
||||
// * text + newline within C columns -- print through newline, ++y, x = 0
|
||||
// * text + wordbreak at C columns -- print through C, ++y, x = 0
|
||||
// * text + text at C columns:
|
||||
// * breaker (some text followed by whitespace): print through breaker, ++y, x = 0
|
||||
// * no breaker (all one word, with possible leading whitespace):
|
||||
// * leading whitespace? dump it, ++y, x = 0
|
||||
// * C == dimx: print through C, ++y, x = 0
|
||||
// * C < dimx: ++y, x = 0
|
||||
static int
|
||||
puttext_line(ncplane* n, ncalign_e align, const char* text, size_t* bytes){
|
||||
int cursx; // current cursor location
|
||||
ncplane_cursor_yx(n, NULL, &cursx);
|
||||
const int dimx = ncplane_dim_x(n);
|
||||
const int avail = dimx - cursx - 1;
|
||||
//fprintf(stderr, "LINE %d starts at %d, len %d, avail %d\n", n->y, cursx, dimx, avail);
|
||||
int bytes_leading_ws; // bytes thus far of leading whitespace
|
||||
int cols_leading_ws; // cols thus far of leading whitespace
|
||||
int bytes_leading_break; // bytes through last wordbreaker, 0 for no break yet
|
||||
int cols_leading_break; // cols through last wordbreaker, 0 for no break yet
|
||||
int cols = 0; // columns consumed thus far, cols > cols_leading_ws -> got_glyph
|
||||
int b = 0; // bytes consumed thus far
|
||||
bytes_leading_ws = cols_leading_ws = 0;
|
||||
bytes_leading_break = cols_leading_break = 0;
|
||||
while(cols <= avail){ // we can print everything we've read, if desired
|
||||
mbstate_t mbstate = {};
|
||||
wchar_t w;
|
||||
const size_t consumed = mbrtowc(&w, text + b, MB_CUR_MAX, &mbstate);
|
||||
if(consumed == (size_t)-2 || consumed == (size_t)-1){
|
||||
logerror(n->nc, "Invalid UTF-8 after %d bytes\n", b);
|
||||
return -1;
|
||||
}
|
||||
//fprintf(stderr, "converted [%s] -> %lc\n", text + b, w);
|
||||
if(consumed == 0){ // text was wholly within destination row, print it
|
||||
if(ncplane_putline(n, align, cols, text, b) < 0){
|
||||
return -1;
|
||||
}
|
||||
if(bytes){
|
||||
*bytes = b;
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
// if w is a linebreaker, print what we have, advance, and return
|
||||
if(islinebreak(w)){
|
||||
//fprintf(stderr, "LINEBREAK at %d/%d\n", n->y, n->x);
|
||||
if(b){
|
||||
if(ncplane_putline(n, align, cols, text, b) < 0){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if(puttext_advance_line(n)){
|
||||
return -1;
|
||||
}
|
||||
if(bytes){
|
||||
*bytes += b + consumed;
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
b += consumed;
|
||||
int width = wcwidth(w);
|
||||
if(width < 0){
|
||||
width = 0; // FIXME
|
||||
}
|
||||
cols += width;
|
||||
if(iswordbreak(w)){
|
||||
if(cols > cols_leading_ws){
|
||||
bytes_leading_break = b;
|
||||
cols_leading_break = cols;
|
||||
}else{
|
||||
bytes_leading_ws = b;
|
||||
cols_leading_ws = cols;
|
||||
}
|
||||
}
|
||||
//fprintf(stderr, "%d approved [%lc] (tbytes: %d tcols: %d)\n", n->y, w, b, cols);
|
||||
}
|
||||
int colsreturn = 0;
|
||||
if(bytes_leading_break){
|
||||
if(ncplane_putline(n, align, cols, text, bytes_leading_break) < 0){
|
||||
return -1;
|
||||
}
|
||||
if(bytes){
|
||||
*bytes += bytes_leading_break;
|
||||
}
|
||||
colsreturn = cols_leading_break;
|
||||
}else if(bytes_leading_ws){
|
||||
if(ncplane_putline(n, align, cols, text, bytes_leading_ws) < 0){
|
||||
return -1;
|
||||
}
|
||||
if(bytes){
|
||||
*bytes += bytes_leading_ws;
|
||||
}
|
||||
colsreturn = cols_leading_ws;
|
||||
}else if(cols == dimx){
|
||||
if(ncplane_putline(n, align, cols, text, b) < 0){
|
||||
return -1;
|
||||
}
|
||||
if(bytes){
|
||||
*bytes = b;
|
||||
}
|
||||
colsreturn = cols;
|
||||
}
|
||||
//fprintf(stderr, "FELL OFF line %d after %d cols %dB returning %d\n", n->y, cols, b, colsreturn);
|
||||
if(puttext_advance_line(n)){
|
||||
return -1;
|
||||
}
|
||||
return colsreturn;
|
||||
}
|
||||
|
||||
// FIXME probably best to use u8_wordbreaks() and get all wordbreaks at once...
|
||||
int ncplane_puttext(ncplane* n, int y, ncalign_e align, const char* text, size_t* bytes){
|
||||
if(bytes){
|
||||
*bytes = 0;
|
||||
}
|
||||
int totalcols = 0;
|
||||
// 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.
|
||||
// if we're using NCALIGN_LEFT, we'll be printing with x==-1, i.e. wherever
|
||||
// the cursor is. if there's insufficient room to print anything, we need to
|
||||
// try moving to the next line first. FIXME this ought actually apply to all
|
||||
// alignments, which ought be taken relative to n->x. no change for
|
||||
// NCALIGN_RIGHT, but NCALIGN_CENTER needs explicitly handle it...
|
||||
do{
|
||||
if(y != -1){
|
||||
if(ncplane_cursor_move_yx(n, y, -1)){
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
size_t linebytes = 0;
|
||||
int cols = puttext_line(n, align, text, &linebytes);
|
||||
if(cols < 0){
|
||||
return -1;
|
||||
}
|
||||
totalcols += cols;
|
||||
if(bytes){
|
||||
*bytes += linebytes;
|
||||
}
|
||||
text += linebytes;
|
||||
//fprintf(stderr, "new cursor: %d/%d consumed: %zu\n", n->y, n->x, linebytes);
|
||||
y = n->y;
|
||||
}while(*text);
|
||||
return totalcols;
|
||||
}
|
||||
|
Loading…
Reference in New Issue