1 /**
2  *  Yudit Unicode Editor Source File
3  *
4  *  GNU Copyright (C) 1997-2006  Gaspar Sinai <gaspar@yudit.org>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License, version 2,
8  *  dated June 1991. See file COPYYING for details.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19 
20 #include "stoolkit/syntax/SHunspellPattern.h"
21 #include "stoolkit/SIO.h"
22 #include "stoolkit/SCharClass.h"
23 
24 #ifdef USE_WINAPI
25 #include <windows.h>
26 #else
27 #include <stdlib.h>
28 #include <dlfcn.h>
29 #include <string.h>
30 #endif
31 
32 static void fixFileName (SString& str);
33 static void convertRovasiras (const SV_UCS4& in, SV_UCS4& out);
34 
fixFileName(SString & str)35 static void fixFileName (SString& str)
36 {
37 #ifdef USE_WINAPI
38   str.replaceAll ("/", "\\");
39   if (str.size() > 0 && str[0] == '\\') str.remove (0);
40 #endif
41 }
42 
43 static void* dynHandle = 0;
44 
SHunspellPattern(const SString & _name,const SStringVector & path)45 SHunspellPattern::SHunspellPattern (const SString& _name,
46   const SStringVector& path)
47 {
48   name = _name;
49   SString ps_dic = name;
50   ps_dic.append (".dic");
51   SFile fdic (ps_dic, path);
52   if (fdic.size() < 0)
53   {
54     valid = false;
55     fprintf (stderr, "Hunspell invalid fdic.size.\n");
56   }
57   else
58   {
59     dicFile = fdic.getName ();
60     fixFileName (dicFile);
61   }
62 
63   SString ps_aff = name;
64   ps_aff.append (".aff");
65   SFile faff (ps_aff, path);
66   if (faff.size() < 0)
67   {
68     valid = false;
69     fprintf (stderr, "Hunspell invalid faff.size.\n");
70   }
71   else
72   {
73     affFile = faff.getName ();
74     fixFileName (affFile);
75   }
76 
77 
78   dicFile.append ((char)0);
79   affFile.append ((char)0);
80   SString mising = loadLibrary (path);
81   hunspell = 0;
82   functions.create = 0;
83   functions.spell = 0;
84   functions.destroy = 0;
85   functions.get_dic_encoding = 0;
86   entryEncoder = 0;
87   if (dynHandle)
88   {
89 #ifdef USE_WINAPI
90     functions.create = (void* (*)(const char*, const char*)) GetProcAddress((HMODULE)dynHandle, "hunspell_initialize");
91     functions.spell = (int (*)(void*, const char*)) GetProcAddress((HMODULE)dynHandle, "hunspell_spell");
92     functions.destroy = (int (*)(void*)) GetProcAddress((HMODULE)dynHandle, "hunspell_uninitialize");
93     functions.get_dic_encoding = (char* (*)(void*))  GetProcAddress((HMODULE)dynHandle, "hunspell_get_dic_encoding");
94 #else
95     functions.create = (void* (*)(const char*, const char*)) dlsym(dynHandle, "Hunspell_create");
96     functions.spell = (int (*)(void*, const char*)) dlsym(dynHandle, "Hunspell_spell");
97     functions.destroy = (int (*)(void*)) dlsym(dynHandle, "Hunspell_destroy");
98     functions.get_dic_encoding = (char* (*)(void*))  dlsym(dynHandle, "Hunspell_get_dic_encoding");
99 #endif
100     if (functions.create && functions.spell && functions.destroy &&
101         functions.get_dic_encoding)
102     {
103       hunspell = (*functions.create)(affFile.array (), dicFile.array ());
104       valid = (hunspell != 0);
105     }
106     else
107     {
108      fprintf (stderr, "hunspell: can not get function handles. create=%d spell=%d destroy=%d get_dic_encoding=%d\n",
109          (int)(functions.create!=0),
110          (int)(functions.spell!=0),
111          (int)(functions.destroy!=0),
112          (int)(functions.get_dic_encoding!=0)
113       );
114       functions.create = 0;
115       functions.spell = 0;
116       functions.destroy = 0;
117       functions.get_dic_encoding = 0;
118       valid = false;
119     }
120   }
121   else
122   {
123      valid = false;
124   }
125   if (valid) {
126     char* enc = (*functions.get_dic_encoding)(hunspell);
127     // Get at least the ISO set that comes with Yudit.
128     if (enc == 0) {
129       entryEncoder = new SEncoder ("utf-8");
130     } else if (strcmp (enc, "UTF-8") == 0) {
131       entryEncoder = new SEncoder ("utf-8");
132     } else if (strcmp (enc, "ISO8859-1") == 0) {
133       entryEncoder = new SEncoder ("iso-8859-1");
134     } else if (strcmp (enc, "ISO8859-2") == 0) {
135       entryEncoder = new SEncoder ("iso-8859-2");
136     } else if (strcmp (enc, "ISO8859-3") == 0) {
137       entryEncoder = new SEncoder ("iso-8859-3");
138     } else if (strcmp (enc, "ISO8859-4") == 0) {
139       entryEncoder = new SEncoder ("iso-8859-4");
140     } else if (strcmp (enc, "ISO8859-5") == 0) {
141       entryEncoder = new SEncoder ("iso-8859-5");
142     } else if (strcmp (enc, "ISO8859-6") == 0) {
143       entryEncoder = new SEncoder ("iso-8859-6");
144     } else if (strcmp (enc, "ISO8859-7") == 0) {
145       entryEncoder = new SEncoder ("iso-8859-7");
146     } else if (strcmp (enc, "ISO8859-8") == 0) {
147       entryEncoder = new SEncoder ("iso-8859-8");
148     } else if (strcmp (enc, "ISO8859-9") == 0) {
149       entryEncoder = new SEncoder ("iso-8859-9");
150     } else if (strcmp (enc, "ISO8859-13") == 0) {
151       entryEncoder = new SEncoder ("iso-8859-13");
152     } else if (strcmp (enc, "ISO8859-15") == 0) {
153       entryEncoder = new SEncoder ("iso-8859-15");
154     } else if (strcmp (enc, "ISO8859-16") == 0) {
155       entryEncoder = new SEncoder ("iso-8859-16");
156     } else if (strcmp (enc, "KOI8-R") == 0) {
157       entryEncoder = new SEncoder ("koi8-r");
158     } else {
159       fprintf (stderr, "Need hunspell encoder for %s\n", enc);
160       entryEncoder = new SEncoder ("utf-8");
161     }
162   }
163 }
164 
~SHunspellPattern()165 SHunspellPattern::~SHunspellPattern ()
166 {
167   if (functions.destroy && hunspell)
168   {
169     (*functions.destroy) (hunspell);
170   }
171   if (entryEncoder) delete entryEncoder;
172 }
173 
174 SString
loadLibrary(const SStringVector & path)175 SHunspellPattern::loadLibrary (const SStringVector& path)
176 {
177   if (dynHandle == 0)
178   {
179     SString libFile;
180 #ifdef USE_WINAPI
181     libFile = "libhunspell.dll";
182 #else
183 #ifdef __APPLE__
184     libFile = "libhunspell.dylib";
185 #else
186     libFile = "libhunspell.so";
187 #endif
188 #endif
189     SString ret = libFile;
190     SFile flib (libFile, path);
191     if (flib.size() > 0)
192     {
193       libFile = flib.getName();
194       fixFileName (libFile);
195     }
196     libFile.append ((char)0);
197 #ifdef USE_WINAPI
198     SEncoder u8("utf-8");
199     SEncoder u16("utf-16-le");
200     SString res = u16.encode (u8.decode (libFile));
201     WCHAR* filenameW = (WCHAR *) res.array();
202     dynHandle = LoadLibraryW (filenameW);
203     if (!dynHandle)
204     {
205       dynHandle = LoadLibraryA (libFile.array());
206       if (!dynHandle) return SString(ret);
207     }
208 #else
209     dynHandle = dlopen (libFile.array(), RTLD_LAZY);
210     if (!dynHandle)
211     {
212       fprintf (stderr, "Can not open shared library %s\n", libFile.array());
213       return SString(ret);
214     }
215 #endif
216     return SString ();
217   }
218   return SString ();
219 }
220 
221 SString
getFolderFor(const SString & name,const SStringVector & path)222 SHunspellPattern::getFolderFor (const SString& name,
223   const SStringVector& path)
224 {
225   for (unsigned int i=0; i<path.size(); i++)
226   {
227     SString f = path[i];
228     f.append ("/");
229     f.append (name);
230     f.append (".dic");
231     SFile file (f);
232     if (file.size() > 0) return  SString (path[i]);
233   }
234   return SString ("");
235 }
236 
237 // Return a non "" string in case there are some missing files
238 SString
getMissingFile(const SString & name,const SStringVector & path)239 SHunspellPattern::getMissingFile (const SString& name,
240   const SStringVector& path)
241 {
242   SString missLib = loadLibrary (path);
243   if (missLib != "") return SString (missLib);
244   if (name == "")
245   {
246     return SString ("*.dic *.aff");
247   }
248   for (unsigned int i=0; i<path.size(); i++)
249   {
250     SString f_dic = path[i];
251     f_dic.append ("/");
252     f_dic.append (name);
253     f_dic.append (".dic");
254     SFile file_dic (f_dic);
255     // aff file should be in the same directory
256     if (file_dic.size() > 0)
257     {
258       SString f_aff = path[i];
259       f_aff.append ("/");
260       f_aff.append (name);
261       f_aff.append (".aff");
262       SFile file_aff (f_aff);
263       if (file_aff.size() > 0) return (SString (""));
264       f_aff = name;
265       f_aff.append (".aff");
266       return (SString (f_aff));
267     }
268   }
269   SString ret = name;
270   ret.append (".dic");
271   return SString (ret);
272 }
273 
274 
275 // This method updates matchBegin and matchEnd (exclusive)
276 // variables in case of a match, in case of no match, this
277 // will not be touched. Action will contain that action string.
278 bool
checkMatch()279 SHunspellPattern::checkMatch ()
280 {
281   if (current.size() == 0) return false;
282   if (current.size() == 1) return false;
283   // get the controls out first
284   SS_UCS4 chr = current[0];
285   if (isNumber (chr))
286   {
287     action = ACT_NUMBER;
288     matchBegin = matchEnd;
289     matchEnd++;
290     current.remove (0);
291     return true;
292   }
293   if (isOther (chr))
294   {
295     action = ACT_OTHER;
296     matchBegin = matchEnd;
297     matchEnd++;
298     current.remove (0);
299     return true;
300   }
301   if (isSeparator (chr) || chr == (SS_UCS4) '-')
302   {
303     action = ACT_CONTROL;
304     matchBegin = matchEnd;
305     matchEnd++;
306     current.remove (0);
307     return true;
308   }
309   if (isJapanese (chr))
310   {
311     action = ACT_NONE;
312     matchBegin = matchEnd;
313     matchEnd++;
314     current.remove (0);
315     return true;
316   }
317   unsigned int i;
318   for (i=0; i<current.size(); i++)
319   {
320     if (isSeparator (current[i])) break;
321   }
322   if (i==current.size()) return false;
323   if (i==0) return false; // never
324   action = ACT_NONE;
325   SV_UCS4 v = current;
326   v.truncate (v.size() -1);
327 
328   matchBegin = matchEnd;
329   matchEnd = matchBegin + v.size();
330   for (i=0; i<v.size(); i++)
331   {
332     current.remove (0);
333   }
334   SV_UCS4 v1;
335   convertRovasiras (v, v1);
336   SString str =  (entryEncoder==0) ? SString ("E_R_R_O_R") : entryEncoder->encode (v1);
337   str.append ((char) 0);
338 /*
339 fprintf (stderr, "call %08lx %08lx %s\n",
340    (unsigned long) functions.spell, (unsigned long) hunspell, str.array());
341 */
342   if ((*functions.spell)(hunspell, str.array())==0)
343   {
344     action = ACT_ERROR;
345   }
346   return true;
347 }
348 
349 // There is no spell checking for japanese/chinese
350 bool
isJapanese(SS_UCS4 chr) const351 SHunspellPattern::isJapanese (SS_UCS4 chr) const
352 {
353   if (chr==SD_CD_ZWJ) return false;
354   if (chr==SD_CD_ZWNJ) return false;
355   SD_CharClass cc = getCharClass (chr);
356   if (cc == SD_CC_Mn || cc == SD_CC_Me) return false;
357 
358 
359   // CJK Symbols and Punctuation
360   if (chr >= 0x3000 && chr <=0x303f) return true;
361   // Hiragana
362   if (chr >= 0x3040 && chr <=0x309f) return true;
363   // Katakana
364   if (chr >= 0x30A0 && chr <=0x30ff) return true;
365   //
366   if (chr >= 0x31c0 && chr <=0x9fff) return true;
367 
368   // Japanes fullwidth alphabet
369   if (chr >= 0xff21 && chr <=0xff5a) return false;
370 
371   // Halfwidth and Fullwidth Forms
372   if (chr >= 0xff00 && chr <=0xffef) return true;
373   // CJK Compatibility Ideographs
374   if (chr >= 0xf900 && chr <=0xfaff) return true;
375   // CJK Unified Ideographs Extension B
376   if (chr >= 0x20000 && chr <=0x2A6DF) return true;
377   // CJK Compatibility Ideographs Supplement
378   if (chr >= 0x2F800 && chr <=0x2FA1F) return true;
379   return false;
380 }
381 
382 // This includes numbers
383 bool
isSeparator(SS_UCS4 chr) const384 SHunspellPattern::isSeparator (SS_UCS4 chr) const
385 {
386   if (chr == 0xEE2F) return true; // HUNGARIAN RUNIC SEPARATOR in PUA
387 
388   if (chr==SD_CD_ZWJ) return false;
389   if (chr==SD_CD_ZWNJ) return false; // this can be used within the word
390   SD_CharClass cc = getCharClass (chr);
391   // combined
392   if (cc == SD_CC_Mn || cc == SD_CC_Me) return false;
393 
394   return (chr != (SS_UCS4) '-'
395        && cc != SD_CC_Lu && cc != SD_CC_Ll && cc != SD_CC_Lt
396        && cc != SD_CC_Lm && cc != SD_CC_Lo);
397 }
398 bool
isNumber(SS_UCS4 chr) const399 SHunspellPattern::isNumber (SS_UCS4 chr) const
400 {
401   SD_CharClass cc = getCharClass (chr);
402   return (cc == SD_CC_Nd || cc == SD_CC_Nl || cc == SD_CC_No);
403 }
404 bool
isOther(SS_UCS4 chr) const405 SHunspellPattern::isOther (SS_UCS4 chr) const
406 {
407   SD_CharClass cc = getCharClass (chr);
408   return (cc == SD_CC_So);
409 }
410 
411 // Convert rovasiras into standard hungarian, all caps
412 static void
convertRovasiras(const SV_UCS4 & in,SV_UCS4 & out)413 convertRovasiras (const SV_UCS4& in, SV_UCS4& out)
414 {
415   SS_UCS4 chr;
416   for (unsigned int i=0; i<in.size(); i++)
417   {
418     chr = in[i];
419     switch  ((int)chr)
420     {
421     case 0xEE00: out.append ((SS_UCS4) 'A'); break;
422     case 0xEE01: out.append ((SS_UCS4) 0xC1); break; // AA
423     case 0xEE02: out.append ((SS_UCS4) 'B'); break;
424     case 0xEE03: out.append ((SS_UCS4) 'C'); break;
425     case 0xEE04: out.append ((SS_UCS4) 'C'); out.append ((SS_UCS4) 'S'); break;
426     case 0xEE05: out.append ((SS_UCS4) 'D'); break;
427     case 0xEE06: out.append ((SS_UCS4) 'E'); break; // AE
428     case 0xEE07: out.append ((SS_UCS4) 'E'); break;
429     case 0xEE08: out.append ((SS_UCS4) 0xC9); break; // EE
430     case 0xEE09: out.append ((SS_UCS4) 'F'); break;
431     case 0xEE0A: out.append ((SS_UCS4) 'G'); break;
432     case 0xEE0B: out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'Y'); break;
433     case 0xEE0C: out.append ((SS_UCS4) 'H'); break;
434     case 0xEE0D: out.append ((SS_UCS4) 'I'); break;
435     case 0xEE0E: out.append ((SS_UCS4) 0xCD); break; // II
436     case 0xEE0F: out.append ((SS_UCS4) 'J'); break;
437     case 0xEE10: out.append ((SS_UCS4) 'K'); break; // AK
438     case 0xEE11: out.append ((SS_UCS4) 'K'); break; // EK
439     case 0xEE12: out.append ((SS_UCS4) 'L'); break;
440     case 0xEE13: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 'Y'); break;
441     case 0xEE14: out.append ((SS_UCS4) 'M'); break;
442     case 0xEE15: out.append ((SS_UCS4) 'N'); break;
443     case 0xEE16: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'Y'); break;
444     case 0xEE17: out.append ((SS_UCS4) 'O'); break;
445     case 0xEE18: out.append ((SS_UCS4) 0xD3); break; // OO
446     case 0xEE19: out.append ((SS_UCS4) 0xD6); break; // OE
447     case 0xEE1A: out.append ((SS_UCS4) 0x150); break; // OEE
448     case 0xEE1B: out.append ((SS_UCS4) 'P'); break;
449     case 0xEE1C: out.append ((SS_UCS4) 'R'); break;
450     case 0xEE1D: out.append ((SS_UCS4) 'S'); break; // AS
451     case 0xEE1E: out.append ((SS_UCS4) 'S'); break; // ES
452     case 0xEE1F: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'Z'); break;
453     case 0xEE20: out.append ((SS_UCS4) 'T'); break;
454     case 0xEE21: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'Y'); break; // ATY
455     case 0xEE22: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'Y'); break; // ETY
456     case 0xEE23: out.append ((SS_UCS4) 'U'); break;
457     case 0xEE24: out.append ((SS_UCS4) 0xDA); break; // UU
458     case 0xEE25: out.append ((SS_UCS4) 0xDC); break; // UE
459     case 0xEE26: out.append ((SS_UCS4) 0x170); break; // UEE
460     case 0xEE27: out.append ((SS_UCS4) 'V'); break;
461     case 0xEE28: out.append ((SS_UCS4) 'Z'); break;
462     case 0xEE29: out.append ((SS_UCS4) 'Z'); out.append ((SS_UCS4) 'S'); break;
463     case 0xEE2F: out.append ((SS_UCS4) ' '); break;
464 
465     // NUMBERS
466     case 0xEE31: out.append ((SS_UCS4) '1'); break;
467     case 0xEE35: out.append ((SS_UCS4) '5'); break;
468     case 0xEE3A: out.append ((SS_UCS4) '1'); out.append ((SS_UCS4) '0'); break;
469     case 0xEE3B: out.append ((SS_UCS4) '5'); out.append ((SS_UCS4) '0'); break;
470     case 0xEE3C: out.append ((SS_UCS4) '1'); out.append ((SS_UCS4) '0'); out.append ((SS_UCS4) '0');break;
471     case 0xEE3D: out.append ((SS_UCS4) '1'); out.append ((SS_UCS4) '0'); out.append ((SS_UCS4) '0'); out.append ((SS_UCS4) '0'); break;
472 
473     // LIGATURES
474     case 0xEE40: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'B'); break;
475     case 0xEE41: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'D'); break;
476     case 0xEE42: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'L'); break;
477     case 0xEE43: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'M'); out.append ((SS_UCS4) 'B'); break;
478     case 0xEE44: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'D'); break;
479     case 0xEE45: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'T'); break;
480     case 0xEE46: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'R'); break;
481     case 0xEE47: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'T'); break;
482     // AAR
483     case 0xEE48: out.append ((SS_UCS4) 0xC1); out.append ((SS_UCS4) 'R'); break;
484     case 0xEE49: out.append ((SS_UCS4) 'B'); out.append ((SS_UCS4) 'A'); break;
485     case 0xEE4A: out.append ((SS_UCS4) 'B'); out.append ((SS_UCS4) 'E'); break;
486     case 0xEE4B: out.append ((SS_UCS4) 'B'); out.append ((SS_UCS4) 'E'); out.append ((SS_UCS4) 'T'); break;
487     case 0xEE4C: out.append ((SS_UCS4) 'B'); out.append ((SS_UCS4) 'I'); break;
488     case 0xEE4D: out.append ((SS_UCS4) 'B'); out.append ((SS_UCS4) 'O'); break;
489     case 0xEE4E: out.append ((SS_UCS4) 'C'); out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'K'); break;
490     case 0xEE4F: out.append ((SS_UCS4) 'C'); out.append ((SS_UCS4) 'K'); break;
491 
492     case 0xEE50: out.append ((SS_UCS4) 'C'); out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'A'); break;
493     case 0xEE51: out.append ((SS_UCS4) 'C'); out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'I'); out.append ((SS_UCS4) 'N'); break;
494     case 0xEE52: out.append ((SS_UCS4) 'D'); out.append ((SS_UCS4) 'U'); break;
495     case 0xEE53: out.append ((SS_UCS4) 'E'); out.append ((SS_UCS4) 'M'); out.append ((SS_UCS4) 'P'); break;
496     case 0xEE54: out.append ((SS_UCS4) 'E'); out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'T'); break;
497     case 0xEE55: out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'A'); break;
498     case 0xEE56: out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'E'); break;
499     case 0xEE57: out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'I'); break;
500     case 0xEE58: out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'O'); break;
501     case 0xEE59: out.append ((SS_UCS4) 'H'); out.append ((SS_UCS4) 'A'); break;
502     case 0xEE5A: out.append ((SS_UCS4) 'H'); out.append ((SS_UCS4) 'E'); break;
503     case 0xEE5B: out.append ((SS_UCS4) 'H'); out.append ((SS_UCS4) 'I'); break;
504     case 0xEE5C: out.append ((SS_UCS4) 'H'); out.append ((SS_UCS4) 'O'); break;
505     case 0xEE5D: out.append ((SS_UCS4) 'I'); out.append ((SS_UCS4) 'T'); break;
506     case 0xEE5E: out.append ((SS_UCS4) 'I'); out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'T'); break;
507     case 0xEE5F: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 'A'); break;
508 
509     // LAA
510     case 0xEE60: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 0xC1); break;
511     case 0xEE61: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 'E'); break;
512     case 0xEE62: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 'O'); break;
513     case 0xEE63: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 'T'); break;
514     case 0xEE64: out.append ((SS_UCS4) 'M'); out.append ((SS_UCS4) 'B'); break;
515     case 0xEE65: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'A'); break;
516     case 0xEE66: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'P'); break;
517     case 0xEE67: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'B'); break;
518     case 0xEE68: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'C'); break;
519     case 0xEE69: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'D'); break;
520     case 0xEE6A: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'Y'); break;
521     case 0xEE6B: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'I'); break;
522     case 0xEE6C: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'K'); break;
523     case 0xEE6D: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'T'); break;
524     case 0xEE6E: out.append ((SS_UCS4) 'O'); out.append ((SS_UCS4) 'R'); break;
525     case 0xEE6F: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'A'); break;
526 
527 
528     case 0xEE70: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'E'); break;
529     case 0xEE71: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'I'); break;
530     case 0xEE72: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'O'); break;
531     case 0xEE73: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'T'); break;
532     case 0xEE74: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'U'); break;
533     case 0xEE75: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'A'); break;
534     case 0xEE76: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'E'); break;
535     case 0xEE77: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'I'); break;
536     case 0xEE78: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'K'); break;
537     case 0xEE79: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'M'); break;
538     case 0xEE7A: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'O'); break;
539     case 0xEE7B: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'P'); break;
540     case 0xEE7C: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'T'); break;
541     case 0xEE7D: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'Z'); out.append ((SS_UCS4) 'T'); break;
542     case 0xEE7E: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'I'); break;
543     case 0xEE7F: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'P'); out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'U'); break;
544 
545     case 0xEE80: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'P'); out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'S'); break;
546     case 0xEE81: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'Y'); out.append ((SS_UCS4) 'A'); break;
547     case 0xEE82: out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'L'); break;
548     case 0xEE83: out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'M'); break;
549     case 0xEE84: out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'K');break;
550     case 0xEE85: out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'R'); break;
551     case 0xEE86: out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'S'); break;
552     case 0xEE87: out.append ((SS_UCS4) 'V'); out.append ((SS_UCS4) 'A'); break;
553     // VAAR
554     case 0xEE88: out.append ((SS_UCS4) 'V'); out.append ((SS_UCS4) 0xc1); out.append ((SS_UCS4) 'R'); break;
555     case 0xEE89: out.append ((SS_UCS4) 'Z'); out.append ((SS_UCS4) 'A'); break;
556     case 0xEE8A: out.append ((SS_UCS4) 'Z'); out.append ((SS_UCS4) 'R'); break;
557     case 0xEE8B: out.append ((SS_UCS4) 'Z'); out.append ((SS_UCS4) 'T');  break;
558     default: out.append (chr);
559       break;
560    }
561  }
562 }
563