358 lines
9.4 KiB
C
358 lines
9.4 KiB
C
#include "mf_wordwrap.h"
|
|
|
|
/* Returns true if the line can be broken at this character. */
|
|
static bool is_wrap_space(uint16_t c)
|
|
{
|
|
return c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '-';
|
|
}
|
|
|
|
#if MF_USE_ADVANCED_WORDWRAP
|
|
|
|
/* Represents a single word and the whitespace after it. */
|
|
struct wordlen_s
|
|
{
|
|
int16_t word; /* Length of the word in pixels. */
|
|
int16_t space; /* Length of the whitespace in pixels. */
|
|
uint16_t chars; /* Number of characters in word + space, combined. */
|
|
};
|
|
|
|
/* Take the next word from the string and compute its width.
|
|
* Returns true if the word ends in a linebreak. */
|
|
static bool get_wordlen(const struct mf_font_s *font, mf_str *text,
|
|
struct wordlen_s *result)
|
|
{
|
|
mf_char c;
|
|
mf_str prev = *text;
|
|
|
|
result->word = 0;
|
|
result->space = 0;
|
|
result->chars = 0;
|
|
|
|
c = mf_getchar(text);
|
|
while (c && !is_wrap_space(c))
|
|
{
|
|
result->chars++;
|
|
result->word += mf_character_width(font, c);
|
|
|
|
prev = *text;
|
|
c = mf_getchar(text);
|
|
}
|
|
|
|
while (c && is_wrap_space(c))
|
|
{
|
|
result->chars++;
|
|
|
|
if (c == ' ' || c == '-')
|
|
result->space += mf_character_width(font, c);
|
|
else if (c == '\t')
|
|
result->space += mf_character_width(font, 'm') * MF_TABSIZE;
|
|
else if (c == '\n') {
|
|
/* Special case for newlines, skip the character then break. */
|
|
prev = *text;
|
|
break;
|
|
}
|
|
|
|
prev = *text;
|
|
c = mf_getchar(text);
|
|
}
|
|
|
|
/* The last loop reads the first character of next word, put it back. */
|
|
if (c)
|
|
*text = prev;
|
|
|
|
return (c == '\0' || c == '\n');
|
|
}
|
|
|
|
/* Represents the rendered length for a single line. */
|
|
struct linelen_s
|
|
{
|
|
mf_str start; /* Start of the text for line. */
|
|
uint16_t chars; /* Total number of characters on the line. */
|
|
int16_t width; /* Total length of all words + whitespace on the line in pixels. */
|
|
bool linebreak; /* True if line ends in a linebreak */
|
|
struct wordlen_s last_word; /* Last word on the line. */
|
|
struct wordlen_s last_word_2; /* Second to last word on the line. */
|
|
};
|
|
|
|
/* Append word onto the line if it fits. If it would overflow, don't add and
|
|
* return false. */
|
|
static bool append_word(const struct mf_font_s *font, int16_t width,
|
|
struct linelen_s *current, mf_str *text)
|
|
{
|
|
mf_str tmp = *text;
|
|
struct wordlen_s wordlen;
|
|
bool linebreak;
|
|
|
|
linebreak = get_wordlen(font, &tmp, &wordlen);
|
|
|
|
if (current->width + wordlen.word <= width)
|
|
{
|
|
*text = tmp;
|
|
current->last_word_2 = current->last_word;
|
|
current->last_word = wordlen;
|
|
current->linebreak = linebreak;
|
|
current->chars += wordlen.chars;
|
|
current->width += wordlen.word + wordlen.space;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Append a character to the line if it fits. */
|
|
static bool append_char(const struct mf_font_s *font, int16_t width,
|
|
struct linelen_s *current, mf_str *text)
|
|
{
|
|
mf_str tmp = *text;
|
|
mf_char c;
|
|
uint16_t w;
|
|
|
|
c = mf_getchar(&tmp);
|
|
w = mf_character_width(font, c);
|
|
|
|
if (current->width + w <= width)
|
|
{
|
|
*text = tmp;
|
|
current->chars++;
|
|
current->width += w;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*static int16_t abs16(int16_t x) { return (x > 0) ? x : -x; }*/
|
|
static int32_t sq16(int16_t x) { return (int32_t)x * x; }
|
|
|
|
/* Try to balance the lines by potentially moving one word from the previous
|
|
* line to the the current one. */
|
|
static void tune_lines(struct linelen_s *current, struct linelen_s *previous,
|
|
int16_t max_width)
|
|
{
|
|
int16_t curw1, prevw1;
|
|
int16_t curw2, prevw2;
|
|
int32_t delta1, delta2;
|
|
|
|
/* If the lines are rendered as is */
|
|
curw1 = current->width - current->last_word.space;
|
|
prevw1 = previous->width - previous->last_word.space;
|
|
delta1 = sq16(max_width - prevw1) + sq16(max_width - curw1);
|
|
|
|
/* If the last word is moved */
|
|
curw2 = current->width + previous->last_word.word;
|
|
prevw2 = previous->width - previous->last_word.word
|
|
- previous->last_word.space
|
|
- previous->last_word_2.space;
|
|
delta2 = sq16(max_width - prevw2) + sq16(max_width - curw2);
|
|
|
|
if (delta1 > delta2 && curw2 <= max_width)
|
|
{
|
|
/* Do the change. */
|
|
uint16_t chars;
|
|
|
|
chars = previous->last_word.chars;
|
|
previous->chars -= chars;
|
|
current->chars += chars;
|
|
previous->width -= previous->last_word.word + previous->last_word.space;
|
|
current->width += previous->last_word.word + previous->last_word.space;
|
|
previous->last_word = previous->last_word_2;
|
|
|
|
while (chars--) mf_rewind(¤t->start);
|
|
}
|
|
}
|
|
|
|
void mf_wordwrap(const struct mf_font_s *font, int16_t width,
|
|
mf_str text, mf_line_callback_t callback, void *state)
|
|
{
|
|
struct linelen_s current = { 0 };
|
|
struct linelen_s previous = { 0 };
|
|
bool full;
|
|
|
|
current.start = text;
|
|
|
|
while (*text)
|
|
{
|
|
full = !append_word(font, width, ¤t, &text);
|
|
|
|
if (full || current.linebreak)
|
|
{
|
|
if (!current.chars)
|
|
{
|
|
/* We have a very long word. We must just cut it off at some
|
|
* point. */
|
|
while (append_char(font, width, ¤t, &text));
|
|
}
|
|
|
|
if (previous.chars)
|
|
{
|
|
/* Tune the length and dispatch the previous line. */
|
|
if (!previous.linebreak && !current.linebreak)
|
|
tune_lines(¤t, &previous, width);
|
|
|
|
if (!callback(previous.start, previous.chars, state))
|
|
return;
|
|
}
|
|
|
|
previous = current;
|
|
current.start = text;
|
|
current.chars = 0;
|
|
current.width = 0;
|
|
current.linebreak = false;
|
|
current.last_word.word = 0;
|
|
current.last_word.space = 0;
|
|
current.last_word.chars = 0;
|
|
}
|
|
}
|
|
|
|
/* Dispatch the last lines. */
|
|
if (previous.chars)
|
|
{
|
|
if (!callback(previous.start, previous.chars, state))
|
|
return;
|
|
}
|
|
|
|
if (current.chars)
|
|
callback(current.start, current.chars, state);
|
|
}
|
|
|
|
void mf_text_draw_area(const struct mf_font_s *font, int16_t width,
|
|
mf_str text,
|
|
int *total_height_in_rows,
|
|
int *max_pixels_per_row)
|
|
{
|
|
struct linelen_s current = { 0 };
|
|
struct linelen_s previous = { 0 };
|
|
bool full;
|
|
|
|
current.start = text;
|
|
*total_height_in_rows = 0;
|
|
*max_pixels_per_row = 0;
|
|
|
|
while (*text)
|
|
{
|
|
full = !append_word(font, width, ¤t, &text);
|
|
|
|
if (full || current.linebreak)
|
|
{
|
|
if (!current.chars)
|
|
{
|
|
/* We have a very long word. We must just cut it off at some
|
|
* point. */
|
|
while (append_char(font, width, ¤t, &text));
|
|
}
|
|
|
|
if (previous.chars)
|
|
{
|
|
/* Tune the length and dispatch the previous line. */
|
|
if (!previous.linebreak && !current.linebreak)
|
|
tune_lines(¤t, &previous, width);
|
|
*total_height_in_rows += 1;
|
|
}
|
|
|
|
if ( *max_pixels_per_row < current.width )
|
|
*max_pixels_per_row = current.width;
|
|
previous = current;
|
|
current.start = text;
|
|
current.chars = 0;
|
|
current.width = 0;
|
|
current.linebreak = false;
|
|
current.last_word.word = 0;
|
|
current.last_word.space = 0;
|
|
current.last_word.chars = 0;
|
|
}
|
|
}
|
|
|
|
/* Dispatch the last lines. */
|
|
if (previous.chars)
|
|
{
|
|
if ( *max_pixels_per_row < previous.width )
|
|
*max_pixels_per_row = previous.width;
|
|
*total_height_in_rows += 1;
|
|
}
|
|
|
|
if (current.chars) {
|
|
if ( *max_pixels_per_row < current.width )
|
|
*max_pixels_per_row = current.width;
|
|
*total_height_in_rows += 1;
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
void mf_wordwrap(const struct mf_font_s *font, int16_t width,
|
|
mf_str text, mf_line_callback_t callback, void *state)
|
|
{
|
|
mf_str orig = text;
|
|
mf_str linestart;
|
|
|
|
/* Current line width and character count */
|
|
int16_t lw_cur = 0, cc_cur = 0;
|
|
|
|
/* Previous wrap point */
|
|
int16_t cc_prev;
|
|
mf_str ls_prev;
|
|
|
|
linestart = text;
|
|
|
|
while (*text)
|
|
{
|
|
cc_prev = 0;
|
|
ls_prev = text;
|
|
|
|
while (*text)
|
|
{
|
|
mf_char c;
|
|
int16_t new_width;
|
|
mf_str tmp;
|
|
|
|
tmp = text;
|
|
c = mf_getchar(&text);
|
|
new_width = lw_cur + mf_character_width(font, c);
|
|
|
|
if (c == '\n')
|
|
{
|
|
cc_prev = cc_cur + 1;
|
|
ls_prev = text;
|
|
break;
|
|
}
|
|
|
|
if (new_width > width)
|
|
{
|
|
text = tmp;
|
|
break;
|
|
}
|
|
|
|
cc_cur++;
|
|
lw_cur = new_width;
|
|
|
|
if (is_wrap_space(c))
|
|
{
|
|
cc_prev = cc_cur;
|
|
ls_prev = text;
|
|
}
|
|
}
|
|
|
|
/* Handle unbreakable words */
|
|
if (cc_prev == 0)
|
|
{
|
|
cc_prev = cc_cur;
|
|
ls_prev = text;
|
|
}
|
|
|
|
if (!callback(linestart, cc_prev, state))
|
|
return;
|
|
|
|
linestart = ls_prev;
|
|
text = linestart;
|
|
lw_cur = 0;
|
|
cc_cur = 0;
|
|
}
|
|
}
|
|
|
|
#endif
|