1 /***************************************************************************
2 * Copyright (C) 2012~2012 by Yichao Yu *
3 * yyc1992@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #include "fcitx/fcitx.h"
22 #include "config.h"
23
24 #include "fcitx/ime.h"
25 #include "fcitx/instance.h"
26 #include "fcitx/context.h"
27 #include "fcitx/module.h"
28 #include "fcitx/frontend.h"
29 #include "fcitx-config/xdg.h"
30 #include "fcitx-utils/log.h"
31 #include "fcitx-utils/utf8.h"
32 #include <sys/stat.h>
33 #include <time.h>
34 #if defined(__linux__) || defined(__GLIBC__)
35 #include <endian.h>
36 #else
37 #include <sys/endian.h>
38 #endif
39
40 #include "spell-internal.h"
41 #include "spell-custom.h"
42 #include "spell-custom-dict.h"
43
44 /**
45 * update custom dict, if the dictionary of that language
46 * cannot be found, keep the current one until the next successful loading.
47 **/
48 boolean
SpellCustomLoadDict(FcitxSpell * spell,const char * lang)49 SpellCustomLoadDict(FcitxSpell *spell, const char *lang)
50 {
51 SpellCustomDict *custom_dict;
52 if (spell->custom_saved_lang &&
53 !strcmp(spell->custom_saved_lang, lang)) {
54 free(spell->custom_saved_lang);
55 spell->custom_saved_lang = NULL;
56 return false;
57 }
58 custom_dict = SpellCustomNewDict(spell, lang);
59 if (custom_dict) {
60 if (spell->custom_saved_lang) {
61 free(spell->custom_saved_lang);
62 spell->custom_saved_lang = NULL;
63 }
64 if (spell->custom_dict)
65 SpellCustomFreeDict(spell, spell->custom_dict);
66 spell->custom_dict = custom_dict;
67 return true;
68 }
69 if (!spell->custom_dict || !spell->dictLang)
70 return false;
71 if (spell->custom_saved_lang)
72 return false;
73 spell->custom_saved_lang = strdup(spell->dictLang);
74 return false;
75 }
76
77 /* Init work is done in set lang, nothing else to init here */
78 /* boolean */
79 /* SpellCustomInit(FcitxSpell *spell) */
80 /* { */
81 /* return true; */
82 /* } */
83
84 static int
SpellCustomGetDistance(SpellCustomDict * custom_dict,const char * word,const char * dict,int word_len)85 SpellCustomGetDistance(SpellCustomDict *custom_dict, const char *word,
86 const char *dict, int word_len)
87 {
88 #define REPLACE_WEIGHT 3
89 #define INSERT_WEIGHT 3
90 #define REMOVE_WEIGHT 3
91 #define END_WEIGHT 1
92 /*
93 * three kinds of error, replace, insert and remove
94 * replace means apple vs aplle
95 * insert means apple vs applee
96 * remove means apple vs aple
97 *
98 * each error need to follow a correct match.
99 *
100 * number of "remove error" shoud be no more than "maxremove"
101 * while maxremove equals to (length - 2) / 3
102 *
103 * and the total error number should be no more than "maxdiff"
104 * while maxdiff equales to length / 3.
105 */
106 int replace = 0;
107 int insert = 0;
108 int remove = 0;
109 int diff = 0;
110 int maxdiff;
111 int maxremove;
112 unsigned int cur_word_c;
113 unsigned int cur_dict_c;
114 unsigned int next_word_c;
115 unsigned int next_dict_c;
116 maxdiff = word_len / 3;
117 maxremove = (word_len - 2) / 3;
118 word = fcitx_utf8_get_char(word, &cur_word_c);
119 dict = fcitx_utf8_get_char(dict, &cur_dict_c);
120 while ((diff = replace + insert + remove) <= maxdiff &&
121 remove <= maxremove) {
122 /*
123 * cur_word_c and cur_dict_c are the current characters
124 * and dict and word are pointing to the next one.
125 */
126 if (!cur_word_c) {
127 return ((replace * REPLACE_WEIGHT + insert * INSERT_WEIGHT
128 + remove * REMOVE_WEIGHT) +
129 (cur_dict_c ?
130 (fcitx_utf8_strlen(dict) + 1) * END_WEIGHT : 0));
131 }
132 word = fcitx_utf8_get_char(word, &next_word_c);
133
134 /* check remove error */
135 if (!cur_dict_c) {
136 if (next_word_c)
137 return -1;
138 remove++;
139 if (diff <= maxdiff && remove <= maxremove) {
140 return (replace * REPLACE_WEIGHT + insert * INSERT_WEIGHT
141 + remove * REMOVE_WEIGHT);
142 }
143 return -1;
144 }
145 dict = fcitx_utf8_get_char(dict, &next_dict_c);
146 if (cur_word_c == cur_dict_c ||
147 (custom_dict->word_comp_func &&
148 custom_dict->word_comp_func(cur_word_c, cur_dict_c))) {
149 cur_word_c = next_word_c;
150 cur_dict_c = next_dict_c;
151 continue;
152 }
153 if (next_word_c == cur_dict_c ||
154 (custom_dict->word_comp_func && next_word_c &&
155 custom_dict->word_comp_func(next_word_c, cur_dict_c))) {
156 word = fcitx_utf8_get_char(word, &cur_word_c);
157 cur_dict_c = next_dict_c;
158 remove++;
159 continue;
160 }
161
162 /* check insert error */
163 if (cur_word_c == next_dict_c ||
164 (custom_dict->word_comp_func && next_dict_c &&
165 custom_dict->word_comp_func(cur_word_c, next_dict_c))) {
166 cur_word_c = next_word_c;
167 dict = fcitx_utf8_get_char(dict, &cur_dict_c);
168 insert++;
169 continue;
170 }
171
172 /* check replace error */
173 if (next_word_c == next_dict_c ||
174 (custom_dict->word_comp_func && next_word_c && next_dict_c &&
175 custom_dict->word_comp_func(next_word_c, next_dict_c))) {
176 if (next_word_c) {
177 dict = fcitx_utf8_get_char(dict, &cur_dict_c);
178 word = fcitx_utf8_get_char(word, &cur_word_c);
179 } else {
180 cur_word_c = 0;
181 cur_dict_c = 0;
182 }
183 replace++;
184 continue;
185 }
186 break;
187 }
188 return -1;
189 }
190
191 // TODO add frequency
192 static int
SpellCustomCWordCompare(const void * a,const void * b)193 SpellCustomCWordCompare(const void *a, const void *b)
194 {
195 return (int)(((SpellCustomCWord*)a)->dist - ((SpellCustomCWord*)b)->dist);
196 }
197
198 SpellHint*
SpellCustomHintWords(FcitxSpell * spell,unsigned int len_limit)199 SpellCustomHintWords(FcitxSpell *spell, unsigned int len_limit)
200 {
201 SpellCustomCWord clist[len_limit + 1];
202 int i;
203 unsigned int num = 0;
204 int word_type = 0;
205 SpellCustomDict *dict = spell->custom_dict;
206 const char *word;
207 const char *prefix = NULL;
208 int prefix_len = 0;
209 const char *real_word;
210 SpellHint *res;
211 int word_len;
212 if (!SpellCustomCheck(spell))
213 return NULL;
214 if (!*spell->current_str)
215 return NULL;
216 word = spell->current_str;
217 real_word = word;
218 if (dict->delim && *dict->delim) {
219 size_t delta;
220 while (real_word[delta = strcspn(real_word, dict->delim)]) {
221 prefix = word;
222 real_word += delta + 1;
223 }
224 prefix_len = prefix ? real_word - prefix : 0;
225 }
226 if (!*real_word)
227 return NULL;
228 if (dict->word_check_func)
229 word_type = dict->word_check_func(real_word);
230 word_len = fcitx_utf8_strlen(real_word);
231 for (i = 0;i < dict->words_count;i++) {
232 int dist;
233 if ((dist = SpellCustomGetDistance(
234 dict, real_word, dict->map + dict->words[i], word_len)) >= 0) {
235 int j = num;
236 clist[j].word = dict->map + dict->words[i];
237 clist[j].dist = dist;
238 if (num < len_limit)
239 num++;
240 for (;j > 0;j--) {
241 if (SpellCustomCWordCompare(clist + j - 1, clist + j) > 0) {
242 SpellCustomCWord tmp = clist[j];
243 clist[j] = clist[j - 1];
244 clist[j - 1] = tmp;
245 continue;
246 }
247 break;
248 }
249 }
250 }
251 res = SpellHintListWithPrefix(num, prefix, prefix_len,
252 &clist->word, sizeof(SpellCustomCWord));
253 if (!res)
254 return NULL;
255 if (dict->hint_cmplt_func)
256 dict->hint_cmplt_func(res, word_type);
257 return res;
258 }
259
260 boolean
SpellCustomCheck(FcitxSpell * spell)261 SpellCustomCheck(FcitxSpell *spell)
262 {
263 if (spell->custom_dict && !spell->custom_saved_lang)
264 return true;
265 return false;
266 }
267
268 void
SpellCustomDestroy(FcitxSpell * spell)269 SpellCustomDestroy(FcitxSpell *spell)
270 {
271 if (spell->custom_dict)
272 SpellCustomFreeDict(spell, spell->custom_dict);
273 if (spell->custom_saved_lang)
274 free(spell->custom_saved_lang);
275 }
276