| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/renderer/spellchecker/spellcheck.h" |
| |
| #include "base/file_util.h" |
| #include "base/metrics/histogram.h" |
| #include "base/time.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/common/render_messages.h" |
| #include "chrome/common/spellcheck_common.h" |
| #include "chrome/common/spellcheck_messages.h" |
| #include "content/public/renderer/render_thread.h" |
| #include "third_party/hunspell/src/hunspell/hunspell.hxx" |
| |
| using base::TimeTicks; |
| using content::RenderThread; |
| |
| SpellCheck::SpellCheck() |
| : file_(base::kInvalidPlatformFileValue), |
| auto_spell_correct_turned_on_(false), |
| is_using_platform_spelling_engine_(false), |
| initialized_(false) { |
| // Wait till we check the first word before doing any initializing. |
| } |
| |
| SpellCheck::~SpellCheck() { |
| } |
| |
| bool SpellCheck::OnControlMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(SpellCheck, message) |
| IPC_MESSAGE_HANDLER(SpellCheckMsg_Init, OnInit) |
| IPC_MESSAGE_HANDLER(SpellCheckMsg_WordAdded, OnWordAdded) |
| IPC_MESSAGE_HANDLER(SpellCheckMsg_EnableAutoSpellCorrect, |
| OnEnableAutoSpellCorrect) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| return handled; |
| } |
| |
| void SpellCheck::OnInit(IPC::PlatformFileForTransit bdict_file, |
| const std::vector<std::string>& custom_words, |
| const std::string& language, |
| bool auto_spell_correct) { |
| Init(IPC::PlatformFileForTransitToPlatformFile(bdict_file), |
| custom_words, language); |
| auto_spell_correct_turned_on_ = auto_spell_correct; |
| } |
| |
| void SpellCheck::OnWordAdded(const std::string& word) { |
| if (is_using_platform_spelling_engine_) |
| return; |
| |
| if (!hunspell_.get()) { |
| // Save it for later---add it when hunspell is initialized. |
| custom_words_.push_back(word); |
| } else { |
| AddWordToHunspell(word); |
| } |
| } |
| |
| void SpellCheck::OnEnableAutoSpellCorrect(bool enable) { |
| auto_spell_correct_turned_on_ = enable; |
| } |
| |
| void SpellCheck::Init(base::PlatformFile file, |
| const std::vector<std::string>& custom_words, |
| const std::string& language) { |
| initialized_ = true; |
| hunspell_.reset(); |
| bdict_file_.reset(); |
| file_ = file; |
| is_using_platform_spelling_engine_ = |
| file == base::kInvalidPlatformFileValue && !language.empty(); |
| |
| character_attributes_.SetDefaultLanguage(language); |
| |
| custom_words_.insert(custom_words_.end(), |
| custom_words.begin(), custom_words.end()); |
| |
| // We delay the actual initialization of hunspell until it is needed. |
| } |
| |
| bool SpellCheck::SpellCheckWord( |
| const char16* in_word, |
| int in_word_len, |
| int tag, |
| int* misspelling_start, |
| int* misspelling_len, |
| std::vector<string16>* optional_suggestions) { |
| DCHECK(in_word_len >= 0); |
| DCHECK(misspelling_start && misspelling_len) << "Out vars must be given."; |
| |
| // Do nothing if we need to delay initialization. (Rather than blocking, |
| // report the word as correctly spelled.) |
| if (InitializeIfNeeded()) |
| return true; |
| |
| // Do nothing if spell checking is disabled. |
| if (initialized_ && file_ == base::kInvalidPlatformFileValue && |
| !is_using_platform_spelling_engine_) { |
| return true; |
| } |
| |
| *misspelling_start = 0; |
| *misspelling_len = 0; |
| if (in_word_len == 0) |
| return true; // No input means always spelled correctly. |
| |
| string16 word; |
| int word_start; |
| int word_length; |
| if (!text_iterator_.IsInitialized()) |
| text_iterator_.Initialize(&character_attributes_, true); |
| text_iterator_.SetText(in_word, in_word_len); |
| while (text_iterator_.GetNextWord(&word, &word_start, &word_length)) { |
| // Found a word (or a contraction) that the spellchecker can check the |
| // spelling of. |
| if (CheckSpelling(word, tag)) |
| continue; |
| |
| // If the given word is a concatenated word of two or more valid words |
| // (e.g. "hello:hello"), we should treat it as a valid word. |
| if (IsValidContraction(word, tag)) |
| continue; |
| |
| *misspelling_start = word_start; |
| *misspelling_len = word_length; |
| |
| // Get the list of suggested words. |
| if (optional_suggestions) |
| FillSuggestionList(word, optional_suggestions); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| string16 SpellCheck::GetAutoCorrectionWord(const string16& word, int tag) { |
| string16 autocorrect_word; |
| if (!auto_spell_correct_turned_on_) |
| return autocorrect_word; // Return the empty string. |
| |
| int word_length = static_cast<int>(word.size()); |
| if (word_length < 2 || word_length > SpellCheckCommon::kMaxAutoCorrectWordSize) |
| return autocorrect_word; |
| |
| if (InitializeIfNeeded()) |
| return autocorrect_word; |
| |
| char16 misspelled_word[SpellCheckCommon::kMaxAutoCorrectWordSize + 1]; |
| const char16* word_char = word.c_str(); |
| for (int i = 0; i <= SpellCheckCommon::kMaxAutoCorrectWordSize; i++) { |
| if (i >= word_length) |
| misspelled_word[i] = 0; |
| else |
| misspelled_word[i] = word_char[i]; |
| } |
| |
| // Swap adjacent characters and spellcheck. |
| int misspelling_start, misspelling_len; |
| for (int i = 0; i < word_length - 1; i++) { |
| // Swap. |
| std::swap(misspelled_word[i], misspelled_word[i + 1]); |
| |
| // Check spelling. |
| misspelling_start = misspelling_len = 0; |
| SpellCheckWord(misspelled_word, word_length, tag, &misspelling_start, |
| &misspelling_len, NULL); |
| |
| // Make decision: if only one swap produced a valid word, then we want to |
| // return it. If we found two or more, we don't do autocorrection. |
| if (misspelling_len == 0) { |
| if (autocorrect_word.empty()) { |
| autocorrect_word.assign(misspelled_word); |
| } else { |
| autocorrect_word.clear(); |
| break; |
| } |
| } |
| |
| // Restore the swapped characters. |
| std::swap(misspelled_word[i], misspelled_word[i + 1]); |
| } |
| return autocorrect_word; |
| } |
| |
| void SpellCheck::InitializeHunspell() { |
| if (hunspell_.get()) |
| return; |
| |
| bdict_file_.reset(new file_util::MemoryMappedFile); |
| |
| if (bdict_file_->Initialize(file_)) { |
| TimeTicks debug_start_time = base::Histogram::DebugNow(); |
| |
| hunspell_.reset( |
| new Hunspell(bdict_file_->data(), bdict_file_->length())); |
| |
| // Add custom words to Hunspell. |
| for (std::vector<std::string>::iterator it = custom_words_.begin(); |
| it != custom_words_.end(); ++it) { |
| AddWordToHunspell(*it); |
| } |
| |
| DHISTOGRAM_TIMES("Spellcheck.InitTime", |
| base::Histogram::DebugNow() - debug_start_time); |
| } else { |
| NOTREACHED() << "Could not mmap spellchecker dictionary."; |
| } |
| } |
| |
| void SpellCheck::AddWordToHunspell(const std::string& word) { |
| if (!word.empty() && word.length() < MAXWORDUTF8LEN) |
| hunspell_->add(word.c_str()); |
| } |
| |
| bool SpellCheck::InitializeIfNeeded() { |
| if (is_using_platform_spelling_engine_) |
| return false; |
| |
| if (!initialized_) { |
| RenderThread::Get()->Send(new SpellCheckHostMsg_RequestDictionary); |
| initialized_ = true; |
| return true; |
| } |
| |
| // Don't initialize if hunspell is disabled. |
| if (file_ != base::kInvalidPlatformFileValue) |
| InitializeHunspell(); |
| |
| return false; |
| } |
| |
| // When called, relays the request to check the spelling to the proper |
| // backend, either hunspell or a platform-specific backend. |
| bool SpellCheck::CheckSpelling(const string16& word_to_check, int tag) { |
| bool word_correct = false; |
| |
| if (is_using_platform_spelling_engine_) { |
| RenderThread::Get()->Send(new SpellCheckHostMsg_PlatformCheckSpelling( |
| word_to_check, tag, &word_correct)); |
| } else { |
| std::string word_to_check_utf8(UTF16ToUTF8(word_to_check)); |
| // Hunspell shouldn't let us exceed its max, but check just in case |
| if (word_to_check_utf8.length() < MAXWORDUTF8LEN) { |
| if (hunspell_.get()) { |
| // |hunspell_->spell| returns 0 if the word is spelled correctly and |
| // non-zero otherwsie. |
| word_correct = (hunspell_->spell(word_to_check_utf8.c_str()) != 0); |
| } else { |
| // If |hunspell_| is NULL here, an error has occurred, but it's better |
| // to check rather than crash. |
| word_correct = true; |
| } |
| } |
| } |
| |
| return word_correct; |
| } |
| |
| void SpellCheck::FillSuggestionList( |
| const string16& wrong_word, |
| std::vector<string16>* optional_suggestions) { |
| if (is_using_platform_spelling_engine_) { |
| RenderThread::Get()->Send(new SpellCheckHostMsg_PlatformFillSuggestionList( |
| wrong_word, optional_suggestions)); |
| return; |
| } |
| |
| // If |hunspell_| is NULL here, an error has occurred, but it's better |
| // to check rather than crash. |
| if (!hunspell_.get()) |
| return; |
| |
| char** suggestions; |
| int number_of_suggestions = |
| hunspell_->suggest(&suggestions, UTF16ToUTF8(wrong_word).c_str()); |
| |
| // Populate the vector of WideStrings. |
| for (int i = 0; i < number_of_suggestions; i++) { |
| if (i < SpellCheckCommon::kMaxSuggestions) |
| optional_suggestions->push_back(UTF8ToUTF16(suggestions[i])); |
| free(suggestions[i]); |
| } |
| if (suggestions != NULL) |
| free(suggestions); |
| } |
| |
| // Returns whether or not the given string is a valid contraction. |
| // This function is a fall-back when the SpellcheckWordIterator class |
| // returns a concatenated word which is not in the selected dictionary |
| // (e.g. "in'n'out") but each word is valid. |
| bool SpellCheck::IsValidContraction(const string16& contraction, int tag) { |
| if (!contraction_iterator_.IsInitialized()) |
| contraction_iterator_.Initialize(&character_attributes_, false); |
| contraction_iterator_.SetText(contraction.c_str(), contraction.length()); |
| |
| string16 word; |
| int word_start; |
| int word_length; |
| while (contraction_iterator_.GetNextWord(&word, &word_start, &word_length)) { |
| if (!CheckSpelling(word, tag)) |
| return false; |
| } |
| return true; |
| } |