Add ICU message format support
[chromium-blink-merge.git] / ui / base / ime / chromeos / character_composer.cc
blob20b27ef9fb75e55b947fa0274ea04718de8da0fe
1 // Copyright 2013 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.
5 #include "ui/base/ime/chromeos/character_composer.h"
7 #include <algorithm>
8 #include <iterator>
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/third_party/icu/icu_utf.h"
13 #include "ui/events/event.h"
14 #include "ui/events/keycodes/dom/dom_key.h"
15 #include "ui/events/keycodes/dom/keycode_converter.h"
16 #include "ui/events/keycodes/keyboard_codes.h"
18 namespace {
20 #include "ui/base/ime/chromeos/character_composer_data.h"
22 bool CheckCharacterComposeTable(
23 const ui::CharacterComposer::ComposeBuffer& compose_sequence,
24 uint32* composed_character) {
25 const ui::TreeComposeChecker kTreeComposeChecker(kCompositions);
26 return kTreeComposeChecker.CheckSequence(compose_sequence,
27 composed_character) !=
28 ui::ComposeChecker::CheckSequenceResult::NO_MATCH;
31 // Converts |character| to UTF16 string.
32 // Returns false when |character| is not a valid character.
33 bool UTF32CharacterToUTF16(uint32 character, base::string16* output) {
34 output->clear();
35 // Reject invalid character. (e.g. codepoint greater than 0x10ffff)
36 if (!CBU_IS_UNICODE_CHAR(character))
37 return false;
38 if (character) {
39 output->resize(CBU16_LENGTH(character));
40 size_t i = 0;
41 CBU16_APPEND_UNSAFE(&(*output)[0], i, character);
43 return true;
46 // Returns an hexadecimal digit integer (0 to 15) corresponding to |keycode|.
47 // -1 is returned when |keycode| cannot be a hexadecimal digit.
48 int KeycodeToHexDigit(unsigned int keycode) {
49 if (ui::VKEY_0 <= keycode && keycode <= ui::VKEY_9)
50 return keycode - ui::VKEY_0;
51 if (ui::VKEY_A <= keycode && keycode <= ui::VKEY_F)
52 return keycode - ui::VKEY_A + 10;
53 return -1; // |keycode| cannot be a hexadecimal digit.
56 } // namespace
58 namespace ui {
60 CharacterComposer::CharacterComposer() : composition_mode_(KEY_SEQUENCE_MODE) {
63 CharacterComposer::~CharacterComposer() {
66 void CharacterComposer::Reset() {
67 compose_buffer_.clear();
68 hex_buffer_.clear();
69 composed_character_.clear();
70 preedit_string_.clear();
71 composition_mode_ = KEY_SEQUENCE_MODE;
74 bool CharacterComposer::FilterKeyPress(const ui::KeyEvent& event) {
75 if (event.type() != ET_KEY_PRESSED && event.type() != ET_KEY_RELEASED)
76 return false;
78 // We don't care about modifier key presses.
79 if (KeycodeConverter::IsDomKeyForModifier(event.GetDomKey()))
80 return false;
82 composed_character_.clear();
83 preedit_string_.clear();
85 // When the user presses Ctrl+Shift+U, maybe switch to HEX_MODE.
86 // We don't care about other modifiers like Alt. When CapsLock is down, we
87 // do nothing because what we receive is Ctrl+Shift+u (not U).
88 if (event.key_code() == VKEY_U &&
89 (event.flags() & (EF_SHIFT_DOWN | EF_CONTROL_DOWN | EF_CAPS_LOCK_DOWN)) ==
90 (EF_SHIFT_DOWN | EF_CONTROL_DOWN)) {
91 if (composition_mode_ == KEY_SEQUENCE_MODE && compose_buffer_.empty()) {
92 // There is no ongoing composition. Let's switch to HEX_MODE.
93 composition_mode_ = HEX_MODE;
94 UpdatePreeditStringHexMode();
95 return true;
99 // Filter key press in an appropriate manner.
100 switch (composition_mode_) {
101 case KEY_SEQUENCE_MODE:
102 return FilterKeyPressSequenceMode(event);
103 case HEX_MODE:
104 return FilterKeyPressHexMode(event);
105 default:
106 NOTREACHED();
107 return false;
111 bool CharacterComposer::FilterKeyPressSequenceMode(const KeyEvent& event) {
112 DCHECK(composition_mode_ == KEY_SEQUENCE_MODE);
113 compose_buffer_.push_back(
114 KeystrokeMeaning(event.GetDomKey(), event.GetCharacter()));
116 // Check compose table.
117 uint32 composed_character_utf32 = 0;
118 if (CheckCharacterComposeTable(compose_buffer_, &composed_character_utf32)) {
119 // Key press is recognized as a part of composition.
120 if (composed_character_utf32 != 0) {
121 // We get a composed character.
122 compose_buffer_.clear();
123 UTF32CharacterToUTF16(composed_character_utf32, &composed_character_);
125 return true;
127 // Key press is not a part of composition.
128 compose_buffer_.pop_back(); // Remove the keypress added this time.
129 if (!compose_buffer_.empty()) {
130 compose_buffer_.clear();
131 return true;
133 return false;
136 bool CharacterComposer::FilterKeyPressHexMode(const KeyEvent& event) {
137 DCHECK(composition_mode_ == HEX_MODE);
138 const size_t kMaxHexSequenceLength = 8;
139 base::char16 c = event.GetCharacter();
140 int hex_digit = 0;
141 if (base::IsHexDigit(c)) {
142 hex_digit = base::HexDigitToInt(c);
143 } else {
144 // With 101 keyboard, control + shift + 3 produces '#', but a user may
145 // have intended to type '3'. So, if a hexadecimal character was not found,
146 // suppose a user is holding shift key (and possibly control key, too) and
147 // try a character with modifier keys removed.
148 hex_digit = KeycodeToHexDigit(event.key_code());
150 if (hex_digit >= 0) {
151 if (hex_buffer_.size() < kMaxHexSequenceLength) {
152 // Add the key to the buffer if it is a hex digit.
153 hex_buffer_.push_back(hex_digit);
155 } else {
156 DomKey key = event.GetDomKey();
157 if (key == DomKey::ESCAPE) {
158 // Cancel composition when ESC is pressed.
159 Reset();
160 } else if (key == DomKey::ENTER || c == ' ') {
161 // Commit the composed character when Enter or space is pressed.
162 CommitHex();
163 } else if (key == DomKey::BACKSPACE) {
164 // Pop back the buffer when Backspace is pressed.
165 if (!hex_buffer_.empty()) {
166 hex_buffer_.pop_back();
167 } else {
168 // If there is no character in |hex_buffer_|, cancel composition.
169 Reset();
172 // Other keystrokes are ignored in hex composition mode.
174 UpdatePreeditStringHexMode();
175 return true;
178 void CharacterComposer::CommitHex() {
179 DCHECK(composition_mode_ == HEX_MODE);
180 uint32 composed_character_utf32 = 0;
181 for (size_t i = 0; i != hex_buffer_.size(); ++i) {
182 const uint32 digit = hex_buffer_[i];
183 DCHECK(0 <= digit && digit < 16);
184 composed_character_utf32 <<= 4;
185 composed_character_utf32 |= digit;
187 Reset();
188 UTF32CharacterToUTF16(composed_character_utf32, &composed_character_);
191 void CharacterComposer::UpdatePreeditStringHexMode() {
192 if (composition_mode_ != HEX_MODE) {
193 preedit_string_.clear();
194 return;
196 std::string preedit_string_ascii("u");
197 for (size_t i = 0; i != hex_buffer_.size(); ++i) {
198 const int digit = hex_buffer_[i];
199 DCHECK(0 <= digit && digit < 16);
200 preedit_string_ascii += digit <= 9 ? ('0' + digit) : ('a' + (digit - 10));
202 preedit_string_ = base::ASCIIToUTF16(preedit_string_ascii);
205 ComposeChecker::CheckSequenceResult TreeComposeChecker::CheckSequence(
206 const ui::CharacterComposer::ComposeBuffer& sequence,
207 uint32_t* composed_character) const {
208 *composed_character = 0;
209 if (sequence.size() > data_.maximum_sequence_length)
210 return CheckSequenceResult::NO_MATCH;
212 uint16_t tree_index = 0;
213 for (const auto& keystroke : sequence) {
214 DCHECK(tree_index < data_.tree_entries);
216 // If we are looking up a dead key, skip over the character tables.
217 if (keystroke.key == ui::DomKey::DEAD) {
218 tree_index += 2 * data_.tree[tree_index] + 1; // internal unicode table
219 tree_index += 2 * data_.tree[tree_index] + 1; // leaf unicode table
220 } else if (keystroke.key != ui::DomKey::CHARACTER) {
221 return CheckSequenceResult::NO_MATCH;
224 // Check the internal subtree table.
225 uint16_t result = 0;
226 uint16_t entries = data_.tree[tree_index++];
227 if (entries && Find(tree_index, entries, keystroke.character, &result)) {
228 tree_index = result;
229 continue;
232 // Skip over the internal subtree table and check the leaf table.
233 tree_index += 2 * entries;
234 entries = data_.tree[tree_index++];
235 if (entries && Find(tree_index, entries, keystroke.character, &result)) {
236 *composed_character = result;
237 return CheckSequenceResult::FULL_MATCH;
239 return CheckSequenceResult::NO_MATCH;
241 return CheckSequenceResult::PREFIX_MATCH;
244 bool TreeComposeChecker::Find(uint16_t index,
245 uint16_t size,
246 uint16_t key,
247 uint16_t* value) const {
248 struct TableEntry {
249 uint16_t key;
250 uint16_t value;
251 bool operator<(const TableEntry& other) const {
252 return this->key < other.key;
255 const TableEntry* a = reinterpret_cast<const TableEntry*>(&data_.tree[index]);
256 const TableEntry* z = a + size;
257 const TableEntry target = {key, 0};
258 const TableEntry* it = std::lower_bound(a, z, target);
259 if ((it != z) && (it->key == key)) {
260 *value = it->value;
261 return true;
263 return false;
266 } // namespace ui