*** empty log message ***
[m17n/m17n-lib-js.git] / ime.js
1 /*
2   Ajax IME: http://ajaxime.chasen.org/
3   Author: Taku Kudo <taku@chasen.org>
4
5   (C) Taku Kudo, all rights reserve rd.
6   Personal use only!
7 */
8
9 //////////////////////////////////////////////////////////////////////////////////////////
10
11 //
12 // Comment by kazawa
13 // We cannot simply define getComputedStyle() in "if" block because it seems
14 // like Safari overwrites functions during parsing the script i.e, before
15 // execution (bug?). So we substitue it at run time instead.
16 //
17 // *** TESTED ONLY ON Firefox3 AND Safari3 ***
18 //
19 if (typeof(getComputedStyle) == 'undefined') {
20   function capitalize(prop) {
21     return prop.replace(/-(.)/g, function(m, m1) { return m1.toUpperCase(); })
22   }
23   function getComputedStyle(element, pseudo){
24     return {
25       currentStyle : element.currentStyle,
26         getPropertyValue : function(prop){
27         return this.currentStyle[capitalize(prop)];
28       },
29         setProperty : function(prop, value){
30           this.currentStyle[capitalize(prop)] = value;
31         }
32      }
33   }
34
35 //  getComputedStyle == __getComputedStyle;
36 }
37
38 function JSONRequest(url) {
39   this.url_ = url;
40   this.parent_ = document.getElementsByTagName('head').item(0);
41   this.obj_ = document.createElement('script');
42   this.obj_.setAttribute('type', 'text/javascript');
43   this.obj_.setAttribute('charset', 'utf-8');
44   this.obj_.setAttribute('src', this.url_);
45   this.parent_.appendChild(this.obj_);
46 }
47
48 JSONRequest.prototype.remove = function () {
49   this.parent_.removeChild(this.obj_);
50 }
51
52 function getEvent (evt) {
53   return (evt) ? evt : ((window.event) ? event : null);
54 }
55
56 function getKeyCode(event) {
57   var evt = getEvent(event);
58   return ((evt.which) ? evt.which : evt.keyCode);
59 }
60
61 function callWithDelay(func, delay) {
62   if (delay) setTimeout(func, 0);
63   else func();
64 }
65
66 function addEvent(obj, event, func) {
67   if (obj.addEventListener) {
68     obj.addEventListener(event, func, false);
69   } else if(obj.attachEvent){
70     obj.attachEvent('on' + event, func);
71   } else {
72   }
73 }
74
75 function copyFontStyle(elmOriginal, elmClone) {
76   var styleOriginal = getComputedStyle(elmOriginal,'');
77   var styleClone = getComputedStyle(elmClone,'');
78   elmClone.style["fontFamily"] = styleOriginal.getPropertyValue("font-family");
79   elmClone.style["fontSize"] = styleOriginal.getPropertyValue("font-size");
80   elmClone.style["fontWeight"] = styleOriginal.getPropertyValue('font-weight');
81   elmClone.style["fontVariant"] = styleOriginal.getPropertyValue('font-variant');
82 }
83
84 function cloneElement(elmOriginal, elmClone) {
85   var styleOriginal = getComputedStyle(elmOriginal,'');
86
87   var styleClone = getComputedStyle(elmClone,'');
88   elmClone.style.left = elmOriginal.offsetLeft + 'px';
89   elmClone.style.top = (elmOriginal.offsetTop + elmOriginal.offsetHeight + 30) + 'px';
90
91   elmClone.style["width"] =  styleOriginal.getPropertyValue('width');
92   elmClone.style["height"] =  styleOriginal.getPropertyValue('height');
93   elmClone.style["padding"] =  styleOriginal.getPropertyValue('padding');
94   elmClone.style["paddingLeft"] =  styleOriginal.getPropertyValue('padding-left');
95   elmClone.style["paddingRight"] =  styleOriginal.getPropertyValue('padding-right');
96   elmClone.style["paddingTop"] =  styleOriginal.getPropertyValue('padding-top');
97   elmClone.style["paddingBottom"] =  styleOriginal.getPropertyValue('padding-bottom');
98   elmClone.style["borderStyle"] =  styleOriginal.getPropertyValue('border-style');
99   elmClone.style["borderLeftStyle"] =  styleOriginal.getPropertyValue('border-left-style');
100   elmClone.style["borderRightStyle"] =  styleOriginal.getPropertyValue('border-right-style');
101   elmClone.style["borderTopStyle"] =  styleOriginal.getPropertyValue('border-top-style');
102   elmClone.style["borderBottomStyle"] =  styleOriginal.getPropertyValue('border-bottom-style');
103   elmClone.style["borderLeftWidth"] =  styleOriginal.getPropertyValue('border-left-width');
104   elmClone.style["borderRightWidth"] =  styleOriginal.getPropertyValue('border-right-width');
105   elmClone.style["borderTopWidth"] =  styleOriginal.getPropertyValue('border-top-width');
106   elmClone.style["borderBottomWidth"] =  styleOriginal.getPropertyValue('border-bottom-width');
107   elmClone.style["borderWidth"] =  styleOriginal.getPropertyValue('border-width');
108   elmClone.style["fontFamily"] =  styleOriginal.getPropertyValue('font-family');
109   elmClone.style["fontSize"] =  styleOriginal.getPropertyValue('font-size');
110   elmClone.style["fontVariant"] =  styleOriginal.getPropertyValue('font-variant');
111   elmClone.style["fontWeight"] =  styleOriginal.getPropertyValue('font-weight');
112   elmClone.style["lineHeight"] =  styleOriginal.getPropertyValue('line-height');
113   elmClone.style["letterSpacing"] =  styleOriginal.getPropertyValue('letter-spacing');
114   elmClone.style["wordSpacing"] =  styleOriginal.getPropertyValue('word-spacing');
115   elmClone.style.width = elmOriginal.offsetWidth;
116   elmClone.style.height = elmOriginal.offsetHeight;
117   elmClone.scrollLeft = elmOriginal.scrollLeft;
118   elmClone.scrollTop = elmOriginal.scrollTop;
119 }
120
121 //////////////////////////////////////////////////////////////////////////////////////////
122 var ImeCGI_ = "http://api.chasen.org/ajaxime/";
123 var ImeBackGroundColor_ = 'aliceblue';
124 var ImeID_ = 0;
125 var ImeCache_ = [];
126 var ImeCurrentDocument_ = null;
127
128 function AjaxIME(doc) {
129   var ImeJsonp_ = null;
130   var ImeJsonpLog_ = null;
131   var ImeDocument_ = doc;
132   var ImeMode_ = null;
133   var ImeResults_ = [];
134   var ImeTo_ = 0;
135   var ImeNumResults_ = 0;
136   var ImeLoaded_ = false;
137   var ImeCandidates_ = null;
138   var ImePreEdit_ = null;
139   var ImePreEditWidth_ = 0;
140   var ImeWindow_ = null;
141   var ImeTextArea_ = null;
142   var ImeTextAreaClone_ = null;
143   var ImeSelectedIndex_ = -1;
144   var ImeStartPos_ = 0;
145   var ImeEndPos_ = 0;
146
147   // initializer
148   {
149     if (ImeLoaded_) return;
150     ImeWindow_ = ImeDocument_.createElement('div');
151     ImePreEdit_ = ImeDocument_.createElement('input');
152     ImePreEdit_.setAttribute('type', 'text');
153     ImePreEdit_.setAttribute('autocomplete', 'off');
154     ImeCandidates_ = ImeDocument_.createElement('div');
155     ImeTextAreaClone_ = ImeDocument_.createElement('pre');
156     ImePreEdit_.__ImeUsed = ImeTextAreaClone_.__ImeUsed = true;
157     ImeWindow_.appendChild(ImePreEdit_);
158     ImeWindow_.appendChild(ImeCandidates_);
159     ImeDocument_.body.appendChild(ImeWindow_);
160
161     ImeWindow_.style.position = 'absolute';
162     ImeWindow_.style.margin = '0px';
163     ImeWindow_.style.padding = '0px';
164     ImeWindow_.style.textAlign = 'left';
165     ImePreEdit_.style.backgroundColor = ImeBackGroundColor_;
166     ImePreEdit_.style.borderWidth = '0px';
167     ImePreEdit_.style.textDecoration = 'underline';
168     ImePreEdit_.style.textUnderLineColor = 'red';
169     ImePreEdit_.style.textAlign = 'left';
170     ImeCandidates_.style.padding = '0px';
171     ImeCandidates_.style.borderColor = '#000';
172     ImeCandidates_.style.borderWidth = '1px';
173     ImeCandidates_.style.borderStyle = 'solid';
174     ImeCandidates_.style.styleFloat = 'left'; // IE
175     ImeCandidates_.style.cssFloat = 'left'; // Firefox
176     ImeCandidates_.style.textAlign = 'left';
177
178     ImeDocument_.ImeRequestCallback = ImeRequestCallback;
179     ImeDocument_.ImeChangeMode = ImeChangeMode;
180
181     addEvent(ImeDocument_, 'keydown', ImeDocumentKeyDown);
182     addEvent(ImePreEdit_,  'keydown', ImePreEditKeyDown);
183     addEvent(ImePreEdit_,  'keyup',   ImePreEditKeyUp);
184   
185     setInterval(ImeInitTextAreas, 500);
186     ImeLoaded_ = true;
187
188     ImeHide();
189   }
190
191   function ImeInitTextAreas() {
192     var inputs = ImeDocument_.getElementsByTagName('input');
193     for (var i = 0; i < inputs.length; ++i) {
194       if (inputs[i].type == 'text') ImeInitTextAreaEvent(inputs[i]);
195     }
196
197     var textareas = ImeDocument_.getElementsByTagName('textarea');
198     for (var i = 0; i < textareas.length; ++i) {
199       ImeInitTextAreaEvent(textareas[i]);
200     }
201   }
202
203   function ImeInitTextAreaEvent(textarea) {
204     if (!textarea.__ImeUsed && !textarea.__ImeRegistered) {
205       addEvent(textarea, 'keydown', ImeTextAreaKeyDown);
206       addEvent(textarea, 'click',   ImeHide);
207       textarea.__ImeRegistered = true;
208     }
209   }
210
211   function ImeSetCaretPos(pos) {
212     if (ImeTextArea_.setSelectionRange) { // Mozilla
213       ImeTextArea_.setSelectionRange(pos, pos);
214     } else if (ImeDocument_.selection.createRange ){ // IE
215       var e = ImeTextArea_.createTextRange();
216       var tx = ImeTextArea_.value.substr(0, pos);
217       var pl = tx.split(/\n/);
218       e.collapse(true);
219       e.moveStart('character', pos - pl.length + 1);
220       e.collapse(false);
221       e.select();
222     }
223     ImeTextArea_.focus();
224   }
225
226   function ImeStoreCaretPos() {
227     var startpos = 0;
228     var endpos = 0;
229     if (ImeTextArea_.setSelectionRange) { // mozilla
230       startpos = ImeTextArea_.selectionStart;
231       endpos = ImeTextArea_.selectionEnd;
232     } else if (ImeDocument_.selection.createRange) { // IE
233       if (ImeTextArea_.type == 'textarea') {
234         ImeTextArea_.caretPos = ImeDocument_.selection.createRange().duplicate();
235         var sel = ImeDocument_.selection.createRange();
236         var r = ImeTextArea_.createTextRange();
237         var len = ImeTextArea_.value.length;
238         r.moveToPoint(sel.offsetLeft, sel.offsetTop);
239         r.moveEnd('textedit');
240         startpos = len - r.text.length
241           endpos = startpos +  sel.text.length;
242       } else {
243         var r = ImeDocument_.selection.createRange();
244         var len = ImeTextArea_.value.length;
245         r.moveEnd('textedit');
246         startpos = len - r.text.length
247           endpos = startpos;
248       }
249     }
250     ImeStartPos_ = startpos;
251     ImeEndPos_ = endpos;
252   }
253
254   function ImeStoreWindowPos() {
255     var x = 0;
256     var y = 0;
257     var x2 = ImeDocument_.body.scrollTop;
258     var y2 = ImeDocument_.body.scrollLeft;
259
260     if (! ImeTextArea_.createTextRange || ImeTextArea_.type != 'textarea') {
261       for (var o = ImeTextArea_; o ; o = o.offsetParent) {
262         x2 += (o.offsetLeft - o.scrollLeft);
263         y2 += (o.offsetTop - o.scrollTop);
264       }
265     }
266
267     if (ImeTextArea_.selectionStart || ImeTextArea_.selectionStart == '0') { // Mozilla
268       cloneElement(ImeTextArea_, ImeTextAreaClone_);
269       ImeTextArea_.offsetParent.appendChild(ImeTextAreaClone_);
270       var value = ImeTextArea_.value.replace(/\r\n/g, '\n\r') + ' ';
271       var caretPos = ImeDocument_.createElement('span');
272       caretPos.innerHTML = '|';
273       ImeTextAreaClone_.innerHTML = '';
274       ImeTextAreaClone_.appendChild(ImeDocument_.createTextNode(value.substr(0,ImeStartPos_)));
275       ImeTextAreaClone_.appendChild(caretPos);
276       ImeTextAreaClone_.appendChild(ImeDocument_.createTextNode(value.substr(ImeStartPos_)));
277       y = y2 + (caretPos.offsetTop - ImeTextAreaClone_.offsetTop);
278       x = x2 + (caretPos.offsetLeft - ImeTextAreaClone_.offsetLeft);
279       ImeTextArea_.offsetParent.removeChild(ImeTextAreaClone_);
280     } else {
281       var caretPos = ImeDocument_.selection.createRange();
282       y = y2 + (caretPos.offsetTop + ImeDocument_.documentElement.scrollTop - 3); // why -3 ??
283       x = x2 + (caretPos.offsetLeft + ImeDocument_.documentElement.scrollLeft);
284     }
285
286     ImeWindow_.style.top = y + 'px';
287     ImeWindow_.style.left = x + 'px';
288   }
289
290   function ImeInsertText(text, delay) {
291     var func = function() { ImeSetCaretPos(ImeStartPos_ + text.length); }
292
293     if (ImeTextArea_.createTextRange && ImeTextArea_.caretPos) {
294       var caretPos = ImeTextArea_.caretPos;
295       caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ?
296         text  + ' ' : text;
297       callWithDelay(func, delay);
298     } else {
299       var tmpTop = ImeTextArea_.scrollTop;
300       var tmpLeft = ImeTextArea_.scrollLeft;
301       ImeTextArea_.value = ImeTextArea_.value.substring(0, ImeStartPos_) +
302         text + ImeTextArea_.value.substring(ImeEndPos_, ImeTextArea_.value.length);
303       callWithDelay(func, delay);
304       ImeTextArea_.scrollTop = tmpTop;
305       ImeTextArea_.scrollLeft = tmpLeft;
306     }
307   }
308
309   function ImeAdd(text, selected) {
310     if (text == '') return;
311   
312     if (selected) ImeSelectedIndex_ = ImeNumResults_;
313
314     var n = ImeNumResults_;
315     ImeResults_[ImeNumResults_++] = text;
316
317     if (text.length > ImePreEditWidth_)
318       ImePreEditWidth_ = text.length;
319
320     var div = ImeDocument_.createElement('div');
321     div.style.width = '100%';
322     div.style.styleFloat = 'left';
323     div.style.cssFloat = 'left';
324     if (selected) {
325       div.style.backgroundColor = '#36c';
326       div.style.color = '#fff';
327     } else {
328       div.style.backgroundColor = '#fff';
329       div.style.color = '#000';
330     }
331
332     var txt = ImeDocument_.createTextNode(text);
333     var span = ImeDocument_.createElement('span');
334     span.appendChild(txt);
335     copyFontStyle(ImeTextArea_, span);
336     addEvent(div, 'click', function() {
337       ImeSelectedIndex_ = n; ImeSelect(true);  ImeHide(); });
338     div.appendChild(span);
339     ImeCandidates_.appendChild(div);
340   }
341
342   function ImeHandleDown() {
343     if (ImeIsHidden()) return;
344     ++ImeSelectedIndex_;
345     if (ImeSelectedIndex_ == ImeNumResults_) ImeSelectedIndex_ = 0;
346     ImeHighlight();
347   }
348
349   function ImeHandleUp() {
350     if (ImeIsHidden()) return;
351     --ImeSelectedIndex_;
352     if (ImeSelectedIndex_ < 0) ImeSelectedIndex = ImeNumResults_ - 1;
353     ImeHighlight();
354   }
355
356   function ImeHighlight() {
357     var divs = ImeCandidates_.getElementsByTagName('div');
358     for (var i = 0; i < divs.length; ++i) {
359       if (i == ImeSelectedIndex_) {
360         ImePreEdit_.value = ImeResults_[i];
361         divs[i].style.backgroundColor = '#36c';
362         divs[i].style.color = '#fff';
363       } else {
364         divs[i].style.backgroundColor = '#fff';
365         divs[i].style.color = '#000';
366       }
367     }
368   }
369
370   function ImeNop() {
371     ImeInsertText('', false);
372     if (ImeRawInput_ != '')
373       ImeLog('cancel');
374   }
375
376   function ImeSelect(delay) {
377     var result = ImeResults_.length ? ImeResults_[ImeSelectedIndex_] : ImePreEdit_.value;
378     ImeInsertText(result, delay);
379     ImeLog(result);
380   }
381
382   function ImeIsHidden() {
383     return (ImeCandidates_.style.display == 'none');
384   }
385
386   function ImeHideCandidates() {
387     ImeCandidates_.style.display = 'none';
388     ImeCandidates_.innerHTML = '';
389   }
390
391   function ImeHide() {
392     ImeWindow_.style.display = 'none';
393     ImePreEdit_.style.display = 'none';
394     ImeHideCandidates();
395     ImeClear();
396   }
397
398   function ImeClear() {
399     ImeClearPreEdit();
400     ImeClearResults();
401   }
402
403   function ImeClearPreEdit() {
404     ImePreEdit_.value = '';
405     ImePreEditWidth_ = 0;
406     ImeRawInput_ = '';
407     ImeTo_ = "ime";
408   }
409
410   function ImeClearResults() {
411     ImeSelectedIndex_ = -1;
412     ImeNumResults_ = 0;
413     ImeResults_ = [];
414   }
415
416   function ImeShow() {
417     ImePreEdit_.style.display = 'block';
418     ImeWindow_.style.display = 'block';
419   }
420
421   function ImeShowPreEdit() {
422     ImeStoreCaretPos();
423     ImeStoreWindowPos();
424     ImeHide();
425     ImeShow();
426     ImePreEdit_.focus();
427   }
428
429   function ImeLog(result) {
430     if (ImeRawInput_ == '') ImeRawInput_ = result;
431     if (ImeSelectedIndex_ >= 1) ImeCache_[ImeRawInput_] = result;
432     var request = "action=log&query=" + encodeURI(ImeRawInput_) + 
433       "&result=" + encodeURI(result) + "&sel=" + ImeSelectedIndex_ +
434       "&id=" + ImeID_;
435     ImeJsonpLog_ = new JSONRequest(ImeCGI_ + request);
436     ++ImeID_;
437   }
438
439   function ImeRequestCallback(result) {
440     try {
441       ImeClearResults();
442       var c = ImeCache_[ImeRawInput_];
443       if (c) {
444         ImeAdd(c, 1);
445         for (var i = 0; i < result.length; i++) {
446           if (result[i] != c) ImeAdd(result[i], 0);
447         }
448       } else {
449         for (var i = 0; i < result.length; i++) {
450           ImeAdd(result[i], i == 0 ? 1 : 0);
451         }
452       }
453       if (ImeResults_.length) ImePreEdit_.value = ImeResults_[0];
454       if (ImeRawInput_ == '') ImeRawInput_ = roma2hiragana(ImePreEdit_.value, false);
455       ImeCandidates_.style.display = 'block';
456       if (ImeJsonp_) ImeJsonp_.remove();
457     } catch (e) {}
458   }
459
460   function ImeShowCandidates() {
461     if (!ImeIsHidden()) return;
462     if (ImeRawInput_ == '') ImeRawInput_ = roma2hiragana(ImePreEdit_.value, false);
463     var request = "action=conv&to=" + ImeTo_ + "&query=" + encodeURI(ImeRawInput_) +
464       "&id=" + ImeID_;
465     ImeCurrentDocument_ = ImeDocument_;
466     ImeJsonp_ = new JSONRequest(ImeCGI_ + request);
467   }
468
469   function ImePreEditKeyDown(event)  {
470     if (!ImeMode_) return false;
471
472     var evt = getEvent(event);
473     var key = getKeyCode(event);
474
475     ImePreEdit_.focus();
476     if (key == 0x20) { // space
477       ImeTo_ = "ime";
478       if (ImeIsHidden())
479         ImeShowCandidates();
480       else
481         ImeHandleDown();
482     } else if (key == 120) { // F9 katakana
483       ImeTo_ = "katakana";
484       ImeHideCandidates();
485       ImeShowCandidates();
486     } else if (key == 119) { // F9 roma
487       ImeTo_ = "alpha";
488       ImeHideCandidates();
489       ImeShowCandidates();
490     } else if (key == 40) { // down
491       ImeHandleDown();
492     } else if (key == 38) { // up
493       ImeHandleUp();
494     } else if (key == 13) { // return
495       ImeSelect(true);
496       ImeHide();
497     } else if (key == 27) {  // esc, delete, bs
498       ImeNop();
499       ImeHide();
500     } else if (! ImeIsHidden()) { // next word
501       ImeSelect(false);
502       ImeStartPos_ += ImePreEdit_.value.length;
503       ImeSetCaretPos(ImeStartPos_);
504       ImeStoreWindowPos();
505       ImeStoreCaretPos();
506       ImeHideCandidates();
507       ImeClear();
508       ImePreEdit_.focus();
509     }
510
511     return false;
512   }
513
514   function ImePreEditKeyUp(event) {
515     if (ImePreEdit_.value.length == 0) {
516       ImeHide();
517       ImeNop();
518       return false;
519     }
520
521     ImePreEdit_.value = roma2hiragana(ImePreEdit_.value, true);
522
523     if (ImePreEdit_.value.length > ImePreEditWidth_)
524       ImePreEditWidth_ = ImePreEdit_.value.length;
525
526     var fontsize = parseFloat(getComputedStyle(ImeTextArea_, '').getPropertyValue('font-size'));
527
528     var length = (1.5 * fontsize * (ImePreEditWidth_ + 1)) + 'px';
529     ImePreEdit_.style.width = length;
530     ImeWindow_.style.width = length;
531     ImeCandidates_.style.width = length;
532
533     return true;
534   }
535
536   function ImeChangeMode() {
537     ImeMode_ = !ImeMode_;
538     ImeHide();
539
540     var inputs = ImeDocument_.getElementsByTagName('input');
541      for (var i = 0; i < inputs.length; ++i) {
542        if (!inputs[i].__ImeUsed && inputs[i].type == 'text') {
543         inputs[i].style.backgroundColor = ImeMode_ ? ImeBackGroundColor_ : 'white';
544       }
545     }
546
547     var textareas = ImeDocument_.getElementsByTagName('textarea');
548     for (var i = 0; i < textareas.length; ++i) {
549       if (!textareas[i].__ImeUsed)
550         textareas[i].style.backgroundColor = ImeMode_ ? ImeBackGroundColor_ : 'white';
551     }
552   }
553
554   function ImeDocumentKeyDown(event) {
555     var evt = getEvent(event);
556     var key = getKeyCode(event);
557     if ((key == 79 && evt.altKey) || ((key == 57 || key == 81) && evt.ctrlKey)) {
558       ImeChangeMode();
559       return false;
560     }
561     return true;
562   }
563
564   function ImeTextAreaKeyDown(event) {
565     var evt = getEvent(event);
566     var key = getKeyCode(event);
567
568     ImeTextArea_ = evt.target || evt.srcElement;
569
570     copyFontStyle(ImeTextArea_, ImePreEdit_);
571
572     if (ImeMode_) ImeHide();
573
574     // ignore Ctrl+C
575     if (evt.ctrlKey || evt.altKey) return true;
576
577     if (ImeMode_ && ImeIsHidden() &&
578         ((key >= 65 && key <= 90) ||
579          (key >= 48 && key <= 57) || (key >= 187 && key <= 222))) {
580
581       ImeShowPreEdit();
582       return false;
583     }
584
585     return true;
586   }
587 }
588
589 // roma 2 hiragana
590 function roma2hiragana(str, delay) {
591   var result = [];
592   var text = str;
593   var rem = '';
594
595   if (delay) {
596     var l = str.length;
597     var last  = str.substr(l - 1, 1);
598     var last2 = str.substr(l - 2, 2);
599     if (l > 1 && last2 == 'nn') {
600       text = str;
601       rem = '';
602     } else if (l > 1 && last2.match(/^[qwrtyplkjhgfdszxcvbmn]y$/)) {
603       text = str.substr(0, l - 2);
604       rem = last2;
605     } else if (l > 0 && last.match(/[qwrtyplkjhgfdszxcvbmn]/)) {
606       text = str.substr(0, l - 1);
607       rem = last;
608     }
609   }
610   
611   for (var i = 0; i < text.length;) {
612     var o = text.charAt(i);
613     var c = o.charCodeAt(0);
614     var len = 0;
615     if ((c >= 97 && c <= 122) || (c >= 65 && c <= 90) || (c >= 44 && c <= 46)) 
616       len = 4;
617     while (len) {
618       var key = text.slice(i, i + len);
619       if (key in IMERoma2KatakanaTable_) {
620         var kana = IMERoma2KatakanaTable_[key];
621         if (typeof(kana) == 'string') {
622           result.push(kana);
623           i += len;
624         } else {
625           result.push(kana[0]);
626           i += (len - kana[1]);
627         }
628         break;
629       }
630       --len;
631     }
632     
633     if (len == 0) {
634       result.push(o);
635       ++i;
636     }
637   }
638   
639   return result.join("") + rem;
640 }
641
642 IMERoma2KatakanaTable_ = {'.':'。',',':'、','-':'ー','~':'〜','va':'う゛ぁ','vi':'う゛ぃ','vu':'う゛','ve':'う゛ぇ','vo':'う゛ぉ','vv': ['っ',1],'xx': ['っ',1],'kk': ['っ',1],'gg': ['っ',1],'ss': ['っ',1],'zz': ['っ',1],'jj': ['っ',1],'tt': ['っ',1],'dd': ['っ',1],'hh': ['っ',1],'ff': ['っ',1],'bb': ['っ',1],'pp': ['っ',1],'mm': ['っ',1],'yy': ['っ',1],'rr': ['っ',1],'ww': ['っ',1],'cc': ['っ',1],'kya':'きゃ','kyi':'きぃ','kyu':'きゅ','kye':'きぇ','kyo':'きょ','gya':'ぎゃ','gyi':'ぎぃ','gyu':'ぎゅ','gye':'ぎぇ','gyo':'ぎょ','sya':'しゃ','syi':'しぃ','syu':'しゅ','sye':'しぇ','syo':'しょ','sha':'しゃ','shi':'し','shu':'しゅ','she':'しぇ','sho':'しょ','zya':'じゃ','zyi':'じぃ','zyu':'じゅ','zye':'じぇ','zyo':'じょ','tya':'ちゃ','tyi':'ちぃ','tyu':'ちゅ','tye':'ちぇ','tyo':'ちょ','cha':'ちゃ','chi':'ち','chu':'ちゅ','che':'ちぇ','cho':'ちょ','dya':'ぢゃ','dyi':'ぢぃ','dyu':'ぢゅ','dye':'ぢぇ','dyo':'ぢょ','tha':'てゃ','thi':'てぃ','thu':'てゅ','the':'てぇ','tho':'てょ','dha':'でゃ','dhi':'でぃ','dhu':'でゅ','dhe':'でぇ','dho':'でょ','nya':'にゃ','nyi':'にぃ','nyu':'にゅ','nye':'にぇ','nyo':'にょ','jya':'じゃ','jyi':'じ','jyu':'じゅ','jye':'じぇ','jyo':'じょ','hya':'ひゃ','hyi':'ひぃ','hyu':'ひゅ','hye':'ひぇ','hyo':'ひょ','bya':'びゃ','byi':'びぃ','byu':'びゅ','bye':'びぇ','byo':'びょ','pya':'ぴゃ','pyi':'ぴぃ','pyu':'ぴゅ','pye':'ぴぇ','pyo':'ぴょ','fa':'ふぁ','fi':'ふぃ','fu':'ふ','fe':'ふぇ','fo':'ふぉ','mya':'みゃ','myi':'みぃ','myu':'みゅ','mye':'みぇ','myo':'みょ','rya':'りゃ','ryi':'りぃ','ryu':'りゅ','rye':'りぇ','ryo':'りょ','n\'':'ん','nn':'ん','n':'ん','a':'あ','i':'い','u':'う','e':'え','o':'お','xa':'ぁ','xi':'ぃ','xu':'ぅ','xe':'ぇ','xo':'ぉ','la':'ぁ','li':'ぃ','lu':'ぅ','le':'ぇ','lo':'ぉ','ka':'か','ki':'き','ku':'く','ke':'け','ko':'こ','ga':'が','gi':'ぎ','gu':'ぐ','ge':'げ','go':'ご','sa':'さ','si':'し','su':'す','se':'せ','so':'そ','za':'ざ','zi':'じ','zu':'ず','ze':'ぜ','zo':'ぞ','ja':'じゃ','ji':'じ','ju':'じゅ','je':'じぇ','jo':'じょ','ta':'た','ti':'ち','tu':'つ','tsu':'つ','te':'て','to':'と','da':'だ','di':'ぢ','du':'づ','de':'で','do':'ど','xtu':'っ','xtsu':'っ','na':'な','ni':'に','nu':'ぬ','ne':'ね','no':'の','ha':'は','hi':'ひ','hu':'ふ','fu':'ふ','he':'へ','ho':'ほ','ba':'ば','bi':'び','bu':'ぶ','be':'べ','bo':'ぼ','pa':'ぱ','pi':'ぴ','pu':'ぷ','pe':'ぺ','po':'ぽ','ma':'ま','mi':'み','mu':'む','me':'め','mo':'も','xya':'ゃ','ya':'や','xyu':'ゅ','yu':'ゆ','xyo':'ょ','yo':'よ','ra':'ら','ri':'り','ru':'る','re':'れ','ro':'ろ','xwa':'ゎ','wa':'わ','wi':'うぃ','we':'うぇ','wo':'を'};
643
644 // global functions
645 function ImeInit() {
646   function ImeInitRecursive(w) {
647     var frames = w.frames;
648     for (var i = 0; i < frames.length; ++i) {
649       AjaxIME(frames[i].document);
650       ImeInitRecursive(frames[i].window);
651     }
652   }
653   AjaxIME(document);
654   ImeInitRecursive(window);
655 }
656
657 function ImeChangeMode() {
658   function ImeChangeModeRecursive(w) {
659     var frames = w.frames;
660     for (var i = 0; i < frames.length; ++i) {
661       frames[i].document.ImeChangeMode();
662       ImeChangeModeRecursive(frames[i].window);
663     }
664   }
665   document.ImeChangeMode();
666   ImeChangeModeRecursive(window);
667 }
668
669 function ImeRequestCallback(result) {
670   ImeCurrentDocument_.ImeRequestCallback(result);
671 }
672
673 addEvent(window, 'load', ImeInit);