| /* GTK - The GIMP Toolkit |
| * Copyright (C) 2015 Takao Fujiwara <takao.fujiwara1@gmail.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <gdk/gdk.h> |
| #include <glib.h> |
| #include <glib/gprintf.h> |
| #include <glib/gstdio.h> |
| #include <locale.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "gtkcomposetable.h" |
| #include "gtkimcontextsimple.h" |
| |
| |
| #define GTK_COMPOSE_TABLE_MAGIC "GtkComposeTable" |
| #define GTK_COMPOSE_TABLE_VERSION (2) |
| |
| /* Maximum length of sequences we parse */ |
| |
| #define MAX_COMPOSE_LEN 20 |
| |
| typedef struct { |
| gunichar *sequence; |
| char *value; |
| } GtkComposeData; |
| |
| |
| static void |
| gtk_compose_data_free (GtkComposeData *compose_data) |
| { |
| g_free (compose_data->sequence); |
| g_free (compose_data->value); |
| g_slice_free (GtkComposeData, compose_data); |
| } |
| |
| static void |
| gtk_compose_list_element_free (GtkComposeData *compose_data, gpointer data) |
| { |
| gtk_compose_data_free (compose_data); |
| } |
| |
| static gboolean |
| is_codepoint (const char *str) |
| { |
| int i; |
| |
| /* 'U' is not code point but 'U00C0' is code point */ |
| if (str[0] == '\0' || str[0] != 'U' || str[1] == '\0') |
| return FALSE; |
| |
| for (i = 1; str[i] != '\0'; i++) |
| { |
| if (!g_ascii_isxdigit (str[i])) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| parse_compose_value (GtkComposeData *compose_data, |
| const char *val, |
| const char *line) |
| { |
| const char *p; |
| GString *value; |
| gunichar ch; |
| char *endp; |
| |
| value = g_string_new (""); |
| |
| if (val[0] != '"') |
| { |
| g_warning ("Only strings supported after ':': %s: %s", val, line); |
| goto fail; |
| } |
| |
| p = val + 1; |
| while (*p) |
| { |
| if (*p == '\"') |
| { |
| compose_data->value = g_string_free (value, FALSE); |
| return TRUE; |
| } |
| |
| if (p[1] == '\0') |
| { |
| g_warning ("Missing closing '\"': %s: %s", val, line); |
| goto fail; |
| } |
| else if (*p == '\\') |
| { |
| if (p[1] == '"') |
| { |
| g_string_append_c (value, '"'); |
| p += 2; |
| } |
| else if (p[1] == '\\') |
| { |
| g_string_append_c (value, '\\'); |
| p += 2; |
| } |
| else if (p[1] >= '0' && p[1] < '8') |
| { |
| ch = g_ascii_strtoll (p + 1, &endp, 8); |
| if (ch == 0) |
| { |
| g_warning ("Invalid escape sequence: %s: %s", val, line); |
| goto fail; |
| } |
| g_string_append_unichar (value, ch); |
| p = endp; |
| } |
| else if (p[1] == 'x' || p[1] == 'X') |
| { |
| ch = g_ascii_strtoll (p + 2, &endp, 16); |
| if (ch == 0) |
| { |
| g_warning ("Invalid escape sequence: %s: %s", val, line); |
| goto fail; |
| } |
| g_string_append_unichar (value, ch); |
| p = endp; |
| } |
| else |
| { |
| g_warning ("Invalid escape sequence: %s: %s", val, line); |
| goto fail; |
| } |
| } |
| else |
| { |
| ch = g_utf8_get_char (p); |
| g_string_append_unichar (value, ch); |
| p = g_utf8_next_char (p); |
| } |
| } |
| |
| fail: |
| g_string_free (value, TRUE); |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| parse_compose_sequence (GtkComposeData *compose_data, |
| const char *seq, |
| const char *line) |
| { |
| char **words = g_strsplit (seq, "<", -1); |
| int i; |
| int n = 0; |
| |
| if (g_strv_length (words) < 2) |
| { |
| g_warning ("key sequence format is <a> <b>...: %s", line); |
| goto fail; |
| } |
| |
| for (i = 1; words[i] != NULL; i++) |
| { |
| char *start = words[i]; |
| char *end = strchr (words[i], '>'); |
| char *match; |
| gunichar codepoint; |
| |
| if (words[i][0] == '\0') |
| continue; |
| |
| if (start == NULL || end == NULL || end <= start) |
| { |
| g_warning ("key sequence format is <a> <b>...: %s", line); |
| goto fail; |
| } |
| |
| match = g_strndup (start, end - start); |
| |
| if (compose_data->sequence == NULL) |
| compose_data->sequence = g_malloc (sizeof (gunichar) * 2); |
| else |
| compose_data->sequence = g_realloc (compose_data->sequence, sizeof (gunichar) * (n + 2)); |
| |
| if (is_codepoint (match)) |
| { |
| codepoint = (gunichar) g_ascii_strtoll (match + 1, NULL, 16); |
| compose_data->sequence[n] = codepoint; |
| compose_data->sequence[n + 1] = 0; |
| } |
| else |
| { |
| codepoint = (gunichar) gdk_keyval_from_name (match); |
| compose_data->sequence[n] = codepoint; |
| compose_data->sequence[n + 1] = 0; |
| } |
| |
| if (codepoint == GDK_KEY_VoidSymbol) |
| g_warning ("Could not get code point of keysym %s", match); |
| g_free (match); |
| n++; |
| } |
| |
| g_strfreev (words); |
| if (0 == n || n > MAX_COMPOSE_LEN) |
| { |
| g_warning ("Suspicious compose sequence length (%d). Are you sure this is right?: %s", |
| n, line); |
| return FALSE; |
| } |
| |
| return TRUE; |
| |
| fail: |
| g_strfreev (words); |
| return FALSE; |
| } |
| |
| static void |
| parse_compose_line (GList **compose_list, |
| const char *line) |
| { |
| char **components = NULL; |
| GtkComposeData *compose_data = NULL; |
| |
| if (line[0] == '\0' || line[0] == '#') |
| return; |
| |
| if (g_str_has_prefix (line, "include ")) |
| return; |
| |
| components = g_strsplit (line, ":", 2); |
| |
| if (components[1] == NULL) |
| { |
| g_warning ("No delimiter ':': %s", line); |
| goto fail; |
| } |
| |
| compose_data = g_slice_new0 (GtkComposeData); |
| |
| if (!parse_compose_sequence (compose_data, g_strstrip (components[0]), line)) |
| goto fail; |
| |
| if (!parse_compose_value (compose_data, g_strstrip (components[1]), line)) |
| goto fail; |
| |
| g_strfreev (components); |
| |
| *compose_list = g_list_append (*compose_list, compose_data); |
| |
| return; |
| |
| fail: |
| g_strfreev (components); |
| if (compose_data) |
| gtk_compose_data_free (compose_data); |
| } |
| |
| extern const GtkComposeTableCompact gtk_compose_table_compact; |
| |
| static GList * |
| gtk_compose_list_parse_file (const char *compose_file) |
| { |
| char *contents = NULL; |
| char **lines = NULL; |
| gsize length = 0; |
| GError *error = NULL; |
| GList *compose_list = NULL; |
| int i; |
| |
| if (!g_file_get_contents (compose_file, &contents, &length, &error)) |
| { |
| g_warning ("%s", error->message); |
| g_error_free (error); |
| return NULL; |
| } |
| |
| lines = g_strsplit (contents, "\n", -1); |
| g_free (contents); |
| for (i = 0; lines[i] != NULL; i++) |
| parse_compose_line (&compose_list, lines[i]); |
| g_strfreev (lines); |
| |
| return compose_list; |
| } |
| |
| static GList * |
| gtk_compose_list_check_duplicated (GList *compose_list) |
| { |
| GList *list; |
| GList *removed_list = NULL; |
| GtkComposeData *compose_data; |
| |
| for (list = compose_list; list != NULL; list = list->next) |
| { |
| static guint16 keysyms[MAX_COMPOSE_LEN + 1]; |
| int i; |
| int n_compose = 0; |
| gboolean compose_finish; |
| gunichar output_char; |
| char buf[8] = { 0, }; |
| |
| compose_data = list->data; |
| |
| for (i = 0; i < MAX_COMPOSE_LEN + 1; i++) |
| keysyms[i] = 0; |
| |
| for (i = 0; i < MAX_COMPOSE_LEN + 1; i++) |
| { |
| gunichar codepoint = compose_data->sequence[i]; |
| keysyms[i] = (guint16) codepoint; |
| |
| if (codepoint == 0) |
| break; |
| |
| n_compose++; |
| } |
| |
| if (gtk_compose_table_compact_check (>k_compose_table_compact, |
| keysyms, n_compose, |
| &compose_finish, |
| NULL, |
| &output_char) && |
| compose_finish) |
| { |
| g_unichar_to_utf8 (output_char, buf); |
| if (strcmp (compose_data->value, buf) == 0) |
| removed_list = g_list_prepend (removed_list, compose_data); |
| } |
| else if (gtk_check_algorithmically (keysyms, n_compose, &output_char)) |
| { |
| g_unichar_to_utf8 (output_char, buf); |
| if (strcmp (compose_data->value, buf) == 0) |
| removed_list = g_list_prepend (removed_list, compose_data); |
| } |
| } |
| |
| for (list = removed_list; list != NULL; list = list->next) |
| { |
| compose_data = list->data; |
| compose_list = g_list_remove (compose_list, compose_data); |
| gtk_compose_data_free (compose_data); |
| } |
| |
| g_list_free (removed_list); |
| |
| return compose_list; |
| } |
| |
| static GList * |
| gtk_compose_list_check_uint16 (GList *compose_list) |
| { |
| GList *list; |
| GList *removed_list = NULL; |
| GtkComposeData *compose_data; |
| |
| for (list = compose_list; list != NULL; list = list->next) |
| { |
| int i; |
| |
| compose_data = list->data; |
| for (i = 0; i < MAX_COMPOSE_LEN; i++) |
| { |
| gunichar codepoint = compose_data->sequence[i]; |
| |
| if (codepoint == 0) |
| break; |
| |
| if (codepoint > 0xffff) |
| { |
| removed_list = g_list_prepend (removed_list, compose_data); |
| break; |
| } |
| } |
| } |
| |
| for (list = removed_list; list != NULL; list = list->next) |
| { |
| compose_data = list->data; |
| compose_list = g_list_remove (compose_list, compose_data); |
| gtk_compose_data_free (compose_data); |
| } |
| |
| g_list_free (removed_list); |
| |
| return compose_list; |
| } |
| |
| static GList * |
| gtk_compose_list_format_for_gtk (GList *compose_list, |
| int *p_max_compose_len, |
| int *p_n_index_stride) |
| { |
| GList *list; |
| GtkComposeData *compose_data; |
| int max_compose_len = 0; |
| int i; |
| gunichar codepoint; |
| |
| for (list = compose_list; list != NULL; list = list->next) |
| { |
| compose_data = list->data; |
| for (i = 0; i < MAX_COMPOSE_LEN + 1; i++) |
| { |
| codepoint = compose_data->sequence[i]; |
| if (codepoint == 0) |
| { |
| if (max_compose_len < i) |
| max_compose_len = i; |
| break; |
| } |
| } |
| } |
| |
| if (p_max_compose_len) |
| *p_max_compose_len = max_compose_len; |
| if (p_n_index_stride) |
| *p_n_index_stride = max_compose_len + 2; |
| |
| return compose_list; |
| } |
| |
| static int |
| gtk_compose_data_compare (gpointer a, |
| gpointer b, |
| gpointer data) |
| { |
| GtkComposeData *compose_data_a = a; |
| GtkComposeData *compose_data_b = b; |
| int max_compose_len = GPOINTER_TO_INT (data); |
| int i; |
| for (i = 0; i < max_compose_len; i++) |
| { |
| gunichar code_a = compose_data_a->sequence[i]; |
| gunichar code_b = compose_data_b->sequence[i]; |
| |
| if (code_a != code_b) |
| return code_a - code_b; |
| } |
| |
| return 0; |
| } |
| |
| /* Implemented from g_str_hash() */ |
| static guint32 |
| gtk_compose_table_data_hash (gconstpointer v, int length) |
| { |
| const guint16 *p, *head; |
| unsigned char c; |
| guint32 h = 5381; |
| |
| for (p = v, head = v; (p - head) < length; p++) |
| { |
| c = 0x00ff & (*p >> 8); |
| h = (h << 5) + h + c; |
| c = 0x00ff & *p; |
| h = (h << 5) + h + c; |
| } |
| |
| return h; |
| } |
| |
| static char * |
| gtk_compose_hash_get_cache_path (guint32 hash) |
| { |
| char *basename = NULL; |
| char *dir = NULL; |
| char *path = NULL; |
| |
| basename = g_strdup_printf ("%08x.cache", hash); |
| |
| dir = g_build_filename (g_get_user_cache_dir (), "gtk-4.0", "compose", NULL); |
| path = g_build_filename (dir, basename, NULL); |
| if (g_mkdir_with_parents (dir, 0755) != 0) |
| { |
| g_warning ("Failed to mkdir %s", dir); |
| g_free (path); |
| path = NULL; |
| } |
| |
| g_free (dir); |
| g_free (basename); |
| |
| return path; |
| } |
| |
| static char * |
| gtk_compose_table_serialize (GtkComposeTable *compose_table, |
| gsize *count) |
| { |
| char *p, *contents; |
| gsize length, total_length; |
| guint16 bytes; |
| const char *header = GTK_COMPOSE_TABLE_MAGIC; |
| const guint16 version = GTK_COMPOSE_TABLE_VERSION; |
| guint16 max_seq_len = compose_table->max_seq_len; |
| guint16 index_stride = max_seq_len + 2; |
| guint16 n_seqs = compose_table->n_seqs; |
| guint16 n_chars = compose_table->n_chars; |
| guint32 i; |
| |
| g_return_val_if_fail (compose_table != NULL, NULL); |
| g_return_val_if_fail (max_seq_len > 0, NULL); |
| g_return_val_if_fail (index_stride > 0, NULL); |
| |
| length = strlen (header); |
| total_length = length + sizeof (guint16) * (4 + index_stride * n_seqs) + n_chars; |
| if (count) |
| *count = total_length; |
| |
| p = contents = g_malloc (total_length); |
| |
| memcpy (p, header, length); |
| p += length; |
| |
| #define APPEND_GUINT16(elt) \ |
| bytes = GUINT16_TO_BE (elt); \ |
| memcpy (p, &bytes, sizeof (guint16)); \ |
| p += sizeof (guint16); |
| |
| APPEND_GUINT16 (version); |
| APPEND_GUINT16 (max_seq_len); |
| APPEND_GUINT16 (n_seqs); |
| APPEND_GUINT16 (n_chars); |
| |
| for (i = 0; i < (guint32) index_stride * n_seqs; i++) |
| { |
| APPEND_GUINT16 (compose_table->data[i]); |
| } |
| |
| if (compose_table->n_chars > 0) |
| memcpy (p, compose_table->char_data, compose_table->n_chars); |
| |
| #undef APPEND_GUINT16 |
| |
| return contents; |
| } |
| |
| static int |
| gtk_compose_table_find (gconstpointer data1, |
| gconstpointer data2) |
| { |
| const GtkComposeTable *compose_table = (const GtkComposeTable *) data1; |
| guint32 hash = (guint32) GPOINTER_TO_INT (data2); |
| return compose_table->id != hash; |
| } |
| |
| static GtkComposeTable * |
| gtk_compose_table_load_cache (const char *compose_file) |
| { |
| guint32 hash; |
| char *path = NULL; |
| char *contents = NULL; |
| char *p; |
| GStatBuf original_buf; |
| GStatBuf cache_buf; |
| gsize total_length; |
| GError *error = NULL; |
| guint16 bytes; |
| guint16 version; |
| guint16 max_seq_len; |
| guint16 index_stride; |
| guint16 n_seqs; |
| guint16 n_chars; |
| guint32 i; |
| guint16 *gtk_compose_seqs = NULL; |
| GtkComposeTable *retval; |
| char *char_data = NULL; |
| |
| hash = g_str_hash (compose_file); |
| if ((path = gtk_compose_hash_get_cache_path (hash)) == NULL) |
| return NULL; |
| if (!g_file_test (path, G_FILE_TEST_EXISTS)) |
| goto out_load_cache; |
| |
| g_stat (compose_file, &original_buf); |
| g_stat (path, &cache_buf); |
| if (original_buf.st_mtime > cache_buf.st_mtime) |
| goto out_load_cache; |
| if (!g_file_get_contents (path, &contents, &total_length, &error)) |
| { |
| g_warning ("Failed to get cache content %s: %s", path, error->message); |
| g_error_free (error); |
| goto out_load_cache; |
| } |
| |
| #define GET_GUINT16(elt) \ |
| memcpy (&bytes, p, sizeof (guint16)); \ |
| elt = GUINT16_FROM_BE (bytes); \ |
| p += sizeof (guint16); |
| |
| p = contents; |
| if (g_ascii_strncasecmp (p, GTK_COMPOSE_TABLE_MAGIC, |
| strlen (GTK_COMPOSE_TABLE_MAGIC)) != 0) |
| { |
| g_warning ("The file is not a GtkComposeTable cache file %s", path); |
| goto out_load_cache; |
| } |
| |
| p += strlen (GTK_COMPOSE_TABLE_MAGIC); |
| if (p - contents > total_length) |
| { |
| g_warning ("Broken cache content %s at head", path); |
| goto out_load_cache; |
| } |
| |
| GET_GUINT16 (version); |
| if (version != GTK_COMPOSE_TABLE_VERSION) |
| { |
| g_warning ("cache version is different %u != %u", |
| version, GTK_COMPOSE_TABLE_VERSION); |
| goto out_load_cache; |
| } |
| |
| GET_GUINT16 (max_seq_len); |
| GET_GUINT16 (n_seqs); |
| GET_GUINT16 (n_chars); |
| |
| if (max_seq_len == 0 || n_seqs == 0) |
| { |
| g_warning ("cache size is not correct %d %d", max_seq_len, n_seqs); |
| goto out_load_cache; |
| } |
| |
| index_stride = max_seq_len + 2; |
| gtk_compose_seqs = g_new0 (guint16, n_seqs * index_stride); |
| |
| for (i = 0; i < (guint32) index_stride * n_seqs; i++) |
| { |
| GET_GUINT16 (gtk_compose_seqs[i]); |
| } |
| |
| if (n_chars > 0) |
| { |
| char_data = g_new (char, n_chars + 1); |
| memcpy (char_data, p, n_chars); |
| char_data[n_chars] = '\0'; |
| } |
| |
| retval = g_new0 (GtkComposeTable, 1); |
| retval->data = gtk_compose_seqs; |
| retval->max_seq_len = max_seq_len; |
| retval->n_seqs = n_seqs; |
| retval->char_data = char_data; |
| retval->n_chars = n_chars; |
| retval->id = hash; |
| |
| g_free (contents); |
| g_free (path); |
| |
| return retval; |
| |
| #undef GET_GUINT16 |
| |
| out_load_cache: |
| g_free (gtk_compose_seqs); |
| g_free (char_data); |
| g_free (contents); |
| g_free (path); |
| return NULL; |
| } |
| |
| static void |
| gtk_compose_table_save_cache (GtkComposeTable *compose_table) |
| { |
| char *path = NULL; |
| char *contents = NULL; |
| GError *error = NULL; |
| gsize length = 0; |
| |
| if ((path = gtk_compose_hash_get_cache_path (compose_table->id)) == NULL) |
| return; |
| |
| contents = gtk_compose_table_serialize (compose_table, &length); |
| if (contents == NULL) |
| { |
| g_warning ("Failed to serialize compose table %s", path); |
| goto out_save_cache; |
| } |
| if (!g_file_set_contents (path, contents, length, &error)) |
| { |
| g_warning ("Failed to save compose table %s: %s", path, error->message); |
| g_error_free (error); |
| goto out_save_cache; |
| } |
| |
| out_save_cache: |
| g_free (contents); |
| g_free (path); |
| } |
| |
| static GtkComposeTable * |
| gtk_compose_table_new_with_list (GList *compose_list, |
| int max_compose_len, |
| int n_index_stride, |
| guint32 hash) |
| { |
| guint length; |
| guint n = 0; |
| int i, j; |
| guint16 *gtk_compose_seqs = NULL; |
| GList *list; |
| GtkComposeData *compose_data; |
| GtkComposeTable *retval = NULL; |
| gunichar codepoint; |
| GString *char_data; |
| |
| g_return_val_if_fail (compose_list != NULL, NULL); |
| |
| length = g_list_length (compose_list); |
| |
| gtk_compose_seqs = g_new0 (guint16, length * n_index_stride); |
| |
| char_data = g_string_new (""); |
| |
| for (list = compose_list; list != NULL; list = list->next) |
| { |
| compose_data = list->data; |
| for (i = 0; i < max_compose_len; i++) |
| { |
| if (compose_data->sequence[i] == 0) |
| { |
| for (j = i; j < max_compose_len; j++) |
| gtk_compose_seqs[n++] = 0; |
| break; |
| } |
| gtk_compose_seqs[n++] = (guint16) compose_data->sequence[i]; |
| } |
| |
| if (g_utf8_strlen (compose_data->value, -1) > 1) |
| { |
| if (char_data->len > 0) |
| g_string_append_c (char_data, 0); |
| |
| codepoint = char_data->len | (1 << 31); |
| |
| g_string_append (char_data, compose_data->value); |
| } |
| else |
| { |
| codepoint = g_utf8_get_char (compose_data->value); |
| g_assert ((codepoint & (1 << 31)) == 0); |
| } |
| |
| gtk_compose_seqs[n++] = (codepoint & 0xffff0000) >> 16; |
| gtk_compose_seqs[n++] = codepoint & 0xffff; |
| } |
| |
| retval = g_new0 (GtkComposeTable, 1); |
| retval->data = gtk_compose_seqs; |
| retval->max_seq_len = max_compose_len; |
| retval->n_seqs = length; |
| retval->id = hash; |
| retval->n_chars = char_data->len; |
| retval->char_data = g_string_free (char_data, FALSE); |
| |
| return retval; |
| } |
| |
| GtkComposeTable * |
| gtk_compose_table_new_with_file (const char *compose_file) |
| { |
| GList *compose_list = NULL; |
| GtkComposeTable *compose_table; |
| int max_compose_len = 0; |
| int n_index_stride = 0; |
| |
| g_assert (compose_file != NULL); |
| |
| compose_list = gtk_compose_list_parse_file (compose_file); |
| if (compose_list == NULL) |
| return NULL; |
| compose_list = gtk_compose_list_check_duplicated (compose_list); |
| compose_list = gtk_compose_list_check_uint16 (compose_list); |
| compose_list = gtk_compose_list_format_for_gtk (compose_list, |
| &max_compose_len, |
| &n_index_stride); |
| compose_list = g_list_sort_with_data (compose_list, |
| (GCompareDataFunc) gtk_compose_data_compare, |
| GINT_TO_POINTER (max_compose_len)); |
| if (compose_list == NULL) |
| { |
| g_warning ("compose file %s does not include any keys besides keys in en-us compose file", compose_file); |
| return NULL; |
| } |
| |
| compose_table = gtk_compose_table_new_with_list (compose_list, |
| max_compose_len, |
| n_index_stride, |
| g_str_hash (compose_file)); |
| g_list_free_full (compose_list, (GDestroyNotify) gtk_compose_list_element_free); |
| return compose_table; |
| } |
| |
| GSList * |
| gtk_compose_table_list_add_array (GSList *compose_tables, |
| const guint16 *data, |
| int max_seq_len, |
| int n_seqs) |
| { |
| guint32 hash; |
| GtkComposeTable *compose_table; |
| gsize n_index_stride; |
| gsize length; |
| int i; |
| guint16 *gtk_compose_seqs = NULL; |
| |
| g_return_val_if_fail (data != NULL, compose_tables); |
| g_return_val_if_fail (max_seq_len >= 0, compose_tables); |
| g_return_val_if_fail (n_seqs >= 0, compose_tables); |
| |
| n_index_stride = max_seq_len + 2; |
| if (!g_size_checked_mul (&length, n_index_stride, n_seqs)) |
| { |
| g_critical ("Overflow in the compose sequences"); |
| return compose_tables; |
| } |
| |
| hash = gtk_compose_table_data_hash (data, length); |
| |
| if (g_slist_find_custom (compose_tables, GINT_TO_POINTER (hash), gtk_compose_table_find) != NULL) |
| return compose_tables; |
| |
| gtk_compose_seqs = g_new0 (guint16, length); |
| for (i = 0; i < length; i++) |
| gtk_compose_seqs[i] = data[i]; |
| |
| compose_table = g_new (GtkComposeTable, 1); |
| compose_table->data = gtk_compose_seqs; |
| compose_table->max_seq_len = max_seq_len; |
| compose_table->n_seqs = n_seqs; |
| compose_table->id = hash; |
| compose_table->char_data = NULL; |
| compose_table->n_chars = 0; |
| |
| return g_slist_prepend (compose_tables, compose_table); |
| } |
| |
| GSList * |
| gtk_compose_table_list_add_file (GSList *compose_tables, |
| const char *compose_file) |
| { |
| guint32 hash; |
| GtkComposeTable *compose_table; |
| |
| g_return_val_if_fail (compose_file != NULL, compose_tables); |
| |
| hash = g_str_hash (compose_file); |
| if (g_slist_find_custom (compose_tables, GINT_TO_POINTER (hash), gtk_compose_table_find) != NULL) |
| return compose_tables; |
| |
| compose_table = gtk_compose_table_load_cache (compose_file); |
| if (compose_table != NULL) |
| return g_slist_prepend (compose_tables, compose_table); |
| |
| if ((compose_table = gtk_compose_table_new_with_file (compose_file)) == NULL) |
| return compose_tables; |
| |
| gtk_compose_table_save_cache (compose_table); |
| return g_slist_prepend (compose_tables, compose_table); |
| } |
| |
| static int |
| compare_seq (const void *key, const void *value) |
| { |
| int i = 0; |
| const guint16 *keysyms = key; |
| const guint16 *seq = value; |
| |
| while (keysyms[i]) |
| { |
| if (keysyms[i] < seq[i]) |
| return -1; |
| else if (keysyms[i] > seq[i]) |
| return 1; |
| |
| i++; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * gtk_compose_table_check: |
| * @table: the table to check |
| * @compose_buffer: the key vals to match |
| * @n_compose: number of non-zero key vals in @compose_buffer |
| * @compose_finish: (out): return location for whether there may be longer matches |
| * @compose_match: (out): return location for whether there is a match |
| * @output: (out) (caller-allocates): return location for the match values |
| * |
| * Looks for matches for a key sequence in @table. |
| * |
| * Returns: %TRUE if there were any matches, %FALSE otherwise |
| */ |
| gboolean |
| gtk_compose_table_check (const GtkComposeTable *table, |
| const guint16 *compose_buffer, |
| int n_compose, |
| gboolean *compose_finish, |
| gboolean *compose_match, |
| GString *output) |
| { |
| int row_stride = table->max_seq_len + 2; |
| guint16 *seq; |
| |
| *compose_finish = FALSE; |
| *compose_match = FALSE; |
| |
| g_string_set_size (output, 0); |
| |
| /* Will never match, if the sequence in the compose buffer is longer |
| * than the sequences in the table. Further, compare_seq (key, val) |
| * will overrun val if key is longer than val. |
| */ |
| if (n_compose > table->max_seq_len) |
| return FALSE; |
| |
| seq = bsearch (compose_buffer, |
| table->data, table->n_seqs, |
| sizeof (guint16) * row_stride, |
| compare_seq); |
| |
| if (seq) |
| { |
| guint16 *prev_seq; |
| |
| /* Back up to the first sequence that matches to make sure |
| * we find the exact match if there is one. |
| */ |
| while (seq > table->data) |
| { |
| prev_seq = seq - row_stride; |
| if (compare_seq (compose_buffer, prev_seq) != 0) |
| break; |
| seq = prev_seq; |
| } |
| |
| if (n_compose == table->max_seq_len || |
| seq[n_compose] == 0) /* complete sequence */ |
| { |
| guint16 *next_seq; |
| gunichar value; |
| |
| value = (seq[table->max_seq_len] << 16) | seq[table->max_seq_len + 1]; |
| if ((value & (1 << 31)) != 0) |
| g_string_append (output, &table->char_data[value & ~(1 << 31)]); |
| else |
| g_string_append_unichar (output, value); |
| |
| *compose_match = TRUE; |
| |
| /* We found a tentative match. See if there are any longer |
| * sequences containing this subsequence |
| */ |
| next_seq = seq + row_stride; |
| if (next_seq < table->data + row_stride * table->n_seqs) |
| { |
| if (compare_seq (compose_buffer, next_seq) == 0) |
| return TRUE; |
| } |
| |
| *compose_finish = TRUE; |
| return TRUE; |
| } |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static int |
| compare_seq_index (const void *key, const void *value) |
| { |
| const guint16 *keysyms = key; |
| const guint16 *seq = value; |
| |
| if (keysyms[0] < seq[0]) |
| return -1; |
| else if (keysyms[0] > seq[0]) |
| return 1; |
| |
| return 0; |
| } |
| |
| gboolean |
| gtk_compose_table_compact_check (const GtkComposeTableCompact *table, |
| const guint16 *compose_buffer, |
| int n_compose, |
| gboolean *compose_finish, |
| gboolean *compose_match, |
| gunichar *output_char) |
| { |
| int row_stride; |
| guint16 *seq_index; |
| guint16 *seq; |
| int i; |
| gboolean match; |
| gunichar value; |
| |
| if (compose_finish) |
| *compose_finish = FALSE; |
| if (compose_match) |
| *compose_match = FALSE; |
| if (output_char) |
| *output_char = 0; |
| |
| /* Will never match, if the sequence in the compose buffer is longer |
| * than the sequences in the table. Further, compare_seq (key, val) |
| * will overrun val if key is longer than val. |
| */ |
| if (n_compose > table->max_seq_len) |
| return FALSE; |
| |
| seq_index = bsearch (compose_buffer, |
| table->data, |
| table->n_index_size, |
| sizeof (guint16) * table->n_index_stride, |
| compare_seq_index); |
| |
| if (!seq_index) |
| return FALSE; |
| |
| if (n_compose == 1) |
| return TRUE; |
| |
| seq = NULL; |
| match = FALSE; |
| value = 0; |
| |
| for (i = n_compose - 1; i < table->max_seq_len; i++) |
| { |
| row_stride = i + 1; |
| |
| if (seq_index[i + 1] - seq_index[i] > 0) |
| { |
| seq = bsearch (compose_buffer + 1, |
| table->data + seq_index[i], |
| (seq_index[i + 1] - seq_index[i]) / row_stride, |
| sizeof (guint16) * row_stride, |
| compare_seq); |
| |
| if (seq) |
| { |
| if (i == n_compose - 1) |
| { |
| value = seq[row_stride - 1]; |
| match = TRUE; |
| } |
| else |
| { |
| if (output_char) |
| *output_char = value; |
| if (match) |
| { |
| if (compose_match) |
| *compose_match = TRUE; |
| } |
| |
| return TRUE; |
| } |
| } |
| } |
| } |
| |
| if (match) |
| { |
| if (compose_match) |
| *compose_match = TRUE; |
| if (compose_finish) |
| *compose_finish = TRUE; |
| if (output_char) |
| *output_char = value; |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| /* Checks if a keysym is a dead key. |
| * Dead key keysym values are defined in ../gdk/gdkkeysyms.h and the |
| * first is GDK_KEY_dead_grave. As X.Org is updated, more dead keys |
| * are added and we need to update the upper limit. |
| */ |
| #define IS_DEAD_KEY(k) \ |
| ((k) >= GDK_KEY_dead_grave && (k) <= GDK_KEY_dead_greek) |
| |
| /* This function receives a sequence of Unicode characters and tries to |
| * normalize it (NFC). We check for the case where the resulting string |
| * has length 1 (single character). |
| * NFC normalisation normally rearranges diacritic marks, unless these |
| * belong to the same Canonical Combining Class. |
| * If they belong to the same canonical combining class, we produce all |
| * permutations of the diacritic marks, then attempt to normalize. |
| */ |
| static gboolean |
| check_normalize_nfc (gunichar *combination_buffer, |
| int n_compose) |
| { |
| gunichar *combination_buffer_temp; |
| char *combination_utf8_temp = NULL; |
| char *nfc_temp = NULL; |
| int n_combinations; |
| gunichar temp_swap; |
| int i; |
| |
| combination_buffer_temp = g_alloca (n_compose * sizeof (gunichar)); |
| |
| n_combinations = 1; |
| |
| for (i = 1; i < n_compose; i++) |
| n_combinations *= i; |
| |
| /* Xorg reuses dead_tilde for the perispomeni diacritic mark. |
| * We check if base character belongs to Greek Unicode block, |
| * and if so, we replace tilde with perispomeni. |
| */ |
| if (combination_buffer[0] >= 0x390 && combination_buffer[0] <= 0x3FF) |
| { |
| for (i = 1; i < n_compose; i++ ) |
| if (combination_buffer[i] == 0x303) |
| combination_buffer[i] = 0x342; |
| } |
| |
| memcpy (combination_buffer_temp, combination_buffer, n_compose * sizeof (gunichar) ); |
| |
| for (i = 0; i < n_combinations; i++) |
| { |
| g_unicode_canonical_ordering (combination_buffer_temp, n_compose); |
| combination_utf8_temp = g_ucs4_to_utf8 (combination_buffer_temp, n_compose, NULL, NULL, NULL); |
| nfc_temp = g_utf8_normalize (combination_utf8_temp, -1, G_NORMALIZE_NFC); |
| |
| if (g_utf8_strlen (nfc_temp, -1) == 1) |
| { |
| memcpy (combination_buffer, combination_buffer_temp, n_compose * sizeof (gunichar) ); |
| |
| g_free (combination_utf8_temp); |
| g_free (nfc_temp); |
| |
| return TRUE; |
| } |
| |
| g_free (combination_utf8_temp); |
| g_free (nfc_temp); |
| |
| if (n_compose > 2) |
| { |
| temp_swap = combination_buffer_temp[i % (n_compose - 1) + 1]; |
| combination_buffer_temp[i % (n_compose - 1) + 1] = combination_buffer_temp[(i+1) % (n_compose - 1) + 1]; |
| combination_buffer_temp[(i+1) % (n_compose - 1) + 1] = temp_swap; |
| } |
| else |
| break; |
| } |
| |
| return FALSE; |
| } |
| |
| gboolean |
| gtk_check_algorithmically (const guint16 *compose_buffer, |
| int n_compose, |
| gunichar *output_char) |
| |
| { |
| int i; |
| gunichar *combination_buffer; |
| char *combination_utf8, *nfc; |
| |
| combination_buffer = alloca (sizeof (gunichar) * (n_compose + 1)); |
| |
| if (output_char) |
| *output_char = 0; |
| |
| for (i = 0; i < n_compose && IS_DEAD_KEY (compose_buffer[i]); i++) |
| ; |
| |
| /* Allow at most 2 dead keys */ |
| if (i > 2) |
| return FALSE; |
| |
| /* Can't combine if there's no base character */ |
| if (i == n_compose) |
| return TRUE; |
| |
| if (i > 0 && i == n_compose - 1) |
| { |
| combination_buffer[0] = gdk_keyval_to_unicode (compose_buffer[i]); |
| combination_buffer[n_compose] = 0; |
| i--; |
| while (i >= 0) |
| { |
| switch (compose_buffer[i]) |
| { |
| #define CASE(keysym, unicode) \ |
| case GDK_KEY_dead_##keysym: combination_buffer[i+1] = unicode; break |
| |
| CASE (grave, 0x0300); |
| CASE (acute, 0x0301); |
| CASE (circumflex, 0x0302); |
| CASE (tilde, 0x0303); /* Also used with perispomeni, 0x342. */ |
| CASE (macron, 0x0304); |
| CASE (breve, 0x0306); |
| CASE (abovedot, 0x0307); |
| CASE (diaeresis, 0x0308); |
| CASE (abovering, 0x30A); |
| CASE (hook, 0x0309); |
| CASE (doubleacute, 0x030B); |
| CASE (caron, 0x030C); |
| CASE (cedilla, 0x0327); |
| CASE (ogonek, 0x0328); /* Legacy use for dasia, 0x314.*/ |
| CASE (iota, 0x0345); |
| CASE (voiced_sound, 0x3099); /* Per Markus Kuhn keysyms.txt file. */ |
| CASE (semivoiced_sound, 0x309A); /* Per Markus Kuhn keysyms.txt file. */ |
| CASE (belowdot, 0x0323); |
| CASE (horn, 0x031B); /* Legacy use for psili, 0x313 (or 0x343). */ |
| CASE (stroke, 0x335); |
| CASE (abovecomma, 0x0313); /* Equivalent to psili */ |
| CASE (abovereversedcomma, 0x0314); /* Equivalent to dasia */ |
| CASE (doublegrave, 0x30F); |
| CASE (belowring, 0x325); |
| CASE (belowmacron, 0x331); |
| CASE (belowcircumflex, 0x32D); |
| CASE (belowtilde, 0x330); |
| CASE (belowbreve, 0x32e); |
| CASE (belowdiaeresis, 0x324); |
| CASE (invertedbreve, 0x32f); |
| CASE (belowcomma, 0x326); |
| CASE (lowline, 0x332); |
| CASE (aboveverticalline, 0x30D); |
| CASE (belowverticalline, 0x329); |
| CASE (longsolidusoverlay, 0x338); |
| CASE (a, 0x363); |
| CASE (A, 0x363); |
| CASE (e, 0x364); |
| CASE (E, 0x364); |
| CASE (i, 0x365); |
| CASE (I, 0x365); |
| CASE (o, 0x366); |
| CASE (O, 0x366); |
| CASE (u, 0x367); |
| CASE (U, 0x367); |
| CASE (small_schwa, 0x1DEA); |
| CASE (capital_schwa, 0x1DEA); |
| #undef CASE |
| default: |
| combination_buffer[i+1] = gdk_keyval_to_unicode (compose_buffer[i]); |
| } |
| i--; |
| } |
| |
| /* If the buffer normalizes to a single character, then modify the order |
| * of combination_buffer accordingly, if necessary, and return TRUE. |
| */ |
| if (check_normalize_nfc (combination_buffer, n_compose)) |
| { |
| combination_utf8 = g_ucs4_to_utf8 (combination_buffer, -1, NULL, NULL, NULL); |
| nfc = g_utf8_normalize (combination_utf8, -1, G_NORMALIZE_NFC); |
| |
| if (output_char) |
| *output_char = g_utf8_get_char (nfc); |
| |
| g_free (combination_utf8); |
| g_free (nfc); |
| |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |