OLD | NEW |
| (Empty) |
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 "ui/base/ime/character_composer.h" | |
6 | |
7 #include <X11/Xlib.h> | |
8 #include <X11/Xutil.h> | |
9 | |
10 #include <algorithm> | |
11 #include <iterator> | |
12 | |
13 #include "base/strings/utf_string_conversions.h" | |
14 #include "base/third_party/icu/icu_utf.h" | |
15 // Note for Gtk removal: gdkkeysyms.h only contains a set of | |
16 // '#define GDK_KeyName 0xNNNN' macros and does not #include any Gtk headers. | |
17 #include "third_party/gtk+/gdk/gdkkeysyms.h" | |
18 #include "ui/base/glib/glib_integers.h" | |
19 #include "ui/events/event.h" | |
20 #include "ui/events/event_constants.h" | |
21 #include "ui/gfx/x/x11_types.h" | |
22 | |
23 // Note for Gtk removal: gtkimcontextsimpleseqs.h does not #include any Gtk | |
24 // headers and only contains one big guint16 array |gtk_compose_seqs_compact| | |
25 // which defines the main compose table. The table has internal linkage. | |
26 // The order of header inclusion is out of order because | |
27 // gtkimcontextsimpleseqs.h depends on guint16, which is defined in | |
28 // "ui/base/glib/glib_integers.h". | |
29 #include "third_party/gtk+/gtk/gtkimcontextsimpleseqs.h" | |
30 | |
31 namespace { | |
32 | |
33 // A black list for not composing dead keys. Once the key combination is listed | |
34 // below, the dead key won't work even when this is listed in | |
35 // gtkimcontextsimpleseqs.h. This only supports two keyevent sequenses. | |
36 // TODO(nona): Remove this hack. | |
37 const struct BlackListedDeadKey { | |
38 uint32 first_key; // target first key event. | |
39 uint32 second_key; // target second key event. | |
40 uint32 output_char; // the character to be inserted if the filter is matched. | |
41 bool consume; // true if the original key event will be consumed. | |
42 } kBlackListedDeadKeys[] = { | |
43 { GDK_KEY_dead_acute, GDK_KEY_m, GDK_KEY_apostrophe, false }, | |
44 { GDK_KEY_dead_acute, GDK_KEY_s, GDK_KEY_apostrophe, false }, | |
45 { GDK_KEY_dead_acute, GDK_KEY_t, GDK_KEY_apostrophe, false }, | |
46 { GDK_KEY_dead_acute, GDK_KEY_v, GDK_KEY_apostrophe, false }, | |
47 { GDK_KEY_dead_acute, GDK_KEY_dead_acute, GDK_KEY_apostrophe, true }, | |
48 }; | |
49 | |
50 typedef std::vector<unsigned int> ComposeBufferType; | |
51 | |
52 // An iterator class to apply std::lower_bound for composition table. | |
53 class SequenceIterator | |
54 : public std::iterator<std::random_access_iterator_tag, const uint16*> { | |
55 public: | |
56 SequenceIterator() : ptr_(NULL), stride_(0) {} | |
57 SequenceIterator(const uint16* ptr, int stride) | |
58 : ptr_(ptr), stride_(stride) {} | |
59 | |
60 const uint16* ptr() const {return ptr_;} | |
61 int stride() const {return stride_;} | |
62 | |
63 SequenceIterator& operator++() { | |
64 ptr_ += stride_; | |
65 return *this; | |
66 } | |
67 SequenceIterator& operator+=(int n) { | |
68 ptr_ += stride_*n; | |
69 return *this; | |
70 } | |
71 | |
72 const uint16* operator*() const {return ptr_;} | |
73 | |
74 private: | |
75 const uint16* ptr_; | |
76 int stride_; | |
77 }; | |
78 | |
79 inline SequenceIterator operator+(const SequenceIterator& l, int r) { | |
80 return SequenceIterator(l) += r; | |
81 } | |
82 | |
83 inline int operator-(const SequenceIterator& l, const SequenceIterator& r) { | |
84 const int d = l.ptr() - r.ptr(); | |
85 DCHECK(l.stride() == r.stride() && l.stride() > 0 && d%l.stride() == 0); | |
86 return d/l.stride(); | |
87 } | |
88 | |
89 inline bool operator==(const SequenceIterator& l, const SequenceIterator& r) { | |
90 DCHECK(l.stride() == r.stride()); | |
91 return l.ptr() == r.ptr(); | |
92 } | |
93 | |
94 inline bool operator!=(const SequenceIterator& l, const SequenceIterator& r) { | |
95 return !(l == r); | |
96 } | |
97 | |
98 // A function to compare key value. | |
99 inline int CompareSequenceValue(unsigned int l, unsigned int r) { | |
100 return (l > r) ? 1 : ((l < r) ? -1 : 0); | |
101 } | |
102 | |
103 // A template to make |CompareFunc| work like operator<. | |
104 // |CompareFunc| is required to implement a member function, | |
105 // int operator()(const ComposeBufferType& l, const uint16* r) const. | |
106 template<typename CompareFunc> | |
107 struct ComparatorAdoptor { | |
108 bool operator()(const ComposeBufferType& l, const uint16* r) const { | |
109 return CompareFunc()(l, r) == -1; | |
110 } | |
111 bool operator()(const uint16* l, const ComposeBufferType& r) const { | |
112 return CompareFunc()(r, l) == 1; | |
113 } | |
114 }; | |
115 | |
116 class ComposeChecker { | |
117 public: | |
118 // This class does not take the ownership of |data|, |data| should be alive | |
119 // for the lifetime of the object. | |
120 // |data| is a pointer to the head of an array of | |
121 // length (|max_sequence_length| + 2)*|n_sequences|. | |
122 // Every (|max_sequence_length| + 2) elements of |data| represent an entry. | |
123 // First |max_sequence_length| elements of an entry is the sequecne which | |
124 // composes the character represented by the last two elements of the entry. | |
125 ComposeChecker(const uint16* data, int max_sequence_length, int n_sequences); | |
126 bool CheckSequence(const ComposeBufferType& sequence, | |
127 uint32* composed_character) const; | |
128 | |
129 private: | |
130 struct CompareSequence { | |
131 int operator()(const ComposeBufferType& l, const uint16* r) const; | |
132 }; | |
133 | |
134 // This class does not take the ownership of |data_|, | |
135 // the dtor does not delete |data_|. | |
136 const uint16* data_; | |
137 int max_sequence_length_; | |
138 int n_sequences_; | |
139 int row_stride_; | |
140 | |
141 DISALLOW_COPY_AND_ASSIGN(ComposeChecker); | |
142 }; | |
143 | |
144 ComposeChecker::ComposeChecker(const uint16* data, | |
145 int max_sequence_length, | |
146 int n_sequences) | |
147 : data_(data), | |
148 max_sequence_length_(max_sequence_length), | |
149 n_sequences_(n_sequences), | |
150 row_stride_(max_sequence_length + 2) { | |
151 } | |
152 | |
153 bool ComposeChecker::CheckSequence(const ComposeBufferType& sequence, | |
154 uint32* composed_character) const { | |
155 const int sequence_length = sequence.size(); | |
156 if (sequence_length > max_sequence_length_) | |
157 return false; | |
158 // Find sequence in the table. | |
159 const SequenceIterator begin(data_, row_stride_); | |
160 const SequenceIterator end = begin + n_sequences_; | |
161 const SequenceIterator found = std::lower_bound( | |
162 begin, end, sequence, ComparatorAdoptor<CompareSequence>()); | |
163 if (found == end || CompareSequence()(sequence, *found) != 0) | |
164 return false; | |
165 | |
166 if (sequence_length == max_sequence_length_ || | |
167 (*found)[sequence_length] == 0) { | |
168 // |found| is not partially matching. It's fully matching. | |
169 if (found + 1 == end || | |
170 CompareSequence()(sequence, *(found + 1)) != 0) { | |
171 // There is no composition longer than |found| which matches to | |
172 // |sequence|. | |
173 const uint32 value = ((*found)[max_sequence_length_] << 16) | | |
174 (*found)[max_sequence_length_ + 1]; | |
175 *composed_character = value; | |
176 } | |
177 } | |
178 return true; | |
179 } | |
180 | |
181 int ComposeChecker::CompareSequence::operator()(const ComposeBufferType& l, | |
182 const uint16* r) const { | |
183 for(size_t i = 0; i < l.size(); ++i) { | |
184 const int compare_result = CompareSequenceValue(l[i], r[i]); | |
185 if(compare_result) | |
186 return compare_result; | |
187 } | |
188 return 0; | |
189 } | |
190 | |
191 | |
192 class ComposeCheckerWithCompactTable { | |
193 public: | |
194 // This class does not take the ownership of |data|, |data| should be alive | |
195 // for the lifetime of the object. | |
196 // First |index_size|*|index_stride| elements of |data| are an index table. | |
197 // Every |index_stride| elements of an index table are an index entry. | |
198 // If you are checking with a sequence of length N beginning with character C, | |
199 // you have to find an index entry whose first element is C, then get the N-th | |
200 // element of the index entry as the index. | |
201 // The index is pointing the element of |data| where the composition table for | |
202 // sequences of length N beginning with C is placed. | |
203 | |
204 ComposeCheckerWithCompactTable(const uint16* data, | |
205 int max_sequence_length, | |
206 int index_size, | |
207 int index_stride); | |
208 bool CheckSequence(const ComposeBufferType& sequence, | |
209 uint32* composed_character) const; | |
210 | |
211 private: | |
212 struct CompareSequenceFront { | |
213 int operator()(const ComposeBufferType& l, const uint16* r) const; | |
214 }; | |
215 struct CompareSequenceSkipFront { | |
216 int operator()(const ComposeBufferType& l, const uint16* r) const; | |
217 }; | |
218 | |
219 // This class does not take the ownership of |data_|, | |
220 // the dtor does not delete |data_|. | |
221 const uint16* data_; | |
222 int max_sequence_length_; | |
223 int index_size_; | |
224 int index_stride_; | |
225 }; | |
226 | |
227 ComposeCheckerWithCompactTable::ComposeCheckerWithCompactTable( | |
228 const uint16* data, | |
229 int max_sequence_length, | |
230 int index_size, | |
231 int index_stride) | |
232 : data_(data), | |
233 max_sequence_length_(max_sequence_length), | |
234 index_size_(index_size), | |
235 index_stride_(index_stride) { | |
236 } | |
237 | |
238 bool ComposeCheckerWithCompactTable::CheckSequence( | |
239 const ComposeBufferType& sequence, | |
240 uint32* composed_character) const { | |
241 const int compose_length = sequence.size(); | |
242 if (compose_length > max_sequence_length_) | |
243 return false; | |
244 // Find corresponding index for the first keypress. | |
245 const SequenceIterator index_begin(data_, index_stride_); | |
246 const SequenceIterator index_end = index_begin + index_size_; | |
247 const SequenceIterator index = | |
248 std::lower_bound(index_begin, index_end, sequence, | |
249 ComparatorAdoptor<CompareSequenceFront>()); | |
250 if (index == index_end || CompareSequenceFront()(sequence, *index) != 0) | |
251 return false; | |
252 if (compose_length == 1) | |
253 return true; | |
254 // Check for composition sequences. | |
255 for (int length = compose_length - 1; length < max_sequence_length_; | |
256 ++length) { | |
257 const uint16* table = data_ + (*index)[length]; | |
258 const uint16* table_next = data_ + (*index)[length + 1]; | |
259 if (table_next > table) { | |
260 // There are composition sequences for this |length|. | |
261 const int row_stride = length + 1; | |
262 const int n_sequences = (table_next - table)/row_stride; | |
263 const SequenceIterator table_begin(table, row_stride); | |
264 const SequenceIterator table_end = table_begin + n_sequences; | |
265 const SequenceIterator found = | |
266 std::lower_bound(table_begin, table_end, sequence, | |
267 ComparatorAdoptor<CompareSequenceSkipFront>()); | |
268 if (found != table_end && | |
269 CompareSequenceSkipFront()(sequence, *found) == 0) { | |
270 if (length == compose_length - 1) // Exact match. | |
271 *composed_character = (*found)[length]; | |
272 return true; | |
273 } | |
274 } | |
275 } | |
276 return false; | |
277 } | |
278 | |
279 int ComposeCheckerWithCompactTable::CompareSequenceFront::operator()( | |
280 const ComposeBufferType& l, const uint16* r) const { | |
281 return CompareSequenceValue(l[0], r[0]); | |
282 } | |
283 | |
284 int ComposeCheckerWithCompactTable::CompareSequenceSkipFront::operator()( | |
285 const ComposeBufferType& l, const uint16* r) const { | |
286 for(size_t i = 1; i < l.size(); ++i) { | |
287 const int compare_result = CompareSequenceValue(l[i], r[i - 1]); | |
288 if(compare_result) | |
289 return compare_result; | |
290 } | |
291 return 0; | |
292 } | |
293 | |
294 | |
295 // Additional table. | |
296 | |
297 // The difference between this and the default input method is the handling | |
298 // of C+acute - this method produces C WITH CEDILLA rather than C WITH ACUTE. | |
299 // For languages that use CCedilla and not acute, this is the preferred mapping, | |
300 // and is particularly important for pt_BR, where the us-intl keyboard is | |
301 // used extensively. | |
302 | |
303 const uint16 cedilla_compose_seqs[] = { | |
304 // LATIN_CAPITAL_LETTER_C_WITH_CEDILLA | |
305 GDK_KEY_dead_acute, GDK_KEY_C, 0, 0, 0, 0x00C7, | |
306 // LATIN_SMALL_LETTER_C_WITH_CEDILLA | |
307 GDK_KEY_dead_acute, GDK_KEY_c, 0, 0, 0, 0x00E7, | |
308 // LATIN_CAPITAL_LETTER_C_WITH_CEDILLA | |
309 GDK_KEY_Multi_key, GDK_KEY_apostrophe, GDK_KEY_C, 0, 0, 0x00C7, | |
310 // LATIN_SMALL_LETTER_C_WITH_CEDILLA | |
311 GDK_KEY_Multi_key, GDK_KEY_apostrophe, GDK_KEY_c, 0, 0, 0x00E7, | |
312 // LATIN_CAPITAL_LETTER_C_WITH_CEDILLA | |
313 GDK_KEY_Multi_key, GDK_KEY_C, GDK_KEY_apostrophe, 0, 0, 0x00C7, | |
314 // LATIN_SMALL_LETTER_C_WITH_CEDILLA | |
315 GDK_KEY_Multi_key, GDK_KEY_c, GDK_KEY_apostrophe, 0, 0, 0x00E7, | |
316 }; | |
317 | |
318 bool KeypressShouldBeIgnored(unsigned int keyval) { | |
319 switch(keyval) { | |
320 case GDK_KEY_Shift_L: | |
321 case GDK_KEY_Shift_R: | |
322 case GDK_KEY_Control_L: | |
323 case GDK_KEY_Control_R: | |
324 case GDK_KEY_Caps_Lock: | |
325 case GDK_KEY_Shift_Lock: | |
326 case GDK_KEY_Meta_L: | |
327 case GDK_KEY_Meta_R: | |
328 case GDK_KEY_Alt_L: | |
329 case GDK_KEY_Alt_R: | |
330 case GDK_KEY_Super_L: | |
331 case GDK_KEY_Super_R: | |
332 case GDK_KEY_Hyper_L: | |
333 case GDK_KEY_Hyper_R: | |
334 case GDK_KEY_Mode_switch: | |
335 case GDK_KEY_ISO_Level3_Shift: | |
336 return true; | |
337 default: | |
338 return false; | |
339 } | |
340 } | |
341 | |
342 bool CheckCharacterComposeTable(const ComposeBufferType& sequence, | |
343 uint32* composed_character) { | |
344 // Check cedilla compose table. | |
345 const ComposeChecker kCedillaComposeChecker( | |
346 cedilla_compose_seqs, 4, arraysize(cedilla_compose_seqs)/(4 + 2)); | |
347 if (kCedillaComposeChecker.CheckSequence(sequence, composed_character)) | |
348 return true; | |
349 | |
350 // Check main compose table. | |
351 const ComposeCheckerWithCompactTable kMainComposeChecker( | |
352 gtk_compose_seqs_compact, 5, 24, 6); | |
353 if (kMainComposeChecker.CheckSequence(sequence, composed_character)) | |
354 return true; | |
355 | |
356 return false; | |
357 } | |
358 | |
359 // Converts |character| to UTF16 string. | |
360 // Returns false when |character| is not a valid character. | |
361 bool UTF32CharacterToUTF16(uint32 character, string16* output) { | |
362 output->clear(); | |
363 // Reject invalid character. (e.g. codepoint greater than 0x10ffff) | |
364 if (!CBU_IS_UNICODE_CHAR(character)) | |
365 return false; | |
366 if (character) { | |
367 output->resize(CBU16_LENGTH(character)); | |
368 size_t i = 0; | |
369 CBU16_APPEND_UNSAFE(&(*output)[0], i, character); | |
370 } | |
371 return true; | |
372 } | |
373 | |
374 // Converts a X keycode to a X keysym with no modifiers. | |
375 KeySym XKeyCodeToXKeySym(unsigned int keycode) { | |
376 XDisplay* display = gfx::GetXDisplay(); | |
377 if (!display) | |
378 return NoSymbol; | |
379 | |
380 XKeyEvent x_key_event = {0}; | |
381 x_key_event.type = KeyPress; | |
382 x_key_event.display = display; | |
383 x_key_event.keycode = keycode; | |
384 return ::XLookupKeysym(&x_key_event, 0); | |
385 } | |
386 | |
387 // Returns an hexadecimal digit integer (0 to 15) corresponding to |keyval|. | |
388 // -1 is returned when |keyval| cannot be a hexadecimal digit. | |
389 int KeyvalToHexDigit(unsigned int keyval) { | |
390 if (GDK_KEY_0 <= keyval && keyval <= GDK_KEY_9) | |
391 return keyval - GDK_KEY_0; | |
392 if (GDK_KEY_a <= keyval && keyval <= GDK_KEY_f) | |
393 return keyval - GDK_KEY_a + 10; | |
394 if (GDK_KEY_A <= keyval && keyval <= GDK_KEY_F) | |
395 return keyval - GDK_KEY_A + 10; | |
396 return -1; // |keyval| cannot be a hexadecimal digit. | |
397 } | |
398 | |
399 } // namespace | |
400 | |
401 namespace ui { | |
402 | |
403 CharacterComposer::CharacterComposer() : composition_mode_(KEY_SEQUENCE_MODE) {} | |
404 | |
405 CharacterComposer::~CharacterComposer() {} | |
406 | |
407 void CharacterComposer::Reset() { | |
408 compose_buffer_.clear(); | |
409 composed_character_.clear(); | |
410 preedit_string_.clear(); | |
411 composition_mode_ = KEY_SEQUENCE_MODE; | |
412 } | |
413 | |
414 bool CharacterComposer::FilterKeyPress(const ui::KeyEvent& event) { | |
415 if (!event.HasNativeEvent() || | |
416 (event.type() != ET_KEY_PRESSED && event.type() != ET_KEY_RELEASED)) | |
417 return false; | |
418 | |
419 XEvent* xevent = event.native_event(); | |
420 DCHECK(xevent); | |
421 KeySym keysym = NoSymbol; | |
422 ::XLookupString(&xevent->xkey, NULL, 0, &keysym, NULL); | |
423 | |
424 return FilterKeyPressInternal(keysym, xevent->xkey.keycode, event.flags()); | |
425 } | |
426 | |
427 | |
428 bool CharacterComposer::FilterKeyPressInternal(unsigned int keyval, | |
429 unsigned int keycode, | |
430 int flags) { | |
431 composed_character_.clear(); | |
432 preedit_string_.clear(); | |
433 | |
434 // We don't care about modifier key presses. | |
435 if(KeypressShouldBeIgnored(keyval)) | |
436 return false; | |
437 | |
438 // When the user presses Ctrl+Shift+U, maybe switch to HEX_MODE. | |
439 // We don't care about other modifiers like Alt. When CapsLock is down, we | |
440 // do nothing because what we receive is Ctrl+Shift+u (not U). | |
441 if (keyval == GDK_KEY_U && (flags & EF_SHIFT_DOWN) && | |
442 (flags & EF_CONTROL_DOWN)) { | |
443 if (composition_mode_ == KEY_SEQUENCE_MODE && compose_buffer_.empty()) { | |
444 // There is no ongoing composition. Let's switch to HEX_MODE. | |
445 composition_mode_ = HEX_MODE; | |
446 UpdatePreeditStringHexMode(); | |
447 return true; | |
448 } | |
449 } | |
450 | |
451 // Filter key press in an appropriate manner. | |
452 switch (composition_mode_) { | |
453 case KEY_SEQUENCE_MODE: | |
454 return FilterKeyPressSequenceMode(keyval, flags); | |
455 case HEX_MODE: | |
456 return FilterKeyPressHexMode(keyval, keycode, flags); | |
457 default: | |
458 NOTREACHED(); | |
459 return false; | |
460 } | |
461 } | |
462 | |
463 bool CharacterComposer::FilterKeyPressSequenceMode(unsigned int keyval, | |
464 int flags) { | |
465 DCHECK(composition_mode_ == KEY_SEQUENCE_MODE); | |
466 compose_buffer_.push_back(keyval); | |
467 | |
468 if (compose_buffer_.size() == 2U) { | |
469 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kBlackListedDeadKeys); ++i) { | |
470 if (compose_buffer_[0] == kBlackListedDeadKeys[i].first_key && | |
471 compose_buffer_[1] == kBlackListedDeadKeys[i].second_key ) { | |
472 Reset(); | |
473 composed_character_.push_back(kBlackListedDeadKeys[i].output_char); | |
474 return kBlackListedDeadKeys[i].consume; | |
475 } | |
476 } | |
477 } | |
478 | |
479 // Check compose table. | |
480 uint32 composed_character_utf32 = 0; | |
481 if (CheckCharacterComposeTable(compose_buffer_, &composed_character_utf32)) { | |
482 // Key press is recognized as a part of composition. | |
483 if (composed_character_utf32 != 0) { | |
484 // We get a composed character. | |
485 compose_buffer_.clear(); | |
486 UTF32CharacterToUTF16(composed_character_utf32, &composed_character_); | |
487 } | |
488 return true; | |
489 } | |
490 // Key press is not a part of composition. | |
491 compose_buffer_.pop_back(); // Remove the keypress added this time. | |
492 if (!compose_buffer_.empty()) { | |
493 compose_buffer_.clear(); | |
494 return true; | |
495 } | |
496 return false; | |
497 } | |
498 | |
499 bool CharacterComposer::FilterKeyPressHexMode(unsigned int keyval, | |
500 unsigned int keycode, | |
501 int flags) { | |
502 DCHECK(composition_mode_ == HEX_MODE); | |
503 const size_t kMaxHexSequenceLength = 8; | |
504 int hex_digit = KeyvalToHexDigit(keyval); | |
505 if (hex_digit < 0) { | |
506 // With 101 keyboard, control + shift + 3 produces '#', but a user may | |
507 // have intended to type '3'. So, if a hexadecimal character was not found, | |
508 // suppose a user is holding shift key (and possibly control key, too) and | |
509 // try a character with modifier keys removed. | |
510 hex_digit = KeyvalToHexDigit(XKeyCodeToXKeySym(keycode)); | |
511 } | |
512 | |
513 if (keyval == GDK_KEY_Escape) { | |
514 // Cancel composition when ESC is pressed. | |
515 Reset(); | |
516 } else if (keyval == GDK_KEY_Return || keyval == GDK_KEY_KP_Enter || | |
517 keyval == GDK_KEY_ISO_Enter || | |
518 keyval == GDK_KEY_space || keyval == GDK_KEY_KP_Space) { | |
519 // Commit the composed character when Enter or space is pressed. | |
520 CommitHex(); | |
521 } else if (keyval == GDK_KEY_BackSpace) { | |
522 // Pop back the buffer when Backspace is pressed. | |
523 if (!compose_buffer_.empty()) { | |
524 compose_buffer_.pop_back(); | |
525 } else { | |
526 // If there is no character in |compose_buffer_|, cancel composition. | |
527 Reset(); | |
528 } | |
529 } else if (hex_digit >= 0 && | |
530 compose_buffer_.size() < kMaxHexSequenceLength) { | |
531 // Add the key to the buffer if it is a hex digit. | |
532 compose_buffer_.push_back(hex_digit); | |
533 } | |
534 | |
535 UpdatePreeditStringHexMode(); | |
536 | |
537 return true; | |
538 } | |
539 | |
540 void CharacterComposer::CommitHex() { | |
541 DCHECK(composition_mode_ == HEX_MODE); | |
542 uint32 composed_character_utf32 = 0; | |
543 for (size_t i = 0; i != compose_buffer_.size(); ++i) { | |
544 const uint32 digit = compose_buffer_[i]; | |
545 DCHECK(0 <= digit && digit < 16); | |
546 composed_character_utf32 <<= 4; | |
547 composed_character_utf32 |= digit; | |
548 } | |
549 Reset(); | |
550 UTF32CharacterToUTF16(composed_character_utf32, &composed_character_); | |
551 } | |
552 | |
553 void CharacterComposer::UpdatePreeditStringHexMode() { | |
554 if (composition_mode_ != HEX_MODE) { | |
555 preedit_string_.clear(); | |
556 return; | |
557 } | |
558 std::string preedit_string_ascii("u"); | |
559 for (size_t i = 0; i != compose_buffer_.size(); ++i) { | |
560 const int digit = compose_buffer_[i]; | |
561 DCHECK(0 <= digit && digit < 16); | |
562 preedit_string_ascii += digit <= 9 ? ('0' + digit) : ('a' + (digit - 10)); | |
563 } | |
564 preedit_string_ = ASCIIToUTF16(preedit_string_ascii); | |
565 } | |
566 | |
567 } // namespace ui | |
OLD | NEW |