aboutsummaryrefslogtreecommitdiffstats
path: root/src/gdisp/mcufont/mf_wordwrap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gdisp/mcufont/mf_wordwrap.c')
-rw-r--r--src/gdisp/mcufont/mf_wordwrap.c290
1 files changed, 290 insertions, 0 deletions
diff --git a/src/gdisp/mcufont/mf_wordwrap.c b/src/gdisp/mcufont/mf_wordwrap.c
new file mode 100644
index 00000000..cc735a4e
--- /dev/null
+++ b/src/gdisp/mcufont/mf_wordwrap.c
@@ -0,0 +1,290 @@
+#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;
+
+ 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);
+ c = mf_getchar(text);
+ }
+
+ prev = *text;
+ while (c && is_wrap_space(c))
+ {
+ result->chars++;
+
+ if (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')
+ 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(&current->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 = {};
+ struct linelen_s previous = {};
+ bool full;
+
+ current.start = text;
+
+ while (*text)
+ {
+ full = !append_word(font, width, &current, &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, &current, &text));
+ }
+
+ if (previous.chars)
+ {
+ /* Tune the length and dispatch the previous line. */
+ if (!previous.linebreak && !current.linebreak)
+ tune_lines(&current, &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);
+}
+
+#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