1/*
2 * Copyright (C) 2012 Igalia S.L.
3 * Copyright (C) 2012 Samsung Electronics
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library 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 GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include "config.h"
21#include "TextCheckerEnchant.h"
22
23#if ENABLE(SPELLCHECK)
24
25#include <unicode/ubrk.h>
26#include <wtf/Language.h>
27#include <wtf/NeverDestroyed.h>
28#include <wtf/text/CString.h>
29#include <wtf/text/TextBreakIterator.h>
30#include <wtf/text/WTFString.h>
31
32namespace WebCore {
33
34TextCheckerEnchant& TextCheckerEnchant::singleton()
35{
36 static NeverDestroyed<TextCheckerEnchant> textChecker;
37 return textChecker;
38}
39
40void TextCheckerEnchant::EnchantDictDeleter::operator()(EnchantDict* dictionary) const
41{
42 enchant_broker_free_dict(TextCheckerEnchant::singleton().m_broker, dictionary);
43}
44
45TextCheckerEnchant::TextCheckerEnchant()
46 : m_broker(enchant_broker_init())
47{
48}
49
50void TextCheckerEnchant::ignoreWord(const String& word)
51{
52 auto utf8Word = word.utf8();
53 for (auto& dictionary : m_enchantDictionaries)
54 enchant_dict_add_to_session(dictionary.get(), utf8Word.data(), utf8Word.length());
55}
56
57void TextCheckerEnchant::learnWord(const String& word)
58{
59 auto utf8Word = word.utf8();
60 for (auto& dictionary : m_enchantDictionaries)
61 enchant_dict_add(dictionary.get(), utf8Word.data(), utf8Word.length());
62}
63
64void TextCheckerEnchant::checkSpellingOfWord(const String& word, int start, int end, int& misspellingLocation, int& misspellingLength)
65{
66 CString string = word.substring(start, end - start).utf8();
67
68 for (auto& dictionary : m_enchantDictionaries) {
69 if (!enchant_dict_check(dictionary.get(), string.data(), string.length())) {
70 // Stop checking, this word is ok in at least one dict.
71 misspellingLocation = -1;
72 misspellingLength = 0;
73 return;
74 }
75 }
76
77 misspellingLocation = start;
78 misspellingLength = end - start;
79}
80
81void TextCheckerEnchant::checkSpellingOfString(const String& string, int& misspellingLocation, int& misspellingLength)
82{
83 // Assume that the words in the string are spelled correctly.
84 misspellingLocation = -1;
85 misspellingLength = 0;
86
87 if (!hasDictionary())
88 return;
89
90 UBreakIterator* iter = wordBreakIterator(string);
91 if (!iter)
92 return;
93
94 int start = ubrk_first(iter);
95 for (int end = ubrk_next(iter); end != UBRK_DONE; end = ubrk_next(iter)) {
96 if (isWordTextBreak(iter)) {
97 checkSpellingOfWord(string, start, end, misspellingLocation, misspellingLength);
98 // Stop checking the next words If the current word is misspelled, to do not overwrite its misspelled location and length.
99 if (misspellingLength)
100 return;
101 }
102 start = end;
103 }
104}
105
106Vector<String> TextCheckerEnchant::getGuessesForWord(const String& word)
107{
108 if (!hasDictionary())
109 return { };
110
111 static const size_t maximumNumberOfSuggestions = 10;
112
113 Vector<String> guesses;
114 auto utf8Word = word.utf8();
115 for (auto& dictionary : m_enchantDictionaries) {
116 size_t numberOfSuggestions;
117 size_t i;
118
119 char** suggestions = enchant_dict_suggest(dictionary.get(), utf8Word.data(), utf8Word.length(), &numberOfSuggestions);
120 if (numberOfSuggestions <= 0)
121 continue;
122
123 if (numberOfSuggestions > maximumNumberOfSuggestions)
124 numberOfSuggestions = maximumNumberOfSuggestions;
125
126 for (i = 0; i < numberOfSuggestions; i++)
127 guesses.append(String::fromUTF8(suggestions[i]));
128
129 enchant_dict_free_string_list(dictionary.get(), suggestions);
130 }
131
132 return guesses;
133}
134
135void TextCheckerEnchant::updateSpellCheckingLanguages(const Vector<String>& languages)
136{
137 Vector<UniqueEnchantDict> spellDictionaries;
138 if (!languages.isEmpty()) {
139 for (auto& language : languages) {
140 CString currentLanguage = language.utf8();
141 if (enchant_broker_dict_exists(m_broker, currentLanguage.data())) {
142 if (auto* dict = enchant_broker_request_dict(m_broker, currentLanguage.data()))
143 spellDictionaries.append(dict);
144 }
145 }
146 } else {
147 // Languages are not specified by user, try to get default language.
148 CString language = defaultLanguage().utf8();
149 if (enchant_broker_dict_exists(m_broker, language.data())) {
150 if (auto* dict = enchant_broker_request_dict(m_broker, language.data()))
151 spellDictionaries.append(dict);
152 } else {
153 // No dictionaries selected, we get the first one from the list.
154 CString dictLanguage;
155 enchant_broker_list_dicts(m_broker, [](const char* const languageTag, const char* const, const char* const, const char* const, void* data) {
156 auto* dictLanguage = static_cast<CString*>(data);
157 if (dictLanguage->isNull())
158 *dictLanguage = languageTag;
159 }, &dictLanguage);
160 if (!dictLanguage.isNull()) {
161 if (auto* dict = enchant_broker_request_dict(m_broker, dictLanguage.data()))
162 spellDictionaries.append(dict);
163 }
164 }
165 }
166 m_enchantDictionaries = WTFMove(spellDictionaries);
167}
168
169Vector<String> TextCheckerEnchant::loadedSpellCheckingLanguages() const
170{
171 if (!hasDictionary())
172 return { };
173
174 Vector<String> languages;
175 for (auto& dictionary : m_enchantDictionaries) {
176 enchant_dict_describe(dictionary.get(), [](const char* const languageTag, const char* const, const char* const, const char* const, void* data) {
177 auto* languages = static_cast<Vector<String>*>(data);
178 languages->append(String::fromUTF8(languageTag));
179 }, &languages);
180 }
181 return languages;
182}
183
184Vector<String> TextCheckerEnchant::availableSpellCheckingLanguages() const
185{
186 Vector<String> languages;
187 enchant_broker_list_dicts(m_broker, [](const char* const languageTag, const char* const, const char* const, const char* const, void* data) {
188 auto* languages = static_cast<Vector<String>*>(data);
189 languages->append(String::fromUTF8(languageTag));
190 }, &languages);
191 return languages;
192}
193
194} // namespace WebCore
195
196#endif // ENABLE(SPELLCHECK)
197
198