Rewrite the text wrapping code

Write a new, simpler and more efficient, text rewrapper. Remove
generation of the linked list of lines for display and instead
display lines directly from the buffer.
This commit is contained in:
Mike Sharov 2021-10-02 18:17:22 -04:00
parent 63f0d70806
commit 91192d03b8
5 changed files with 71 additions and 193 deletions

84
conv.c
View File

@ -164,90 +164,6 @@ char* text_from_html (const char* html)
return text;
}
// 5th try at a wrap text functions.
// My first version was broken, my second one sucked, my third try was
// so overcomplicated I didn't understand it anymore... Kianga tried
// the 4th version which corrupted some random memory unfortunately...
// but this one works. Heureka!
char* WrapText (const char* text, unsigned width)
{
char* textblob = strdup (text); // Working copy of text.
char* start = textblob;
char* line = malloc (1); // One line of text with max width.
line[0] = '\0';
char* newtext = malloc (1);
memset (newtext, 0, 1);
while (1) {
// First, cut at \n.
char* chapter = strsep (&textblob, "\n");
if (chapter == NULL)
break;
while (1) {
char* savepos = chapter; // Saved position pointer so we can go back in the string.
const char* chunk = strsep (&chapter, " ");
// Last chunk.
if (chunk == NULL) {
if (line != NULL) {
newtext = realloc (newtext, strlen (newtext) + strlen (line) + 2);
strcat (newtext, line);
strcat (newtext, "\n");
line[0] = '\0';
}
break;
}
if (utf8_length (chunk) > width) {
// First copy remaining stuff in line to newtext.
newtext = realloc (newtext, strlen (newtext) + strlen (line) + 2);
strcat (newtext, line);
strcat (newtext, "\n");
free (line);
line = malloc (1);
line[0] = '\0';
// Then copy chunk with max length of line to newtext.
line = realloc (line, width + 1);
strncat (line, chunk, width - 5);
strcat (line, "...");
newtext = realloc (newtext, strlen (newtext) + width + 2);
strcat (newtext, line);
strcat (newtext, "\n");
free (line);
line = malloc (1);
line[0] = '\0';
continue;
}
if (utf8_length (line) + utf8_length (chunk) <= width) {
line = realloc (line, strlen (line) + strlen (chunk) + 2);
strcat (line, chunk);
strcat (line, " ");
} else {
// Why can chapter be NULL here anyway?
if (chapter != NULL) {
--chapter;
chapter[0] = ' ';
}
chapter = savepos;
newtext = realloc (newtext, strlen (newtext) + strlen (line) + 2);
strcat (newtext, line);
strcat (newtext, "\n");
free (line);
line = malloc (1);
line[0] = '\0';
}
}
}
free (line);
free (start);
return newtext;
}
// Remove leading whitspaces, newlines, tabs.
// This function should be safe for working on UTF-8 strings.
// fullclean: false = only suck chars from beginning of string

1
conv.h
View File

@ -20,7 +20,6 @@
#include "config.h"
char* text_from_html (const char* html);
char* WrapText (const char* text, unsigned width);
void CleanupString (char* string, bool fullclean);
char* Hashify (const char* url);
char* genItemHash (const char* const* hashitems, unsigned items);

146
ui.c
View File

@ -32,15 +32,6 @@
//----------------------------------------------------------------------
// Scrollable feed->description.
struct scrolltext {
char* line;
struct scrolltext* next;
struct scrolltext* prev;
};
//----------------------------------------------------------------------
static bool resize_dirty = false;
//----------------------------------------------------------------------
@ -54,14 +45,11 @@ void sig_winch (int p __attribute__((unused)))
// Speed of this code has been greatly increased in 1.2.1.
static void UIDisplayItem (const struct newsitem* current_item, const struct feed* current_feed)
{
struct scrolltext* first_line = NULL; // Linked list of lines
unsigned linenumber = 0; // First line on screen (scrolling). Ugly hack.
unsigned maxlines = 0;
const unsigned pagesz = LINES-4;
const unsigned ymax = LINES-1;
bool rewrap = true;
unsigned topline = 0; // First line on screen
while (1) {
const unsigned pagesz = LINES-4;
const unsigned pagew = COLS-2;
erase();
const char* feed_title = current_feed->description;
@ -103,53 +91,29 @@ static void UIDisplayItem (const struct newsitem* current_item, const struct fee
}
// Print item text
unsigned desclines = 0; // and count lines
if (!current_item->data->description || !current_item->data->description[0])
mvadd_utf8 (ydesc, xdesc, _("No description available."));
else {
// Only generate a new scroll list if we need to rewrap everything.
// Otherwise just typeaheadskip this block.
if (rewrap) {
char* newtextwrapped = WrapText (current_item->data->description, COLS - 4);
char* freeme = newtextwrapped; // Set ptr to str start so we can free later.
// Split newtextwrapped at \n and put into double linked list.
while (1) {
char* textslice = strsep (&newtextwrapped, "\n");
// Find out max number of lines text has.
++maxlines;
if (textslice != NULL) {
struct scrolltext* textlist = malloc (sizeof (struct scrolltext));
textlist->line = strdup (textslice);
// Gen double list with new items at bottom.
textlist->next = NULL;
if (first_line == NULL) {
textlist->prev = NULL;
first_line = textlist;
} else {
textlist->prev = first_line;
while (textlist->prev->next != NULL)
textlist->prev = textlist->prev->next;
textlist->prev->next = textlist;
}
} else
break;
char* wrapped_dtext = strdup (current_item->data->description);
wrap_text (wrapped_dtext, pagew);
const char* dtext = wrapped_dtext;
const char* dtextend = dtext + strlen (dtext);
unsigned y = ydesc;
while (dtext < dtextend) {
const char* pnl = strchr (dtext, '\n');
if (!pnl)
pnl = dtextend;
if (desclines >= topline && desclines < topline+pagesz) {
unsigned linelen = utf8_range_length (dtext, pnl);
if (linelen > pagew)
linelen = pagew;
mvaddn_utf8 (y++, xdesc, dtext, linelen);
}
free (freeme);
rewrap = false;
++desclines;
dtext = pnl+strlen("\n");
}
// Skip to the first visible line
unsigned ientry = 0;
const struct scrolltext* l = first_line;
while (ientry < linenumber && l) {
++ientry;
l = l->next;
}
// We sould now have the linked list setup'ed... hopefully.
for (unsigned y = ydesc; y < ymax && l; ++ientry, ++y, l = l->next)
mvadd_utf8 (y, xdesc, l->line);
free (wrapped_dtext);
}
char keyinfostr [256];
@ -162,76 +126,42 @@ static void UIDisplayItem (const struct newsitem* current_item, const struct fee
int uiinput = getch();
if (uiinput == _settings.keybindings.help || uiinput == '?')
UIDisplayItemHelp();
else if (uiinput == '\n' || uiinput == _settings.keybindings.prevmenu || uiinput == _settings.keybindings.enter) {
// Free the wrapped text linked list.
while (first_line) {
struct scrolltext* l = first_line;
first_line = first_line->next;
free (l->line);
free (l);
}
else if (uiinput == '\n' || uiinput == _settings.keybindings.prevmenu || uiinput == _settings.keybindings.enter)
return;
} else if (uiinput == _settings.keybindings.urljump)
else if (uiinput == _settings.keybindings.urljump)
UISupportURLJump (current_item->data->link);
else if (uiinput == _settings.keybindings.next || uiinput == KEY_RIGHT) {
if (current_item->next != NULL) {
current_item = current_item->next;
linenumber = 0;
rewrap = true;
maxlines = 0;
} else {
// Setting rewrap to 1 to get the free block below executed.
rewrap = true;
topline = 0;
} else
uiinput = ungetch (_settings.keybindings.prevmenu);
}
} else if (uiinput == _settings.keybindings.prev || uiinput == KEY_LEFT) {
if (current_item->prev != NULL) {
current_item = current_item->prev;
linenumber = 0;
rewrap = true;
maxlines = 0;
} else {
// Setting rewrap to 1 to get the free block below executed.
rewrap = true;
topline = 0;
} else
uiinput = ungetch (_settings.keybindings.prevmenu);
}
} else if (uiinput == KEY_NPAGE || uiinput == ' ' || uiinput == _settings.keybindings.pdown) {
// Scroll by one page.
for (unsigned i = 0; i < pagesz; ++i)
if (linenumber + pagesz < maxlines)
++linenumber;
if (topline + pagesz < desclines)
++topline;
} else if (uiinput == KEY_PPAGE || uiinput == _settings.keybindings.pup) {
for (unsigned i = 0; i < pagesz; ++i)
if (linenumber > 0)
--linenumber;
} else if (uiinput == KEY_UP && linenumber > 0)
--linenumber;
else if (uiinput == KEY_DOWN && linenumber + pagesz < maxlines)
++linenumber;
if (topline > 0)
--topline;
} else if (uiinput == KEY_UP && topline > 0)
--topline;
else if (uiinput == KEY_DOWN && topline + pagesz < desclines)
++topline;
else if (resize_dirty || uiinput == KEY_RESIZE) {
rewrap = true;
// Reset maxlines, otherwise the program will scroll to far down.
maxlines = 0;
endwin();
refresh();
clear();
resize_dirty = false;
} else if (uiinput == _settings.keybindings.about)
UIAbout();
// Redraw screen on ^L
else if (uiinput == 12)
else if (uiinput == 12) // Redraw screen on ^L
clear();
// Free the linked list structure if we need to rewrap the text block.
if (rewrap) {
// Free the wrapped text linked list.
while (first_line) {
struct scrolltext* l = first_line;
first_line = first_line->next;
free (l->line);
free (l);
}
}
}
}

View File

@ -284,6 +284,14 @@ unsigned utf8_length (const char* s)
return l;
}
unsigned utf8_range_length (const char* f, const char* l)
{
unsigned rl = 0;
while (f < l && utf8_next (&f))
++rl;
return rl;
}
void addn_utf8 (const char* s, unsigned n)
{
#if NCURSES_WIDECHAR
@ -352,3 +360,26 @@ char* utf8_write (wchar_t c, char* o)
}
return o;
}
void wrap_text (char* text, unsigned width)
{
unsigned textlen = strlen (text);
char* t = text;
char* tend = t + textlen;
char* sp = NULL;
unsigned linelen = 0;
while (t < tend) {
char* pc = t;
wchar_t c = utf8_next ((const char**) &t);
if (c == '\n') {
linelen = 0;
sp = NULL;
continue;
} else if (c == ' ')
sp = pc;
if (++linelen >= width && sp) {
*sp = '\n';
linelen = pc - sp;
}
}
}

View File

@ -29,9 +29,11 @@ void SmartFeedsUpdate (void);
bool SmartFeedExists (const char* smartfeed);
void DrawProgressBar (unsigned numobjects, unsigned titlestrlen);
unsigned utf8_length (const char* s);
unsigned utf8_range_length (const char* f, const char* l);
void add_utf8 (const char* s);
void addn_utf8 (const char* s, unsigned n);
void mvadd_utf8 (int y, int x, const char* s);
void mvaddn_utf8 (int y, int x, const char* s, unsigned n);
unsigned utf8_osize (wchar_t c);
char* utf8_write (wchar_t c, char* o);
void wrap_text (char* text, unsigned width);