1 /*
2  * Copyright 2002 Paulo Soares
3  *
4  * The contents of this file are subject to the Mozilla Public License Version 1.1
5  * (the "License"); you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
7  *
8  * Software distributed under the License is distributed on an "AS IS" basis,
9  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10  * for the specific language governing rights and limitations under the License.
11  *
12  * The Original Code is 'iText, a free JAVA-PDF library'.
13  *
14  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
15  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
16  * All Rights Reserved.
17  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
18  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
19  *
20  * Contributor(s): all the names of the contributors are added in the source code
21  * where applicable.
22  *
23  * Alternatively, the contents of this file may be used under the terms of the
24  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
25  * provisions of LGPL are applicable instead of those above.  If you wish to
26  * allow use of your version of this file only under the terms of the LGPL
27  * License and not to allow others to use your version of this file under
28  * the MPL, indicate your decision by deleting the provisions above and
29  * replace them with the notice and other provisions required by the LGPL.
30  * If you do not delete the provisions above, a recipient may use your version
31  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
32  *
33  * This library is free software; you can redistribute it and/or modify it
34  * under the terms of the MPL as stated above or under the terms of the GNU
35  * Library General Public License as published by the Free Software Foundation;
36  * either version 2 of the License, or any later version.
37  *
38  * This library is distributed in the hope that it will be useful, but WITHOUT
39  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
40  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
41  * details.
42  *
43  * If you didn't download this code from the following link, you should check if
44  * you aren't using an obsolete version:
45  * http://www.lowagie.com/iText/
46  */

47
48 package com.lowagie.text.pdf;
49 import java.io.BufferedReader;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.InputStreamReader;
53 import java.io.UnsupportedEncodingException;
54 import java.util.ArrayList;
55 import java.util.HashMap;
56 import java.util.StringTokenizer;
57
58 import com.lowagie.text.ExceptionConverter;
59 /** Supports fast encodings for winansi and PDFDocEncoding.
60  * Supports conversions from CJK encodings to CID.
61  * Supports custom encodings.
62  * @author Paulo Soares (psoares@consiste.pt)
63  */

64 public class PdfEncodings {
65     protected static final int CIDNONE = 0;
66     protected static final int CIDRANGE = 1;
67     protected static final int CIDCHAR = 2;
68
69     static final char winansiByteToChar[] = {
70         0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 
71         16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 
72         32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 
73         48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 
74         64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 
75         80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 
76         96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 
77         112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 
78         8364, 65533, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338, 65533, 381, 65533, 
79         65533, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732, 8482, 353, 8250, 339, 65533, 382, 376, 
80         160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 
81         176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 
82         192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 
83         208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 
84         224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 
85         240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255};
86         
87     static final char pdfEncodingByteToChar[] = {
88         0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 
89         16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 
90         32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 
91         48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 
92         64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 
93         80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 
94         96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 
95         112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 
96         0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x0192, 0x2044, 0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018,
97         0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x0141, 0x0152, 0x0160, 0x0178, 0x017d, 0x0131, 0x0142, 0x0153, 0x0161, 0x017e, 65533,
98         0x20ac, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 
99         176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 
100         192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 
101         208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 
102         224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 
103         240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255};
104         
105     static final IntHashtable winansi = new IntHashtable();
106     
107     static final IntHashtable pdfEncoding = new IntHashtable();
108     
109     static HashMap extraEncodings = new HashMap();
110     
111     static {        
112         for (int k = 128; k < 161; ++k) {
113             char c = winansiByteToChar[k];
114             if (c != 65533)
115                 winansi.put(c, k);
116         }
117
118         for (int k = 128; k < 161; ++k) {
119             char c = pdfEncodingByteToChar[k];
120             if (c != 65533)
121                 pdfEncoding.put(c, k);
122         }
123         
124         addExtraEncoding("Wingdings"new WingdingsConversion());
125         addExtraEncoding("Symbol"new SymbolConversion(true));
126         addExtraEncoding("ZapfDingbats"new SymbolConversion(false));
127         addExtraEncoding("SymbolTT"new SymbolTTConversion());
128         addExtraEncoding("Cp437"new Cp437Conversion());
129     }
130
131     /** Converts a <CODE>String</CODE> to a </CODE>byte</CODE> array according
132      * to the font's encoding.
133      * @return an array of <CODE>byte</CODE> representing the conversion according to the font's encoding
134      * @param encoding the encoding
135      * @param text the <CODE>String</CODE> to be converted
136      */

137     public static final byte[] convertToBytes(String text, String encoding) {
138         if (text == null)
139             return new byte[0];
140         if (encoding == null || encoding.length() == 0) {
141             int len = text.length();
142             byte b[] = new byte[len];
143             for (int k = 0; k < len; ++k)
144                 b[k] = (byte)text.charAt(k);
145             return b;
146         }
147         ExtraEncoding extra = (ExtraEncoding)extraEncodings.get(encoding.toLowerCase());
148         if (extra != null) {
149             byte b[] = extra.charToByte(text, encoding);
150             if (b != null)
151                 return b;
152         }
153         IntHashtable hash = null;
154         if (encoding.equals(BaseFont.WINANSI))
155             hash = winansi;
156         else if (encoding.equals(PdfObject.TEXT_PDFDOCENCODING))
157             hash = pdfEncoding;
158         if (hash != null) {
159             char cc[] = text.toCharArray();
160             int len = cc.length;
161             int ptr = 0;
162             byte b[] = new byte[len];
163             int c = 0;
164             for (int k = 0; k < len; ++k) {
165                 char char1 = cc[k];
166                 if (char1 < 128 || (char1 > 160 && char1 <= 255))
167                     c = char1;
168                 else
169                     c = hash.get(char1);
170                 if (c != 0)
171                     b[ptr++] = (byte)c;
172             }
173             if (ptr == len)
174                 return b;
175             byte b2[] = new byte[ptr];
176             System.arraycopy(b, 0, b2, 0, ptr);
177             return b2;
178         }
179         if (encoding.equals(PdfObject.TEXT_UNICODE)) {
180             // workaround for jdk 1.2.2 bug
181             char cc[] = text.toCharArray();
182             int len = cc.length;
183             byte b[] = new byte[cc.length * 2 + 2];
184             b[0] = -2;
185             b[1] = -1;
186             int bptr = 2;
187             for (int k = 0; k < len; ++k) {
188                 char c = cc[k];
189                 b[bptr++] = (byte)(c >> 8);
190                 b[bptr++] = (byte)(c & 0xff);
191             }
192             return b;
193         }
194         try {
195             return text.getBytes(encoding);
196         }
197         catch (UnsupportedEncodingException e) {
198             throw new ExceptionConverter(e);
199         }
200     }
201     
202     /** Converts a <CODE>String</CODE> to a </CODE>byte</CODE> array according
203      * to the font's encoding.
204      * @return an array of <CODE>byte</CODE> representing the conversion according to the font's encoding
205      * @param encoding the encoding
206      * @param char1 the <CODE>char</CODE> to be converted
207      */

208     public static final byte[] convertToBytes(char char1, String encoding) {
209         if (encoding == null || encoding.length() == 0)
210             return new byte[]{(byte)char1};
211         ExtraEncoding extra = (ExtraEncoding)extraEncodings.get(encoding.toLowerCase());
212         if (extra != null) {
213             byte b[] = extra.charToByte(char1, encoding);
214             if (b != null)
215                 return b;
216         }
217         IntHashtable hash = null;
218         if (encoding.equals(BaseFont.WINANSI))
219             hash = winansi;
220         else if (encoding.equals(PdfObject.TEXT_PDFDOCENCODING))
221             hash = pdfEncoding;
222         if (hash != null) {
223             int c = 0;
224             if (char1 < 128 || (char1 > 160 && char1 <= 255))
225                 c = char1;
226             else
227                 c = hash.get(char1);
228             if (c != 0)
229                 return new byte[]{(byte)c};
230             else
231                 return new byte[0];
232         }
233         if (encoding.equals(PdfObject.TEXT_UNICODE)) {
234             // workaround for jdk 1.2.2 bug
235             byte b[] = new byte[4];
236             b[0] = -2;
237             b[1] = -1;
238             b[2] = (byte)(char1 >> 8);
239             b[3] = (byte)(char1 & 0xff);
240             return b;
241         }
242         try {
243             return String.valueOf(char1).getBytes(encoding);
244         }
245         catch (UnsupportedEncodingException e) {
246             throw new ExceptionConverter(e);
247         }
248     }
249     
250     /** Converts a </CODE>byte</CODE> array to a <CODE>String</CODE> according
251      * to the some encoding.
252      * @param bytes the bytes to convert
253      * @param encoding the encoding
254      * @return the converted <CODE>String</CODE>
255      */
    
256     public static final String convertToString(byte bytes[], String encoding) {
257         if (bytes == null)
258             return PdfObject.NOTHING;
259         if (encoding == null || encoding.length() == 0) {
260             char c[] = new char[bytes.length];
261             for (int k = 0; k < bytes.length; ++k)
262                 c[k] = (char)(bytes[k] & 0xff);
263             return new String(c);
264         }
265         ExtraEncoding extra = (ExtraEncoding)extraEncodings.get(encoding.toLowerCase());
266         if (extra != null) {
267             String text = extra.byteToChar(bytes, encoding);
268             if (text != null)
269                 return text;
270         }
271         char ch[] = null;
272         if (encoding.equals(BaseFont.WINANSI))
273             ch = winansiByteToChar;
274         else if (encoding.equals(PdfObject.TEXT_PDFDOCENCODING))
275             ch = pdfEncodingByteToChar;
276         if (ch != null) {
277             int len = bytes.length;
278             char c[] = new char[len];
279             for (int k = 0; k < len; ++k) {
280                 c[k] = ch[bytes[k] & 0xff];
281             }
282             return new String(c);
283         }
284         try {
285             return new String(bytes, encoding);
286         }
287         catch (UnsupportedEncodingException e) {
288             throw new ExceptionConverter(e);
289         }
290     }
291     
292     /** Checks is <CODE>text</CODE> only has PdfDocEncoding characters.
293      * @param text the <CODE>String</CODE> to test
294      * @return <CODE>true</CODE> if only PdfDocEncoding characters are present
295      */
    
296     public static boolean isPdfDocEncoding(String text) {
297         if (text == null)
298             return true;
299         int len = text.length();
300         for (int k = 0; k < len; ++k) {
301             char char1 = text.charAt(k);
302             if (char1 < 128 || (char1 > 160 && char1 <= 255))
303                 continue;
304             if (!pdfEncoding.containsKey(char1))
305                 return false;
306         }
307         return true;
308     }
309     
310     static final HashMap cmaps = new HashMap();
311     /** Assumes that '\\n' and '\\r\\n' are the newline sequences. It may not work for
312      * all CJK encodings. To be used with loadCmap().
313      */
    
314     public static final byte CRLF_CID_NEWLINE[][] = new byte[][]{{(byte)'\n'}, {(byte)'\r', (byte)'\n'}};
315
316     /** Clears the CJK cmaps from the cache. If <CODE>name</CODE> is the
317      * empty string then all the cache is cleared. Calling this method
318      * has no consequences other than the need to reload the cmap
319      * if needed.
320      * @param name the name of the cmap to clear or all the cmaps if the empty string
321      */
    
322     public static void clearCmap(String name) {
323         synchronized (cmaps) {
324             if (name.length() == 0)
325                 cmaps.clear();
326             else
327                 cmaps.remove(name);
328         }
329     }
330     
331     /** Loads a CJK cmap to the cache with the option of associating
332      * sequences to the newline.
333      * @param name the CJK cmap name
334      * @param newline the sequences to be replaced by a newline in the resulting CID. See <CODE>CRLF_CID_NEWLINE</CODE>
335      */
    
336     public static void loadCmap(String name, byte newline[][]) {
337         try {
338             char planes[][] = null;
339             synchronized (cmaps) {
340                 planes = (char[][])cmaps.get(name);
341             }
342             if (planes == null) {
343                 planes = readCmap(name, newline);
344                 synchronized (cmaps) {
345                     cmaps.put(name, planes);
346                 }
347             }
348         }
349         catch (IOException e) {
350             throw new ExceptionConverter(e);
351         }        
352     }
353     
354     /** Converts a <CODE>byte</CODE> array encoded as <CODE>name</CODE>
355      * to a CID string. This is needed to reach some CJK characters
356      * that don't exist in 16 bit Unicode.</p>
357      * The font to use this result must use the encoding "Identity-H"
358      * or "Identity-V".</p>
359      * See ftp://ftp.oreilly.com/pub/examples/nutshell/cjkv/adobe/.
360      * @param name the CJK encoding name
361      * @param seq the <CODE>byte</CODE> array to be decoded
362      * @return the CID string
363      */
    
364     public static String convertCmap(String name, byte seq[]) {
365         return convertCmap(name, seq, 0, seq.length);
366     }
367     
368     /** Converts a <CODE>byte</CODE> array encoded as <CODE>name</CODE>
369      * to a CID string. This is needed to reach some CJK characters
370      * that don't exist in 16 bit Unicode.</p>
371      * The font to use this result must use the encoding "Identity-H"
372      * or "Identity-V".</p>
373      * See ftp://ftp.oreilly.com/pub/examples/nutshell/cjkv/adobe/.
374      * @param name the CJK encoding name
375      * @param start the start offset in the data
376      * @param length the number of bytes to convert
377      * @param seq the <CODE>byte</CODE> array to be decoded
378      * @return the CID string
379      */
    
380     public static String convertCmap(String name, byte seq[], int start, int length) {
381         try {
382             char planes[][] = null;
383             synchronized (cmaps) {
384                 planes = (char[][])cmaps.get(name);
385             }
386             if (planes == null) {
387                 planes = readCmap(name, (byte[][])null);
388                 synchronized (cmaps) {
389                     cmaps.put(name, planes);
390                 }
391             }
392             return decodeSequence(seq, start, length, planes);
393         }
394         catch (IOException e) {
395             throw new ExceptionConverter(e);
396         }        
397     }
398     
399     static String decodeSequence(byte seq[], int start, int length, char planes[][]) {
400         StringBuffer buf = new StringBuffer();
401         int end = start + length;
402         int currentPlane = 0;
403         for (int k = start; k < end; ++k) {
404             int one = seq[k] & 0xff;
405             char plane[] = planes[currentPlane];
406             int cid = plane[one];
407             if ((cid & 0x8000) == 0) {
408                 buf.append((char)cid);
409                 currentPlane = 0;
410             }
411             else
412                 currentPlane = cid & 0x7fff;
413         }
414         return buf.toString();
415     }
416
417     static char[][] readCmap(String name, byte newline[][]) throws IOException {
418         ArrayList planes = new ArrayList();
419         planes.add(new char[256]);
420         readCmap(name, planes);
421         if (newline != null) {
422             for (int k = 0; k < newline.length; ++k)
423                 encodeSequence(newline[k].length, newline[k], BaseFont.CID_NEWLINE, planes);
424         }
425         char ret[][] = new char[planes.size()][];
426         return (char[][])planes.toArray(ret);
427     }
428     
429     static void readCmap(String name, ArrayList planes) throws IOException {
430         String fullName = BaseFont.RESOURCE_PATH + "cmaps/" + name;
431         InputStream in = BaseFont.getResourceStream(fullName);
432         if (in == null)
433             throw new IOException("The Cmap " + name + " was not found.");
434         encodeStream(in, planes);
435         in.close();
436     }
437     
438     static void encodeStream(InputStream in, ArrayList planes) throws IOException {
439         BufferedReader rd = new BufferedReader(new InputStreamReader(in, "iso-8859-1"));
440         String line = null;
441         int state = CIDNONE;
442         byte seqs[] = new byte[7];
443         while ((line = rd.readLine()) != null) {
444             if (line.length() < 6)
445                 continue;
446             switch (state) {
447                 case CIDNONE: {
448                     if (line.indexOf("begincidrange") >= 0)
449                         state = CIDRANGE;
450                     else if (line.indexOf("begincidchar") >= 0)
451                         state = CIDCHAR;
452                     else if (line.indexOf("usecmap") >= 0) {
453                         StringTokenizer tk = new StringTokenizer(line);
454                         String t = tk.nextToken();
455                         readCmap(t.substring(1), planes);
456                     }
457                     break;
458                 }
459                 case CIDRANGE: {
460                     if (line.indexOf("endcidrange") >= 0) {
461                         state = CIDNONE;
462                         break;
463                     }
464                     StringTokenizer tk = new StringTokenizer(line);
465                     String t = tk.nextToken();
466                     int size = t.length() / 2 - 1;
467                     long start = Long.parseLong(t.substring(1, t.length() - 1), 16);
468                     t = tk.nextToken();
469                     long end = Long.parseLong(t.substring(1, t.length() - 1), 16);
470                     t = tk.nextToken();
471                     int cid = Integer.parseInt(t);
472                     for (long k = start; k <= end; ++k) {
473                         breakLong(k, size, seqs);
474                         encodeSequence(size, seqs, (char)cid, planes);
475                         ++cid;
476                     }
477                     break;
478                 }
479                 case CIDCHAR: {
480                     if (line.indexOf("endcidchar") >= 0) {
481                         state = CIDNONE;
482                         break;
483                     }
484                     StringTokenizer tk = new StringTokenizer(line);
485                     String t = tk.nextToken();
486                     int size = t.length() / 2 - 1;
487                     long start = Long.parseLong(t.substring(1, t.length() - 1), 16);
488                     t = tk.nextToken();
489                     int cid = Integer.parseInt(t);
490                     breakLong(start, size, seqs);
491                     encodeSequence(size, seqs, (char)cid, planes);
492                     break;
493                 }
494             }
495         }
496     }
497     
498     static void breakLong(long n, int size, byte seqs[]) {
499         for (int k = 0; k < size; ++k) {
500             seqs[k] = (byte)(n >> ((size - 1 - k) * 8));
501         }
502     }
503
504     static void encodeSequence(int size, byte seqs[], char cid, ArrayList planes) {
505         --size;
506         int nextPlane = 0;
507         for (int idx = 0; idx < size; ++idx) {
508             char plane[] = (char[])planes.get(nextPlane);
509             int one = seqs[idx] & 0xff;
510             char c = plane[one];
511             if (c != 0 && (c & 0x8000) == 0)
512                 throw new RuntimeException("Inconsistent mapping.");
513             if (c == 0) {
514                 planes.add(new char[256]);
515                 c = (char)((planes.size() - 1) | 0x8000);
516                 plane[one] = c;
517             }
518             nextPlane = c & 0x7fff;
519         }
520         char plane[] = (char[])planes.get(nextPlane);
521         int one = seqs[size] & 0xff;
522         char c = plane[one];
523         if ((c & 0x8000) != 0)
524             throw new RuntimeException("Inconsistent mapping.");
525         plane[one] = cid;
526     }
527
528     /** Adds an extra encoding.
529      * @param name the name of the encoding. The encoding recognition is case insensitive
530      * @param enc the conversion class
531      */
    
532     public static void addExtraEncoding(String name, ExtraEncoding enc) {
533         synchronized (extraEncodings) { // This serializes concurrent updates
534             HashMap newEncodings = (HashMap)extraEncodings.clone();
535             newEncodings.put(name.toLowerCase(), enc);
536             extraEncodings = newEncodings;  // This swap does not require synchronization with reader
537         }
538     }
539     
540     private static class WingdingsConversion implements ExtraEncoding {
541         
542         public byte[] charToByte(char char1, String encoding) {
543             if (char1 == ' ')
544                 return new byte[]{(byte)char1};
545             else if (char1 >= '\u2701' && char1 <= '\u27BE') {
546                 byte v = table[char1 - 0x2700];
547                 if (v != 0)
548                     return new byte[]{v};
549             }
550             return new byte[0];
551         }
552         
553         public byte[] charToByte(String text, String encoding) {
554             char cc[] = text.toCharArray();
555             byte b[] = new byte[cc.length];
556             int ptr = 0;
557             int len = cc.length;
558             for (int k = 0; k < len; ++k) {
559                 char c = cc[k];
560                 if (c == ' ')
561                     b[ptr++] = (byte)c;
562                 else if (c >= '\u2701' && c <= '\u27BE') {
563                     byte v = table[c - 0x2700];
564                     if (v != 0)
565                         b[ptr++] = v;
566                 }
567             }
568             if (ptr == len)
569                 return b;
570             byte b2[] = new byte[ptr];
571             System.arraycopy(b, 0, b2, 0, ptr);
572             return b2;
573         }
574         
575         public String byteToChar(byte[] b, String encoding) {
576             return null;
577         }
578
579         private final static byte table[] = {
580             0, 35, 34, 0, 0, 0, 41, 62, 81, 42, 
581             0, 0, 65, 63, 0, 0, 0, 0, 0, -4, 
582             0, 0, 0, -5, 0, 0, 0, 0, 0, 0, 
583             86, 0, 88, 89, 0, 0, 0, 0, 0, 0, 
584             0, 0, -75, 0, 0, 0, 0, 0, -74, 0, 
585             0, 0, -83, -81, -84, 0, 0, 0, 0, 0, 
586             0, 0, 0, 124, 123, 0, 0, 0, 84, 0, 
587             0, 0, 0, 0, 0, 0, 0, -90, 0, 0, 
588             0, 113, 114, 0, 0, 0, 117, 0, 0, 0, 
589             0, 0, 0, 125, 126, 0, 0, 0, 0, 0, 
590             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
591             0, 0, 0, 0, 0, 0, 0, 0, -116, -115, 
592             -114, -113, -112, -111, -110, -109, -108, -107, -127, -126, 
593             -125, -124, -123, -122, -121, -120, -119, -118, -116, -115, 
594             -114, -113, -112, -111, -110, -109, -108, -107, -24, 0, 
595             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
596             0, -24, -40, 0, 0, -60, -58, 0, 0, -16, 
597             0, 0, 0, 0, 0, 0, 0, 0, 0, -36, 
598             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
599             0
600         };
601     }
602
603     private static class Cp437Conversion implements ExtraEncoding {
604         private static IntHashtable c2b = new IntHashtable();
605         
606         public byte[] charToByte(String text, String encoding) {
607             char cc[] = text.toCharArray();
608             byte b[] = new byte[cc.length];
609             int ptr = 0;
610             int len = cc.length;
611             for (int k = 0; k < len; ++k) {
612                 char c = cc[k];
613                 if (c < 128)
614                     b[ptr++] = (byte)c;
615                 else {
616                     byte v = (byte)c2b.get(c);
617                     if (v != 0)
618                         b[ptr++] = v;
619                 }
620             }
621             if (ptr == len)
622                 return b;
623             byte b2[] = new byte[ptr];
624             System.arraycopy(b, 0, b2, 0, ptr);
625             return b2;
626         }
627         
628         public byte[] charToByte(char char1, String encoding) {
629             if (char1 < 128)
630                 return new byte[]{(byte)char1};
631             else {
632                 byte v = (byte)c2b.get(char1);
633                 if (v != 0)
634                     return new byte[]{v};
635                 else
636                     return new byte[0];
637             }
638         }
639         
640         public String byteToChar(byte[] b, String encoding) {
641             int len = b.length;
642             char cc[] = new char[len];
643             int ptr = 0;
644             for (int k = 0; k < len; ++k) {
645                 int c = b[k] & 0xff;
646                 if (c < ' ')
647                     continue;
648                 if (c < 128)
649                     cc[ptr++] = (char)c;
650                 else {
651                     char v = table[c - 128];
652                     cc[ptr++] = v;
653                 }
654             }
655             return new String(cc, 0, ptr);
656         }
657         
658         private final static char table[] = {
659             '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB', '\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5',
660             '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', '\u00FF', '\u00D6', '\u00DC', '\u00A2', '\u00A3', '\u00A5', '\u20A7', '\u0192',
661             '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', '\u00AA', '\u00BA', '\u00BF', '\u2310', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB',
662             '\u2591', '\u2592', '\u2593', '\u2502', '\u2524', '\u2561', '\u2562', '\u2556', '\u2555', '\u2563', '\u2551', '\u2557', '\u255D', '\u255C', '\u255B', '\u2510',
663             '\u2514', '\u2534', '\u252C', '\u251C', '\u2500', '\u253C', '\u255E', '\u255F', '\u255A', '\u2554', '\u2569', '\u2566', '\u2560', '\u2550', '\u256C', '\u2567',
664             '\u2568', '\u2564', '\u2565', '\u2559', '\u2558', '\u2552', '\u2553', '\u256B', '\u256A', '\u2518', '\u250C', '\u2588', '\u2584', '\u258C', '\u2590', '\u2580',
665             '\u03B1', '\u00DF', '\u0393', '\u03C0', '\u03A3', '\u03C3', '\u00B5', '\u03C4', '\u03A6', '\u0398', '\u03A9', '\u03B4', '\u221E', '\u03C6', '\u03B5', '\u2229',
666             '\u2261', '\u00B1', '\u2265', '\u2264', '\u2320', '\u2321', '\u00F7', '\u2248', '\u00B0', '\u2219', '\u00B7', '\u221A', '\u207F', '\u00B2', '\u25A0', '\u00A0'
667         };
668         
669         static {
670             for (int k = 0; k < table.length; ++k)
671                 c2b.put(table[k], k + 128);
672         }
673     }
674     
675     private static class SymbolConversion implements ExtraEncoding {
676         
677         private static final IntHashtable t1 = new IntHashtable();
678         private static final IntHashtable t2 = new IntHashtable();
679         private IntHashtable translation;
680         
681         SymbolConversion(boolean symbol) {
682             if (symbol)
683                 translation = t1;
684             else
685                 translation = t2;
686         }
687         
688         public byte[] charToByte(String text, String encoding) {
689             char cc[] = text.toCharArray();
690             byte b[] = new byte[cc.length];
691             int ptr = 0;
692             int len = cc.length;
693             for (int k = 0; k < len; ++k) {
694                 char c = cc[k];
695                 byte v = (byte)translation.get(c);
696                 if (v != 0)
697                     b[ptr++] = v;
698             }
699             if (ptr == len)
700                 return b;
701             byte b2[] = new byte[ptr];
702             System.arraycopy(b, 0, b2, 0, ptr);
703             return b2;
704         }
705         
706         public byte[] charToByte(char char1, String encoding) {
707             byte v = (byte)translation.get(char1);
708             if (v != 0)
709                 return new byte[]{v};
710             else
711                 return new byte[0];
712         }
713         
714         public String byteToChar(byte[] b, String encoding) {
715             return null;
716         }
717
718         private final static char table1[] = {
719             ' ','!','\u2200','#','\u2203','%','&','\u220b','(',')','*','+',',','-','.','/',
720             '0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?',
721             '\u2245','\u0391','\u0392','\u03a7','\u0394','\u0395','\u03a6','\u0393','\u0397','\u0399','\u03d1','\u039a','\u039b','\u039c','\u039d','\u039f',
722             '\u03a0','\u0398','\u03a1','\u03a3','\u03a4','\u03a5','\u03c2','\u03a9','\u039e','\u03a8','\u0396','[','\u2234',']','\u22a5','_',
723             '\u0305','\u03b1','\u03b2','\u03c7','\u03b4','\u03b5','\u03d5','\u03b3','\u03b7','\u03b9','\u03c6','\u03ba','\u03bb','\u03bc','\u03bd','\u03bf',
724             '\u03c0','\u03b8','\u03c1','\u03c3','\u03c4','\u03c5','\u03d6','\u03c9','\u03be','\u03c8','\u03b6','{','|','}','~','\0',
725             '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',
726             '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',
727             '\u20ac','\u03d2','\u2032','\u2264','\u2044','\u221e','\u0192','\u2663','\u2666','\u2665','\u2660','\u2194','\u2190','\u2191','\u2192','\u2193',
728             '\u00b0','\u00b1','\u2033','\u2265','\u00d7','\u221d','\u2202','\u2022','\u00f7','\u2260','\u2261','\u2248','\u2026','\u2502','\u2500','\u21b5',
729             '\u2135','\u2111','\u211c','\u2118','\u2297','\u2295','\u2205','\u2229','\u222a','\u2283','\u2287','\u2284','\u2282','\u2286','\u2208','\u2209',
730             '\u2220','\u2207','\u00ae','\u00a9','\u2122','\u220f','\u221a','\u2022','\u00ac','\u2227','\u2228','\u21d4','\u21d0','\u21d1','\u21d2','\u21d3',
731             '\u25ca','\u2329','\0','\0','\0','\u2211','\u239b','\u239c','\u239d','\u23a1','\u23a2','\u23a3','\u23a7','\u23a8','\u23a9','\u23aa',
732             '\0','\u232a','\u222b','\u2320','\u23ae','\u2321','\u239e','\u239f','\u23a0','\u23a4','\u23a5','\u23a6','\u23ab','\u23ac','\u23ad','\0'
733         };
734
735         private final static char table2[] = {
736             '\u0020','\u2701','\u2702','\u2703','\u2704','\u260e','\u2706','\u2707','\u2708','\u2709','\u261b','\u261e','\u270C','\u270D','\u270E','\u270F',
737             '\u2710','\u2711','\u2712','\u2713','\u2714','\u2715','\u2716','\u2717','\u2718','\u2719','\u271A','\u271B','\u271C','\u271D','\u271E','\u271F',
738             '\u2720','\u2721','\u2722','\u2723','\u2724','\u2725','\u2726','\u2727','\u2605','\u2729','\u272A','\u272B','\u272C','\u272D','\u272E','\u272F',
739             '\u2730','\u2731','\u2732','\u2733','\u2734','\u2735','\u2736','\u2737','\u2738','\u2739','\u273A','\u273B','\u273C','\u273D','\u273E','\u273F',
740             '\u2740','\u2741','\u2742','\u2743','\u2744','\u2745','\u2746','\u2747','\u2748','\u2749','\u274A','\u274B','\u25cf','\u274D','\u25a0','\u274F',
741             '\u2750','\u2751','\u2752','\u25b2','\u25bc','\u25c6','\u2756','\u25d7','\u2758','\u2759','\u275A','\u275B','\u275C','\u275D','\u275E','\u0000',
742             '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',
743             '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0',
744             '\u0000','\u2761','\u2762','\u2763','\u2764','\u2765','\u2766','\u2767','\u2663','\u2666','\u2665','\u2660','\u2460','\u2461','\u2462','\u2463',
745             '\u2464','\u2465','\u2466','\u2467','\u2468','\u2469','\u2776','\u2777','\u2778','\u2779','\u277A','\u277B','\u277C','\u277D','\u277E','\u277F',
746             '\u2780','\u2781','\u2782','\u2783','\u2784','\u2785','\u2786','\u2787','\u2788','\u2789','\u278A','\u278B','\u278C','\u278D','\u278E','\u278F',
747             '\u2790','\u2791','\u2792','\u2793','\u2794','\u2192','\u2194','\u2195','\u2798','\u2799','\u279A','\u279B','\u279C','\u279D','\u279E','\u279F',
748             '\u27A0','\u27A1','\u27A2','\u27A3','\u27A4','\u27A5','\u27A6','\u27A7','\u27A8','\u27A9','\u27AA','\u27AB','\u27AC','\u27AD','\u27AE','\u27AF',
749             '\u0000','\u27B1','\u27B2','\u27B3','\u27B4','\u27B5','\u27B6','\u27B7','\u27B8','\u27B9','\u27BA','\u27BB','\u27BC','\u27BD','\u27BE','\u0000'
750         };
751
752         static {
753             for (int k = 0; k < table1.length; ++k) {
754                 int v = table1[k];
755                 if (v != 0)
756                     t1.put(v, k + 32);
757             }
758             for (int k = 0; k < table2.length; ++k) {
759                 int v = table2[k];
760                 if (v != 0)
761                     t2.put(v, k + 32);
762             }
763         }
764     }
765     
766     private static class SymbolTTConversion implements ExtraEncoding {
767         
768         public byte[] charToByte(char char1, String encoding) {
769             if ((char1 & 0xff00) == 0 || (char1 & 0xff00) == 0xf000)
770                 return new byte[]{(byte)char1};
771             else
772                 return new byte[0];
773         }
774         
775         public byte[] charToByte(String text, String encoding) {
776             char ch[] = text.toCharArray();
777             byte b[] = new byte[ch.length];
778             int ptr = 0;
779             int len = ch.length;
780             for (int k = 0; k < len; ++k) {
781                 char c = ch[k];
782                 if ((c & 0xff00) == 0 || (c & 0xff00) == 0xf000)
783                     b[ptr++] = (byte)c;
784             }
785             if (ptr == len)
786                 return b;
787             byte b2[] = new byte[ptr];
788             System.arraycopy(b, 0, b2, 0, ptr);
789             return b2;
790         }
791         
792         public String byteToChar(byte[] b, String encoding) {
793             return null;
794         }
795         
796     }
797 }
798