1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/renderer/spellchecker/hunspell_engine.h"
6
7#include <algorithm>
8#include <iterator>
9
10#include "base/files/memory_mapped_file.h"
11#include "base/time/time.h"
12#include "chrome/common/spellcheck_common.h"
13#include "chrome/common/spellcheck_messages.h"
14#include "content/public/renderer/render_thread.h"
15#include "third_party/hunspell/src/hunspell/hunspell.hxx"
16
17using content::RenderThread;
18
19namespace {
20  // Maximum length of words we actually check.
21  // 64 is the observed limits for OSX system checker.
22  const size_t kMaxCheckedLen = 64;
23
24  // Maximum length of words we provide suggestions for.
25  // 24 is the observed limits for OSX system checker.
26  const size_t kMaxSuggestLen = 24;
27
28  COMPILE_ASSERT(kMaxCheckedLen <= size_t(MAXWORDLEN), MaxCheckedLen_too_long);
29  COMPILE_ASSERT(kMaxSuggestLen <= kMaxCheckedLen, MaxSuggestLen_too_long);
30}  // namespace
31
32#if !defined(OS_MACOSX)
33SpellingEngine* CreateNativeSpellingEngine() {
34  return new HunspellEngine();
35}
36#endif
37
38HunspellEngine::HunspellEngine()
39    : hunspell_enabled_(false),
40      initialized_(false),
41      dictionary_requested_(false) {
42  // Wait till we check the first word before doing any initializing.
43}
44
45HunspellEngine::~HunspellEngine() {
46}
47
48void HunspellEngine::Init(base::File file) {
49  initialized_ = true;
50  hunspell_.reset();
51  bdict_file_.reset();
52  file_ = file.Pass();
53  hunspell_enabled_ = file_.IsValid();
54  // Delay the actual initialization of hunspell until it is needed.
55}
56
57void HunspellEngine::InitializeHunspell() {
58  if (hunspell_.get())
59    return;
60
61  bdict_file_.reset(new base::MemoryMappedFile);
62
63  if (bdict_file_->Initialize(file_.Pass())) {
64    hunspell_.reset(new Hunspell(bdict_file_->data(), bdict_file_->length()));
65  } else {
66    NOTREACHED() << "Could not mmap spellchecker dictionary.";
67  }
68}
69
70bool HunspellEngine::CheckSpelling(const base::string16& word_to_check,
71                                   int tag) {
72  // Assume all words that cannot be checked are valid. Since Chrome can't
73  // offer suggestions on them, either, there's no point in flagging them to
74  // the user.
75  bool word_correct = true;
76  std::string word_to_check_utf8(base::UTF16ToUTF8(word_to_check));
77
78  // Limit the size of checked words.
79  if (word_to_check_utf8.length() <= kMaxCheckedLen) {
80    // If |hunspell_| is NULL here, an error has occurred, but it's better
81    // to check rather than crash.
82    if (hunspell_.get()) {
83      // |hunspell_->spell| returns 0 if the word is misspelled.
84      word_correct = (hunspell_->spell(word_to_check_utf8.c_str()) != 0);
85    }
86  }
87
88  return word_correct;
89}
90
91void HunspellEngine::FillSuggestionList(
92    const base::string16& wrong_word,
93    std::vector<base::string16>* optional_suggestions) {
94  std::string wrong_word_utf8(base::UTF16ToUTF8(wrong_word));
95  if (wrong_word_utf8.length() > kMaxSuggestLen)
96    return;
97
98  // If |hunspell_| is NULL here, an error has occurred, but it's better
99  // to check rather than crash.
100  // TODO(groby): Technically, it's not. We should track down the issue.
101  if (!hunspell_.get())
102    return;
103
104  char** suggestions = NULL;
105  int number_of_suggestions =
106      hunspell_->suggest(&suggestions, wrong_word_utf8.c_str());
107
108  // Populate the vector of WideStrings.
109  for (int i = 0; i < number_of_suggestions; ++i) {
110    if (i < chrome::spellcheck_common::kMaxSuggestions)
111      optional_suggestions->push_back(base::UTF8ToUTF16(suggestions[i]));
112    free(suggestions[i]);
113  }
114  if (suggestions != NULL)
115    free(suggestions);
116}
117
118bool HunspellEngine::InitializeIfNeeded() {
119  if (!initialized_ && !dictionary_requested_) {
120    // RenderThread will not exist in test.
121    if (RenderThread::Get())
122      RenderThread::Get()->Send(new SpellCheckHostMsg_RequestDictionary);
123    dictionary_requested_ = true;
124    return true;
125  }
126
127  // Don't initialize if hunspell is disabled.
128  if (file_.IsValid())
129    InitializeHunspell();
130
131  return !initialized_;
132}
133
134bool HunspellEngine::IsEnabled() {
135  return hunspell_enabled_;
136}
137