From a87aca9c76c57770a69740b7e1ac9a56eb73f22c Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Mon, 26 Sep 2016 14:14:01 +0200
Subject: Refactor app_add_utf8_string()
---
src/sdtui.c | 185 +++++++++++++++++++++++++++++++++++++++++++-----------------
1 file changed, 133 insertions(+), 52 deletions(-)
(limited to 'src/sdtui.c')
diff --git a/src/sdtui.c b/src/sdtui.c
index fff326e..d41058b 100644
--- a/src/sdtui.c
+++ b/src/sdtui.c
@@ -344,77 +344,158 @@ app_is_character_in_locale (Application *self, gunichar ch)
return TRUE;
}
-/// Write the given UTF-8 string padded with spaces.
-/// @param[in] n The number of characters to write, or -1 for the whole string.
-/// @param[in] attrs Text attributes for the text, without padding.
-/// To change the attributes of all output, use attrset().
-/// @return The number of characters output.
-static gsize
-app_add_utf8_string (Application *self, const gchar *str, int attrs, int n)
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+// Necessary abstraction to simplify aligned, formatted character output
+
+typedef struct row_char RowChar;
+typedef struct row_buffer RowBuffer;
+
+struct row_char
{
- if (!n)
- return 0;
+ gunichar c; ///< Unicode codepoint
+ chtype attrs; ///< Special attributes
+ int width; ///< How many cells this takes
+};
+struct row_buffer
+{
+ Application *app; ///< Reference to Application
+ GArray *chars; ///< Characters
+ int total_width; ///< Total width of all characters
+};
+
+static void
+row_buffer_init (RowBuffer *self, Application *app)
+{
+ self->app = app;
+ self->chars = g_array_new (FALSE, TRUE, sizeof (RowChar));
+ self->total_width = 0;
+}
+
+#define row_buffer_free(self) g_array_unref ((self)->chars)
+
+/// Replace invalid chars and push all codepoints to the array w/ attributes.
+static void
+row_buffer_append (RowBuffer *self, const gchar *str, chtype attrs)
+{
glong ucs4_len;
gunichar *ucs4 = g_utf8_to_ucs4_fast (str, -1, &ucs4_len);
-
- // Replace invalid chars and compute how many characters fit in the limit
- gint cols, i;
- for (cols = i = 0; i < ucs4_len; i++)
+ for (glong i = 0; i < ucs4_len; i++)
{
- if (!app_is_character_in_locale (self, ucs4[i]))
- ucs4[i] = '?';
+ // XXX: this is very crude as it disrespects combining marks
+ gunichar c =
+ app_is_character_in_locale (self->app, ucs4[i]) ? ucs4[i] : '?';
+ struct row_char rc = { ucs4[i], attrs, unichar_width (c) };
+ g_array_append_val (self->chars, rc);
+ self->total_width += rc.width;
+ }
+ g_free (ucs4);
+}
- gint width = unichar_width (ucs4[i]);
- if (n >= 0 && cols + width > n)
- break;
- cols += width;
+/// Pop as many codepoints as needed to free up "space" character cells.
+/// Given the suffix nature of combining marks, this should work pretty fine.
+static gint
+row_buffer_pop_cells (RowBuffer *self, gint space)
+{
+ int made = 0;
+ while (self->chars->len && made < space)
+ {
+ guint last = self->chars->len - 1;
+ made += g_array_index (self->chars, RowChar, last).width;
+ g_array_remove_index (self->chars, last);
}
+ self->total_width -= made;
+ return made;
+}
- if (n < 0)
- n = cols;
+static void
+row_buffer_ellipsis (RowBuffer *self, int target, chtype attrs)
+{
+ row_buffer_pop_cells (self, self->total_width - target);
- // Append ellipsis if the whole string didn't fit
gunichar ellipsis = L'…';
- gint ellipsis_width = unichar_width (ellipsis);
-
- gint len = i;
- if (len != ucs4_len)
+ if (app_is_character_in_locale (self->app, ellipsis))
{
- if (app_is_character_in_locale (self, ellipsis))
- {
- if (cols + ellipsis_width > n)
- cols -= unichar_width (ucs4[len - 1]);
- else
- len++;
+ if (self->total_width >= target)
+ row_buffer_pop_cells (self, 1);
+ if (self->total_width + 1 <= target)
+ row_buffer_append (self, "…", attrs);
+ }
+ else if (target >= 3)
+ {
+ if (self->total_width >= target)
+ row_buffer_pop_cells (self, 3);
+ if (self->total_width + 3 <= target)
+ row_buffer_append (self, "...", attrs);
+ }
+}
- ucs4[len - 1] = ellipsis;
- cols += ellipsis_width;
- }
- else if (n >= 3 && len >= 3)
+static void
+row_buffer_print (RowBuffer *self, gunichar *ucs4, size_t len, chtype attrs)
+{
+ gsize locale_str_len;
+ guchar *str = (guchar *) g_convert_with_iconv
+ ((const gchar *) ucs4, len * sizeof *ucs4,
+ self->app->ucs4_to_locale, NULL, &locale_str_len, NULL);
+ g_return_if_fail (str != NULL);
+
+ for (gsize i = 0; i < locale_str_len; i++)
+ addch (str[i] | attrs);
+ g_free (str);
+}
+
+static void
+row_buffer_flush (RowBuffer *self)
+{
+ if (!self->chars->len)
+ return;
+
+ gunichar ucs4[self->chars->len];
+ for (guint i = 0; i < self->chars->len; i++)
+ ucs4[i] = g_array_index (self->chars, RowChar, i).c;
+
+ guint mark = 0;
+ for (guint i = 1; i < self->chars->len; i++)
+ {
+ chtype attrs = g_array_index (self->chars, RowChar, i - 1).attrs;
+ if (attrs != g_array_index (self->chars, RowChar, i).attrs)
{
- // With zero-width characters this overflows
- // It's just a fallback anyway
- cols -= unichar_width (ucs4[len - 1]); ucs4[len - 1] = '.';
- cols -= unichar_width (ucs4[len - 2]); ucs4[len - 2] = '.';
- cols -= unichar_width (ucs4[len - 3]); ucs4[len - 3] = '.';
- cols += 3;
+ row_buffer_print (self, ucs4 + mark, i - mark, attrs);
+ mark = i;
}
}
+ row_buffer_print (self, ucs4 + mark, self->chars->len - mark,
+ g_array_index (self->chars, RowChar, self->chars->len - 1).attrs);
+}
- guchar *locale_str;
- gsize locale_str_len;
- locale_str = (guchar *) g_convert_with_iconv ((const gchar *) ucs4,
- len * sizeof *ucs4, self->ucs4_to_locale, NULL, &locale_str_len, NULL);
- g_return_val_if_fail (locale_str != NULL, 0);
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- for (gsize i = 0; i < locale_str_len; i++)
- addch (locale_str[i] | attrs);
- while (cols++ < n)
+/// Write the given UTF-8 string padded with spaces.
+/// @param[in] n The number of characters to write, or -1 for the whole string.
+/// @param[in] attrs Text attributes for the text, without padding.
+/// To change the attributes of all output, use attrset().
+/// @return The number of characters output.
+static gsize
+app_add_utf8_string (Application *self, const gchar *str, chtype attrs, int n)
+{
+ if (!n)
+ return 0;
+
+ RowBuffer buf;
+ row_buffer_init (&buf, self);
+ row_buffer_append (&buf, str, attrs);
+
+ if (n < 0)
+ n = buf.total_width;
+ if (buf.total_width > n)
+ row_buffer_ellipsis (&buf, n, attrs);
+
+ row_buffer_flush (&buf);
+ for (int i = buf.total_width; i < n; i++)
addch (' ');
- g_free (locale_str);
- g_free (ucs4);
+ row_buffer_free (&buf);
return n;
}
--
cgit v1.2.3-70-g09d2