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

48
49 package com.lowagie.text.pdf;
50
51 import java.util.ArrayList;
52
53 import com.lowagie.text.Chunk;
54 import com.lowagie.text.Utilities;
55
56 /** Does all the line bidirectional processing with PdfChunk assembly.
57  *
58  * @author Paulo Soares (psoares@consiste.pt)
59  */

60 public class BidiLine {
61     
62     protected int runDirection;
63     protected int pieceSize = 256;
64     protected char text[] = new char[pieceSize];
65     protected PdfChunk detailChunks[] = new PdfChunk[pieceSize];
66     protected int totalTextLength = 0;
67     
68     protected byte orderLevels[] = new byte[pieceSize];
69     protected int indexChars[] = new int[pieceSize];
70     
71     protected ArrayList chunks = new ArrayList();
72     protected int indexChunk = 0;
73     protected int indexChunkChar = 0;
74     protected int currentChar = 0;
75     
76     protected int storedRunDirection;
77     protected char storedText[] = new char[0];
78     protected PdfChunk storedDetailChunks[] = new PdfChunk[0];
79     protected int storedTotalTextLength = 0;
80     
81     protected byte storedOrderLevels[] = new byte[0];
82     protected int storedIndexChars[] = new int[0];
83     
84     protected int storedIndexChunk = 0;
85     protected int storedIndexChunkChar = 0;
86     protected int storedCurrentChar = 0;
87     
88     protected boolean shortStore;
89 //    protected ArabicShaping arabic = new ArabicShaping(ArabicShaping.LETTERS_SHAPE | ArabicShaping.LENGTH_GROW_SHRINK | ArabicShaping.TEXT_DIRECTION_LOGICAL);
90     protected static final IntHashtable mirrorChars = new IntHashtable();
91     protected int arabicOptions;
92     
93     /** Creates new BidiLine */
94     public BidiLine() {
95     }
96     
97     public BidiLine(BidiLine org) {
98         runDirection = org.runDirection;
99         pieceSize = org.pieceSize;
100         text = (char[])org.text.clone();
101         detailChunks = (PdfChunk[])org.detailChunks.clone();
102         totalTextLength = org.totalTextLength;
103
104         orderLevels = (byte[])org.orderLevels.clone();
105         indexChars = (int[])org.indexChars.clone();
106
107         chunks = new ArrayList(org.chunks);
108         indexChunk = org.indexChunk;
109         indexChunkChar = org.indexChunkChar;
110         currentChar = org.currentChar;
111
112         storedRunDirection = org.storedRunDirection;
113         storedText = (char[])org.storedText.clone();
114         storedDetailChunks = (PdfChunk[])org.storedDetailChunks.clone();
115         storedTotalTextLength = org.storedTotalTextLength;
116
117         storedOrderLevels = (byte[])org.storedOrderLevels.clone();
118         storedIndexChars = (int[])org.storedIndexChars.clone();
119
120         storedIndexChunk = org.storedIndexChunk;
121         storedIndexChunkChar = org.storedIndexChunkChar;
122         storedCurrentChar = org.storedCurrentChar;
123
124         shortStore = org.shortStore;
125         arabicOptions = org.arabicOptions;
126     }
127     
128     public boolean isEmpty() {
129         return (currentChar >= totalTextLength && indexChunk >= chunks.size());
130     }
131     
132     public void clearChunks() {
133         chunks.clear();
134         totalTextLength = 0;
135         currentChar = 0;
136     }
137
138     public boolean getParagraph(int runDirection) {
139         this.runDirection = runDirection;
140         currentChar = 0;
141         totalTextLength = 0;
142         boolean hasText = false;
143         char c;
144         char uniC;
145         BaseFont bf;
146         for (; indexChunk < chunks.size(); ++indexChunk) {
147             PdfChunk ck = (PdfChunk)chunks.get(indexChunk);
148             bf = ck.font().getFont();
149             String s = ck.toString();
150             int len = s.length();
151             for (; indexChunkChar < len; ++indexChunkChar) {
152                 c = s.charAt(indexChunkChar);
153                 uniC = (char)bf.getUnicodeEquivalent(c);
154                 if (uniC == '\r' || uniC == '\n') {
155                     // next condition is never true for CID
156                     if (uniC == '\r' && indexChunkChar + 1 < len && s.charAt(indexChunkChar + 1) == '\n')
157                         ++indexChunkChar;
158                     ++indexChunkChar;
159                     if (indexChunkChar >= len) {
160                         indexChunkChar = 0;
161                         ++indexChunk;
162                     }
163                     hasText = true;
164                     if (totalTextLength == 0)
165                         detailChunks[0] = ck;
166                     break;
167                 }
168                 addPiece(c, ck);
169             }
170             if (hasText)
171                 break;
172             indexChunkChar = 0;
173         }
174         if (totalTextLength == 0)
175             return hasText;
176
177         // remove trailing WS
178         totalTextLength = trimRight(0, totalTextLength - 1) + 1;
179         if (totalTextLength == 0) {
180             return true;
181         }
182         
183         if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) {
184             if (orderLevels.length < totalTextLength) {
185                 orderLevels = new byte[pieceSize];
186                 indexChars = new int[pieceSize];
187             }
188             ArabicLigaturizer.processNumbers(text, 0, totalTextLength, arabicOptions);
189             BidiOrder order = new BidiOrder(text, 0, totalTextLength, (byte)(runDirection == PdfWriter.RUN_DIRECTION_RTL ? 1 : 0));
190             byte od[] = order.getLevels();
191             for (int k = 0; k < totalTextLength; ++k) {
192                 orderLevels[k] = od[k];
193                 indexChars[k] = k;
194             }
195             doArabicShapping();
196             mirrorGlyphs();
197         }
198         totalTextLength = trimRightEx(0, totalTextLength - 1) + 1;
199         return true;
200     }
201     
202     public void addChunk(PdfChunk chunk) {
203         chunks.add(chunk);
204     }
205     
206     public void addChunks(ArrayList chunks) {
207         this.chunks.addAll(chunks);
208     }
209     
210     public void addPiece(char c, PdfChunk chunk) {
211         if (totalTextLength >= pieceSize) {
212             char tempText[] = text;
213             PdfChunk tempDetailChunks[] = detailChunks;
214             pieceSize *= 2;
215             text = new char[pieceSize];
216             detailChunks = new PdfChunk[pieceSize];
217             System.arraycopy(tempText, 0, text, 0, totalTextLength);
218             System.arraycopy(tempDetailChunks, 0, detailChunks, 0, totalTextLength);
219         }
220         text[totalTextLength] = c;
221         detailChunks[totalTextLength++] = chunk;
222     }
223     
224     public void save() {
225         if (indexChunk > 0) {
226             if (indexChunk >= chunks.size())
227                 chunks.clear();
228             else {
229                 for (--indexChunk; indexChunk >= 0; --indexChunk)
230                     chunks.remove(indexChunk);
231             }
232             indexChunk = 0;
233         }
234         storedRunDirection = runDirection;
235         storedTotalTextLength = totalTextLength;
236         storedIndexChunk = indexChunk;
237         storedIndexChunkChar = indexChunkChar;
238         storedCurrentChar = currentChar;
239         shortStore = (currentChar < totalTextLength);
240         if (!shortStore) {
241             // long save
242             if (storedText.length < totalTextLength) {
243                 storedText = new char[totalTextLength];
244                 storedDetailChunks = new PdfChunk[totalTextLength];
245             }
246             System.arraycopy(text, 0, storedText, 0, totalTextLength);
247             System.arraycopy(detailChunks, 0, storedDetailChunks, 0, totalTextLength);
248         }
249         if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) {
250             if (storedOrderLevels.length < totalTextLength) {
251                 storedOrderLevels = new byte[totalTextLength];
252                 storedIndexChars = new int[totalTextLength];
253             }
254             System.arraycopy(orderLevels, currentChar, storedOrderLevels, currentChar, totalTextLength - currentChar);
255             System.arraycopy(indexChars, currentChar, storedIndexChars, currentChar, totalTextLength - currentChar);
256         }
257     }
258     
259     public void restore() {
260         runDirection = storedRunDirection;
261         totalTextLength = storedTotalTextLength;
262         indexChunk = storedIndexChunk;
263         indexChunkChar = storedIndexChunkChar;
264         currentChar = storedCurrentChar;
265         if (!shortStore) {
266             // long restore
267             System.arraycopy(storedText, 0, text, 0, totalTextLength);
268             System.arraycopy(storedDetailChunks, 0, detailChunks, 0, totalTextLength);
269         }
270         if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) {
271             System.arraycopy(storedOrderLevels, currentChar, orderLevels, currentChar, totalTextLength - currentChar);
272             System.arraycopy(storedIndexChars, currentChar, indexChars, currentChar, totalTextLength - currentChar);
273         }
274     }
275     
276     public void mirrorGlyphs() {
277         for (int k = 0; k < totalTextLength; ++k) {
278             if ((orderLevels[k] & 1) == 1) {
279                 int mirror = mirrorChars.get(text[k]);
280                 if (mirror != 0)
281                     text[k] = (char)mirror;
282             }
283         }
284     }
285     
286     public void doArabicShapping() {
287         int src = 0;
288         int dest = 0;
289         for (;;) {
290             while (src < totalTextLength) {
291                 char c = text[src];
292                 if (c >= 0x0600 && c <= 0x06ff)
293                     break;
294                 if (src != dest) {
295                     text[dest] = text[src];
296                     detailChunks[dest] = detailChunks[src];
297                     orderLevels[dest] = orderLevels[src];
298                 }
299                 ++src;
300                 ++dest;
301             }
302             if (src >= totalTextLength) {
303                 totalTextLength = dest;
304                 return;
305             }
306             int startArabicIdx = src;
307             ++src;
308             while (src < totalTextLength) {
309                 char c = text[src];
310                 if (c < 0x0600 || c > 0x06ff)
311                     break;
312                 ++src;
313             }
314             int arabicWordSize = src - startArabicIdx;
315             int size = ArabicLigaturizer.arabic_shape(text, startArabicIdx, arabicWordSize, text, dest, arabicWordSize, arabicOptions);
316             if (startArabicIdx != dest) {
317                 for (int k = 0; k < size; ++k) {
318                     detailChunks[dest] = detailChunks[startArabicIdx];
319                     orderLevels[dest++] = orderLevels[startArabicIdx++];
320                 }
321             }
322             else
323                 dest += size;
324         }
325     }
326        
327     public PdfLine processLine(float leftX, float width, int alignment, int runDirection, int arabicOptions) {
328         this.arabicOptions = arabicOptions;
329         save();
330         boolean isRTL = (runDirection == PdfWriter.RUN_DIRECTION_RTL);
331         if (currentChar >= totalTextLength) {
332             boolean hasText = getParagraph(runDirection);
333             if (!hasText)
334                 return null;
335             if (totalTextLength == 0) {
336                 ArrayList ar = new ArrayList();
337                 PdfChunk ck = new PdfChunk("", detailChunks[0]);
338                 ar.add(ck);
339                 return new PdfLine(0, 0, 0, alignment, true, ar, isRTL);
340             }
341         }
342         float originalWidth = width;
343         int lastSplit = -1;
344         if (currentChar != 0)
345             currentChar = trimLeftEx(currentChar, totalTextLength - 1);
346         int oldCurrentChar = currentChar;
347         int uniC = 0;
348         PdfChunk ck = null;
349         float charWidth = 0;
350         PdfChunk lastValidChunk = null;
351         boolean splitChar = false;
352         boolean surrogate = false;
353         for (; currentChar < totalTextLength; ++currentChar) {
354             ck = detailChunks[currentChar];
355             surrogate = Utilities.isSurrogatePair(text, currentChar);
356             if (surrogate)
357                 uniC = ck.getUnicodeEquivalent(Utilities.convertToUtf32(text, currentChar));
358             else
359                 uniC = ck.getUnicodeEquivalent(text[currentChar]);
360             if (PdfChunk.noPrint(uniC))
361                 continue;
362             if (surrogate)
363                 charWidth = ck.getCharWidth(uniC);
364             else
365                 charWidth = ck.getCharWidth(text[currentChar]);
366             splitChar = ck.isExtSplitCharacter(oldCurrentChar, currentChar, totalTextLength, text, detailChunks);
367             if (splitChar && Character.isWhitespace((char)uniC))
368                 lastSplit = currentChar;
369             if (width - charWidth < 0)
370                 break;
371             if (splitChar)
372                 lastSplit = currentChar;
373             width -= charWidth;
374             lastValidChunk = ck;
375             if (ck.isTab()) {
376                 Object[] tab = (Object[])ck.getAttribute(Chunk.TAB);
377                 float tabPosition = ((Float)tab[1]).floatValue();
378                 boolean newLine = ((Boolean)tab[2]).booleanValue();
379                 if (newLine && tabPosition < originalWidth - width) {
380                     return new PdfLine(0, originalWidth, width, alignment, true, createArrayOfPdfChunks(oldCurrentChar, currentChar - 1), isRTL);
381                 }
382                 detailChunks[currentChar].adjustLeft(leftX);
383                 width = originalWidth - tabPosition;
384             }
385             if (surrogate)
386                 ++currentChar;
387         }
388         if (lastValidChunk == null) {
389             // not even a single char fit; must output the first char
390             ++currentChar;
391             if (surrogate)
392                 ++currentChar;
393             return new PdfLine(0, originalWidth, 0, alignment, false, createArrayOfPdfChunks(currentChar - 1, currentChar - 1), isRTL);
394         }
395         if (currentChar >= totalTextLength) {
396             // there was more line than text
397             return new PdfLine(0, originalWidth, width, alignment, true, createArrayOfPdfChunks(oldCurrentChar, totalTextLength - 1), isRTL);
398         }
399         int newCurrentChar = trimRightEx(oldCurrentChar, currentChar - 1);
400         if (newCurrentChar < oldCurrentChar) {
401             // only WS
402             return new PdfLine(0, originalWidth, width, alignment, false, createArrayOfPdfChunks(oldCurrentChar, currentChar - 1), isRTL);
403         }
404         if (newCurrentChar == currentChar - 1) { // middle of word
405             HyphenationEvent he = (HyphenationEvent)lastValidChunk.getAttribute(Chunk.HYPHENATION);
406             if (he != null) {
407                 int word[] = getWord(oldCurrentChar, newCurrentChar);
408                 if (word != null) {
409                     float testWidth = width + getWidth(word[0], currentChar - 1);
410                     String pre = he.getHyphenatedWordPre(new String(text, word[0], word[1] - word[0]), lastValidChunk.font().getFont(), lastValidChunk.font().size(), testWidth);
411                     String post = he.getHyphenatedWordPost();
412                     if (pre.length() > 0) {
413                         PdfChunk extra = new PdfChunk(pre, lastValidChunk);
414                         currentChar = word[1] - post.length();
415                         return new PdfLine(0, originalWidth, testWidth - lastValidChunk.font().width(pre), alignment, false, createArrayOfPdfChunks(oldCurrentChar, word[0] - 1, extra), isRTL);
416                     }
417                 }
418             }
419         }
420         if (lastSplit == -1 || lastSplit >= newCurrentChar) {
421             // no split point or split point ahead of end
422             return new PdfLine(0, originalWidth, width + getWidth(newCurrentChar + 1, currentChar - 1), alignment, false, createArrayOfPdfChunks(oldCurrentChar, newCurrentChar), isRTL);
423         }
424         // standard split
425         currentChar = lastSplit + 1;
426         newCurrentChar = trimRightEx(oldCurrentChar, lastSplit);
427         if (newCurrentChar < oldCurrentChar) {
428             // only WS again
429             newCurrentChar = currentChar - 1;
430         }
431         return new PdfLine(0, originalWidth, originalWidth - getWidth(oldCurrentChar, newCurrentChar), alignment, false, createArrayOfPdfChunks(oldCurrentChar, newCurrentChar), isRTL);
432     }
433     
434     /** Gets the width of a range of characters.
435      * @param startIdx the first index to calculate
436      * @param lastIdx the last inclusive index to calculate
437      * @return the sum of all widths
438      */
    
439     public float getWidth(int startIdx, int lastIdx) {
440         char c = 0;
441         char uniC;
442         PdfChunk ck = null;
443         float width = 0;
444         for (; startIdx <= lastIdx; ++startIdx) {
445             boolean surrogate = Utilities.isSurrogatePair(text, startIdx);
446             if (surrogate) {
447                 width += detailChunks[startIdx].getCharWidth(Utilities.convertToUtf32(text, startIdx));
448                 ++startIdx;
449             }
450             else {
451                 c = text[startIdx];
452                 ck = detailChunks[startIdx];
453                 if (PdfChunk.noPrint(ck.getUnicodeEquivalent(c)))
454                     continue;
455                 width += detailChunks[startIdx].getCharWidth(c);
456             }
457         }
458         return width;
459     }
460     
461     public ArrayList createArrayOfPdfChunks(int startIdx, int endIdx) {
462         return createArrayOfPdfChunks(startIdx, endIdx, null);
463     }
464     
465     public ArrayList createArrayOfPdfChunks(int startIdx, int endIdx, PdfChunk extraPdfChunk) {
466         boolean bidi = (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL);
467         if (bidi)
468             reorder(startIdx, endIdx);
469         ArrayList ar = new ArrayList();
470         PdfChunk refCk = detailChunks[startIdx];
471         PdfChunk ck = null;
472         StringBuffer buf = new StringBuffer();
473         char c;
474         int idx = 0;
475         for (; startIdx <= endIdx; ++startIdx) {
476             idx = bidi ? indexChars[startIdx] : startIdx;
477             c = text[idx];
478             ck = detailChunks[idx];
479             if (PdfChunk.noPrint(ck.getUnicodeEquivalent(c)))
480                 continue;
481             if (ck.isImage() || ck.isSeparator() || ck.isTab()) {
482                 if (buf.length() > 0) {
483                     ar.add(new PdfChunk(buf.toString(), refCk));
484                     buf = new StringBuffer();
485                 }
486                 ar.add(ck);
487             }
488             else if (ck == refCk) {
489                 buf.append(c);
490             }
491             else {
492                 if (buf.length() > 0) {
493                     ar.add(new PdfChunk(buf.toString(), refCk));
494                     buf = new StringBuffer();
495                 }
496                 if (!ck.isImage() && !ck.isSeparator() && !ck.isTab())
497                     buf.append(c);
498                 refCk = ck;
499             }
500         }
501         if (buf.length() > 0) {
502             ar.add(new PdfChunk(buf.toString(), refCk));
503         }
504         if (extraPdfChunk != null)
505             ar.add(extraPdfChunk);
506         return ar;
507     }
508     
509     public int[] getWord(int startIdx, int idx) {
510         int last = idx;
511         int first = idx;
512         // forward
513         for (; last < totalTextLength; ++last) {
514             if (!Character.isLetter(text[last]))
515                 break;            
516         }
517         if (last == idx)
518             return null;
519         // backward
520         for (; first >= startIdx; --first) {
521             if (!Character.isLetter(text[first]))
522                 break;            
523         }
524         ++first;
525         return new int[]{first, last};
526     }
527     
528     public int trimRight(int startIdx, int endIdx) {
529         int idx = endIdx;
530         char c;
531         for (; idx >= startIdx; --idx) {
532             c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]);
533             if (!isWS(c))
534                 break;
535         }
536         return idx;
537     }
538     
539     public int trimLeft(int startIdx, int endIdx) {
540         int idx = startIdx;
541         char c;
542         for (; idx <= endIdx; ++idx) {
543             c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]);
544             if (!isWS(c))
545                 break;
546         }
547         return idx;
548     }
549     
550     public int trimRightEx(int startIdx, int endIdx) {
551         int idx = endIdx;
552         char c = 0;
553         for (; idx >= startIdx; --idx) {
554             c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]);
555             if (!isWS(c) && !PdfChunk.noPrint(c))
556                 break;
557         }
558         return idx;
559     }
560     
561     public int trimLeftEx(int startIdx, int endIdx) {
562         int idx = startIdx;
563         char c = 0;
564         for (; idx <= endIdx; ++idx) {
565             c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]);
566             if (!isWS(c) && !PdfChunk.noPrint(c))
567                 break;
568         }
569         return idx;
570     }
571     
572     public void reorder(int start, int end) {
573         byte maxLevel = orderLevels[start];
574         byte minLevel = maxLevel;
575         byte onlyOddLevels = maxLevel;
576         byte onlyEvenLevels = maxLevel;
577         for (int k = start + 1; k <= end; ++k) {
578             byte b = orderLevels[k];
579             if (b > maxLevel)
580                 maxLevel = b;
581             else if (b < minLevel)
582                 minLevel = b;
583             onlyOddLevels &= b;
584             onlyEvenLevels |= b;
585         }
586         if ((onlyEvenLevels & 1) == 0) // nothing to do
587             return;
588         if ((onlyOddLevels & 1) == 1) { // single inversion
589             flip(start, end + 1);
590             return;
591         }
592         minLevel |= 1;
593         for (; maxLevel >= minLevel; --maxLevel) {
594             int pstart = start;
595             for (;;) {
596                 for (;pstart <= end; ++pstart) {
597                     if (orderLevels[pstart] >= maxLevel)
598                         break;
599                 }
600                 if (pstart > end)
601                     break;
602                 int pend = pstart + 1;
603                 for (; pend <= end; ++pend) {
604                     if (orderLevels[pend] < maxLevel)
605                         break;
606                 }
607                 flip(pstart, pend);
608                 pstart = pend + 1;
609             }
610         }
611     }
612     
613     public void flip(int start, int end) {
614         int mid = (start + end) / 2;
615         --end;
616         for (; start < mid; ++start, --end) {
617             int temp = indexChars[start];
618             indexChars[start] = indexChars[end];
619             indexChars[end] = temp;
620         }
621     }
622     
623     public static boolean isWS(char c) {
624         return (c <= ' ');
625     }
626
627     static {
628         mirrorChars.put(0x0028, 0x0029); // LEFT PARENTHESIS
629         mirrorChars.put(0x0029, 0x0028); // RIGHT PARENTHESIS
630         mirrorChars.put(0x003C, 0x003E); // LESS-THAN SIGN
631         mirrorChars.put(0x003E, 0x003C); // GREATER-THAN SIGN
632         mirrorChars.put(0x005B, 0x005D); // LEFT SQUARE BRACKET
633         mirrorChars.put(0x005D, 0x005B); // RIGHT SQUARE BRACKET
634         mirrorChars.put(0x007B, 0x007D); // LEFT CURLY BRACKET
635         mirrorChars.put(0x007D, 0x007B); // RIGHT CURLY BRACKET
636         mirrorChars.put(0x00AB, 0x00BB); // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
637         mirrorChars.put(0x00BB, 0x00AB); // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
638         mirrorChars.put(0x2039, 0x203A); // SINGLE LEFT-POINTING ANGLE QUOTATION MARK
639         mirrorChars.put(0x203A, 0x2039); // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
640         mirrorChars.put(0x2045, 0x2046); // LEFT SQUARE BRACKET WITH QUILL
641         mirrorChars.put(0x2046, 0x2045); // RIGHT SQUARE BRACKET WITH QUILL
642         mirrorChars.put(0x207D, 0x207E); // SUPERSCRIPT LEFT PARENTHESIS
643         mirrorChars.put(0x207E, 0x207D); // SUPERSCRIPT RIGHT PARENTHESIS
644         mirrorChars.put(0x208D, 0x208E); // SUBSCRIPT LEFT PARENTHESIS
645         mirrorChars.put(0x208E, 0x208D); // SUBSCRIPT RIGHT PARENTHESIS
646         mirrorChars.put(0x2208, 0x220B); // ELEMENT OF
647         mirrorChars.put(0x2209, 0x220C); // NOT AN ELEMENT OF
648         mirrorChars.put(0x220A, 0x220D); // SMALL ELEMENT OF
649         mirrorChars.put(0x220B, 0x2208); // CONTAINS AS MEMBER
650         mirrorChars.put(0x220C, 0x2209); // DOES NOT CONTAIN AS MEMBER
651         mirrorChars.put(0x220D, 0x220A); // SMALL CONTAINS AS MEMBER
652         mirrorChars.put(0x2215, 0x29F5); // DIVISION SLASH
653         mirrorChars.put(0x223C, 0x223D); // TILDE OPERATOR
654         mirrorChars.put(0x223D, 0x223C); // REVERSED TILDE
655         mirrorChars.put(0x2243, 0x22CD); // ASYMPTOTICALLY EQUAL TO
656         mirrorChars.put(0x2252, 0x2253); // APPROXIMATELY EQUAL TO OR THE IMAGE OF
657         mirrorChars.put(0x2253, 0x2252); // IMAGE OF OR APPROXIMATELY EQUAL TO
658         mirrorChars.put(0x2254, 0x2255); // COLON EQUALS
659         mirrorChars.put(0x2255, 0x2254); // EQUALS COLON
660         mirrorChars.put(0x2264, 0x2265); // LESS-THAN OR EQUAL TO
661         mirrorChars.put(0x2265, 0x2264); // GREATER-THAN OR EQUAL TO
662         mirrorChars.put(0x2266, 0x2267); // LESS-THAN OVER EQUAL TO
663         mirrorChars.put(0x2267, 0x2266); // GREATER-THAN OVER EQUAL TO
664         mirrorChars.put(0x2268, 0x2269); // [BEST FIT] LESS-THAN BUT NOT EQUAL TO
665         mirrorChars.put(0x2269, 0x2268); // [BEST FIT] GREATER-THAN BUT NOT EQUAL TO
666         mirrorChars.put(0x226A, 0x226B); // MUCH LESS-THAN
667         mirrorChars.put(0x226B, 0x226A); // MUCH GREATER-THAN
668         mirrorChars.put(0x226E, 0x226F); // [BEST FIT] NOT LESS-THAN
669         mirrorChars.put(0x226F, 0x226E); // [BEST FIT] NOT GREATER-THAN
670         mirrorChars.put(0x2270, 0x2271); // [BEST FIT] NEITHER LESS-THAN NOR EQUAL TO
671         mirrorChars.put(0x2271, 0x2270); // [BEST FIT] NEITHER GREATER-THAN NOR EQUAL TO
672         mirrorChars.put(0x2272, 0x2273); // [BEST FIT] LESS-THAN OR EQUIVALENT TO
673         mirrorChars.put(0x2273, 0x2272); // [BEST FIT] GREATER-THAN OR EQUIVALENT TO
674         mirrorChars.put(0x2274, 0x2275); // [BEST FIT] NEITHER LESS-THAN NOR EQUIVALENT TO
675         mirrorChars.put(0x2275, 0x2274); // [BEST FIT] NEITHER GREATER-THAN NOR EQUIVALENT TO
676         mirrorChars.put(0x2276, 0x2277); // LESS-THAN OR GREATER-THAN
677         mirrorChars.put(0x2277, 0x2276); // GREATER-THAN OR LESS-THAN
678         mirrorChars.put(0x2278, 0x2279); // NEITHER LESS-THAN NOR GREATER-THAN
679         mirrorChars.put(0x2279, 0x2278); // NEITHER GREATER-THAN NOR LESS-THAN
680         mirrorChars.put(0x227A, 0x227B); // PRECEDES
681         mirrorChars.put(0x227B, 0x227A); // SUCCEEDS
682         mirrorChars.put(0x227C, 0x227D); // PRECEDES OR EQUAL TO
683         mirrorChars.put(0x227D, 0x227C); // SUCCEEDS OR EQUAL TO
684         mirrorChars.put(0x227E, 0x227F); // [BEST FIT] PRECEDES OR EQUIVALENT TO
685         mirrorChars.put(0x227F, 0x227E); // [BEST FIT] SUCCEEDS OR EQUIVALENT TO
686         mirrorChars.put(0x2280, 0x2281); // [BEST FIT] DOES NOT PRECEDE
687         mirrorChars.put(0x2281, 0x2280); // [BEST FIT] DOES NOT SUCCEED
688         mirrorChars.put(0x2282, 0x2283); // SUBSET OF
689         mirrorChars.put(0x2283, 0x2282); // SUPERSET OF
690         mirrorChars.put(0x2284, 0x2285); // [BEST FIT] NOT A SUBSET OF
691         mirrorChars.put(0x2285, 0x2284); // [BEST FIT] NOT A SUPERSET OF
692         mirrorChars.put(0x2286, 0x2287); // SUBSET OF OR EQUAL TO
693         mirrorChars.put(0x2287, 0x2286); // SUPERSET OF OR EQUAL TO
694         mirrorChars.put(0x2288, 0x2289); // [BEST FIT] NEITHER A SUBSET OF NOR EQUAL TO
695         mirrorChars.put(0x2289, 0x2288); // [BEST FIT] NEITHER A SUPERSET OF NOR EQUAL TO
696         mirrorChars.put(0x228A, 0x228B); // [BEST FIT] SUBSET OF WITH NOT EQUAL TO
697         mirrorChars.put(0x228B, 0x228A); // [BEST FIT] SUPERSET OF WITH NOT EQUAL TO
698         mirrorChars.put(0x228F, 0x2290); // SQUARE IMAGE OF
699         mirrorChars.put(0x2290, 0x228F); // SQUARE ORIGINAL OF
700         mirrorChars.put(0x2291, 0x2292); // SQUARE IMAGE OF OR EQUAL TO
701         mirrorChars.put(0x2292, 0x2291); // SQUARE ORIGINAL OF OR EQUAL TO
702         mirrorChars.put(0x2298, 0x29B8); // CIRCLED DIVISION SLASH
703         mirrorChars.put(0x22A2, 0x22A3); // RIGHT TACK
704         mirrorChars.put(0x22A3, 0x22A2); // LEFT TACK
705         mirrorChars.put(0x22A6, 0x2ADE); // ASSERTION
706         mirrorChars.put(0x22A8, 0x2AE4); // TRUE
707         mirrorChars.put(0x22A9, 0x2AE3); // FORCES
708         mirrorChars.put(0x22AB, 0x2AE5); // DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE
709         mirrorChars.put(0x22B0, 0x22B1); // PRECEDES UNDER RELATION
710         mirrorChars.put(0x22B1, 0x22B0); // SUCCEEDS UNDER RELATION
711         mirrorChars.put(0x22B2, 0x22B3); // NORMAL SUBGROUP OF
712         mirrorChars.put(0x22B3, 0x22B2); // CONTAINS AS NORMAL SUBGROUP
713         mirrorChars.put(0x22B4, 0x22B5); // NORMAL SUBGROUP OF OR EQUAL TO
714         mirrorChars.put(0x22B5, 0x22B4); // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO
715         mirrorChars.put(0x22B6, 0x22B7); // ORIGINAL OF
716         mirrorChars.put(0x22B7, 0x22B6); // IMAGE OF
717         mirrorChars.put(0x22C9, 0x22CA); // LEFT NORMAL FACTOR SEMIDIRECT PRODUCT
718         mirrorChars.put(0x22CA, 0x22C9); // RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT
719         mirrorChars.put(0x22CB, 0x22CC); // LEFT SEMIDIRECT PRODUCT
720         mirrorChars.put(0x22CC, 0x22CB); // RIGHT SEMIDIRECT PRODUCT
721         mirrorChars.put(0x22CD, 0x2243); // REVERSED TILDE EQUALS
722         mirrorChars.put(0x22D0, 0x22D1); // DOUBLE SUBSET
723         mirrorChars.put(0x22D1, 0x22D0); // DOUBLE SUPERSET
724         mirrorChars.put(0x22D6, 0x22D7); // LESS-THAN WITH DOT
725         mirrorChars.put(0x22D7, 0x22D6); // GREATER-THAN WITH DOT
726         mirrorChars.put(0x22D8, 0x22D9); // VERY MUCH LESS-THAN
727         mirrorChars.put(0x22D9, 0x22D8); // VERY MUCH GREATER-THAN
728         mirrorChars.put(0x22DA, 0x22DB); // LESS-THAN EQUAL TO OR GREATER-THAN
729         mirrorChars.put(0x22DB, 0x22DA); // GREATER-THAN EQUAL TO OR LESS-THAN
730         mirrorChars.put(0x22DC, 0x22DD); // EQUAL TO OR LESS-THAN
731         mirrorChars.put(0x22DD, 0x22DC); // EQUAL TO OR GREATER-THAN
732         mirrorChars.put(0x22DE, 0x22DF); // EQUAL TO OR PRECEDES
733         mirrorChars.put(0x22DF, 0x22DE); // EQUAL TO OR SUCCEEDS
734         mirrorChars.put(0x22E0, 0x22E1); // [BEST FIT] DOES NOT PRECEDE OR EQUAL
735         mirrorChars.put(0x22E1, 0x22E0); // [BEST FIT] DOES NOT SUCCEED OR EQUAL
736         mirrorChars.put(0x22E2, 0x22E3); // [BEST FIT] NOT SQUARE IMAGE OF OR EQUAL TO
737         mirrorChars.put(0x22E3, 0x22E2); // [BEST FIT] NOT SQUARE ORIGINAL OF OR EQUAL TO
738         mirrorChars.put(0x22E4, 0x22E5); // [BEST FIT] SQUARE IMAGE OF OR NOT EQUAL TO
739         mirrorChars.put(0x22E5, 0x22E4); // [BEST FIT] SQUARE ORIGINAL OF OR NOT EQUAL TO
740         mirrorChars.put(0x22E6, 0x22E7); // [BEST FIT] LESS-THAN BUT NOT EQUIVALENT TO
741         mirrorChars.put(0x22E7, 0x22E6); // [BEST FIT] GREATER-THAN BUT NOT EQUIVALENT TO
742         mirrorChars.put(0x22E8, 0x22E9); // [BEST FIT] PRECEDES BUT NOT EQUIVALENT TO
743         mirrorChars.put(0x22E9, 0x22E8); // [BEST FIT] SUCCEEDS BUT NOT EQUIVALENT TO
744         mirrorChars.put(0x22EA, 0x22EB); // [BEST FIT] NOT NORMAL SUBGROUP OF
745         mirrorChars.put(0x22EB, 0x22EA); // [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP
746         mirrorChars.put(0x22EC, 0x22ED); // [BEST FIT] NOT NORMAL SUBGROUP OF OR EQUAL TO
747         mirrorChars.put(0x22ED, 0x22EC); // [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL
748         mirrorChars.put(0x22F0, 0x22F1); // UP RIGHT DIAGONAL ELLIPSIS
749         mirrorChars.put(0x22F1, 0x22F0); // DOWN RIGHT DIAGONAL ELLIPSIS
750         mirrorChars.put(0x22F2, 0x22FA); // ELEMENT OF WITH LONG HORIZONTAL STROKE
751         mirrorChars.put(0x22F3, 0x22FB); // ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
752         mirrorChars.put(0x22F4, 0x22FC); // SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
753         mirrorChars.put(0x22F6, 0x22FD); // ELEMENT OF WITH OVERBAR
754         mirrorChars.put(0x22F7, 0x22FE); // SMALL ELEMENT OF WITH OVERBAR
755         mirrorChars.put(0x22FA, 0x22F2); // CONTAINS WITH LONG HORIZONTAL STROKE
756         mirrorChars.put(0x22FB, 0x22F3); // CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
757         mirrorChars.put(0x22FC, 0x22F4); // SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
758         mirrorChars.put(0x22FD, 0x22F6); // CONTAINS WITH OVERBAR
759         mirrorChars.put(0x22FE, 0x22F7); // SMALL CONTAINS WITH OVERBAR
760         mirrorChars.put(0x2308, 0x2309); // LEFT CEILING
761         mirrorChars.put(0x2309, 0x2308); // RIGHT CEILING
762         mirrorChars.put(0x230A, 0x230B); // LEFT FLOOR
763         mirrorChars.put(0x230B, 0x230A); // RIGHT FLOOR
764         mirrorChars.put(0x2329, 0x232A); // LEFT-POINTING ANGLE BRACKET
765         mirrorChars.put(0x232A, 0x2329); // RIGHT-POINTING ANGLE BRACKET
766         mirrorChars.put(0x2768, 0x2769); // MEDIUM LEFT PARENTHESIS ORNAMENT
767         mirrorChars.put(0x2769, 0x2768); // MEDIUM RIGHT PARENTHESIS ORNAMENT
768         mirrorChars.put(0x276A, 0x276B); // MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT
769         mirrorChars.put(0x276B, 0x276A); // MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT
770         mirrorChars.put(0x276C, 0x276D); // MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT
771         mirrorChars.put(0x276D, 0x276C); // MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT
772         mirrorChars.put(0x276E, 0x276F); // HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT
773         mirrorChars.put(0x276F, 0x276E); // HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT
774         mirrorChars.put(0x2770, 0x2771); // HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT
775         mirrorChars.put(0x2771, 0x2770); // HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT
776         mirrorChars.put(0x2772, 0x2773); // LIGHT LEFT TORTOISE SHELL BRACKET
777         mirrorChars.put(0x2773, 0x2772); // LIGHT RIGHT TORTOISE SHELL BRACKET
778         mirrorChars.put(0x2774, 0x2775); // MEDIUM LEFT CURLY BRACKET ORNAMENT
779         mirrorChars.put(0x2775, 0x2774); // MEDIUM RIGHT CURLY BRACKET ORNAMENT
780         mirrorChars.put(0x27D5, 0x27D6); // LEFT OUTER JOIN
781         mirrorChars.put(0x27D6, 0x27D5); // RIGHT OUTER JOIN
782         mirrorChars.put(0x27DD, 0x27DE); // LONG RIGHT TACK
783         mirrorChars.put(0x27DE, 0x27DD); // LONG LEFT TACK
784         mirrorChars.put(0x27E2, 0x27E3); // WHITE CONCAVE-SIDED DIAMOND WITH LEFTWARDS TICK
785         mirrorChars.put(0x27E3, 0x27E2); // WHITE CONCAVE-SIDED DIAMOND WITH RIGHTWARDS TICK
786         mirrorChars.put(0x27E4, 0x27E5); // WHITE SQUARE WITH LEFTWARDS TICK
787         mirrorChars.put(0x27E5, 0x27E4); // WHITE SQUARE WITH RIGHTWARDS TICK
788         mirrorChars.put(0x27E6, 0x27E7); // MATHEMATICAL LEFT WHITE SQUARE BRACKET
789         mirrorChars.put(0x27E7, 0x27E6); // MATHEMATICAL RIGHT WHITE SQUARE BRACKET
790         mirrorChars.put(0x27E8, 0x27E9); // MATHEMATICAL LEFT ANGLE BRACKET
791         mirrorChars.put(0x27E9, 0x27E8); // MATHEMATICAL RIGHT ANGLE BRACKET
792         mirrorChars.put(0x27EA, 0x27EB); // MATHEMATICAL LEFT DOUBLE ANGLE BRACKET
793         mirrorChars.put(0x27EB, 0x27EA); // MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET
794         mirrorChars.put(0x2983, 0x2984); // LEFT WHITE CURLY BRACKET
795         mirrorChars.put(0x2984, 0x2983); // RIGHT WHITE CURLY BRACKET
796         mirrorChars.put(0x2985, 0x2986); // LEFT WHITE PARENTHESIS
797         mirrorChars.put(0x2986, 0x2985); // RIGHT WHITE PARENTHESIS
798         mirrorChars.put(0x2987, 0x2988); // Z NOTATION LEFT IMAGE BRACKET
799         mirrorChars.put(0x2988, 0x2987); // Z NOTATION RIGHT IMAGE BRACKET
800         mirrorChars.put(0x2989, 0x298A); // Z NOTATION LEFT BINDING BRACKET
801         mirrorChars.put(0x298A, 0x2989); // Z NOTATION RIGHT BINDING BRACKET
802         mirrorChars.put(0x298B, 0x298C); // LEFT SQUARE BRACKET WITH UNDERBAR
803         mirrorChars.put(0x298C, 0x298B); // RIGHT SQUARE BRACKET WITH UNDERBAR
804         mirrorChars.put(0x298D, 0x2990); // LEFT SQUARE BRACKET WITH TICK IN TOP CORNER
805         mirrorChars.put(0x298E, 0x298F); // RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
806         mirrorChars.put(0x298F, 0x298E); // LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
807         mirrorChars.put(0x2990, 0x298D); // RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER
808         mirrorChars.put(0x2991, 0x2992); // LEFT ANGLE BRACKET WITH DOT
809         mirrorChars.put(0x2992, 0x2991); // RIGHT ANGLE BRACKET WITH DOT
810         mirrorChars.put(0x2993, 0x2994); // LEFT ARC LESS-THAN BRACKET
811         mirrorChars.put(0x2994, 0x2993); // RIGHT ARC GREATER-THAN BRACKET
812         mirrorChars.put(0x2995, 0x2996); // DOUBLE LEFT ARC GREATER-THAN BRACKET
813         mirrorChars.put(0x2996, 0x2995); // DOUBLE RIGHT ARC LESS-THAN BRACKET
814         mirrorChars.put(0x2997, 0x2998); // LEFT BLACK TORTOISE SHELL BRACKET
815         mirrorChars.put(0x2998, 0x2997); // RIGHT BLACK TORTOISE SHELL BRACKET
816         mirrorChars.put(0x29B8, 0x2298); // CIRCLED REVERSE SOLIDUS
817         mirrorChars.put(0x29C0, 0x29C1); // CIRCLED LESS-THAN
818         mirrorChars.put(0x29C1, 0x29C0); // CIRCLED GREATER-THAN
819         mirrorChars.put(0x29C4, 0x29C5); // SQUARED RISING DIAGONAL SLASH
820         mirrorChars.put(0x29C5, 0x29C4); // SQUARED FALLING DIAGONAL SLASH
821         mirrorChars.put(0x29CF, 0x29D0); // LEFT TRIANGLE BESIDE VERTICAL BAR
822         mirrorChars.put(0x29D0, 0x29CF); // VERTICAL BAR BESIDE RIGHT TRIANGLE
823         mirrorChars.put(0x29D1, 0x29D2); // BOWTIE WITH LEFT HALF BLACK
824         mirrorChars.put(0x29D2, 0x29D1); // BOWTIE WITH RIGHT HALF BLACK
825         mirrorChars.put(0x29D4, 0x29D5); // TIMES WITH LEFT HALF BLACK
826         mirrorChars.put(0x29D5, 0x29D4); // TIMES WITH RIGHT HALF BLACK
827         mirrorChars.put(0x29D8, 0x29D9); // LEFT WIGGLY FENCE
828         mirrorChars.put(0x29D9, 0x29D8); // RIGHT WIGGLY FENCE
829         mirrorChars.put(0x29DA, 0x29DB); // LEFT DOUBLE WIGGLY FENCE
830         mirrorChars.put(0x29DB, 0x29DA); // RIGHT DOUBLE WIGGLY FENCE
831         mirrorChars.put(0x29F5, 0x2215); // REVERSE SOLIDUS OPERATOR
832         mirrorChars.put(0x29F8, 0x29F9); // BIG SOLIDUS
833         mirrorChars.put(0x29F9, 0x29F8); // BIG REVERSE SOLIDUS
834         mirrorChars.put(0x29FC, 0x29FD); // LEFT-POINTING CURVED ANGLE BRACKET
835         mirrorChars.put(0x29FD, 0x29FC); // RIGHT-POINTING CURVED ANGLE BRACKET
836         mirrorChars.put(0x2A2B, 0x2A2C); // MINUS SIGN WITH FALLING DOTS
837         mirrorChars.put(0x2A2C, 0x2A2B); // MINUS SIGN WITH RISING DOTS
838         mirrorChars.put(0x2A2D, 0x2A2C); // PLUS SIGN IN LEFT HALF CIRCLE
839         mirrorChars.put(0x2A2E, 0x2A2D); // PLUS SIGN IN RIGHT HALF CIRCLE
840         mirrorChars.put(0x2A34, 0x2A35); // MULTIPLICATION SIGN IN LEFT HALF CIRCLE
841         mirrorChars.put(0x2A35, 0x2A34); // MULTIPLICATION SIGN IN RIGHT HALF CIRCLE
842         mirrorChars.put(0x2A3C, 0x2A3D); // INTERIOR PRODUCT
843         mirrorChars.put(0x2A3D, 0x2A3C); // RIGHTHAND INTERIOR PRODUCT
844         mirrorChars.put(0x2A64, 0x2A65); // Z NOTATION DOMAIN ANTIRESTRICTION
845         mirrorChars.put(0x2A65, 0x2A64); // Z NOTATION RANGE ANTIRESTRICTION
846         mirrorChars.put(0x2A79, 0x2A7A); // LESS-THAN WITH CIRCLE INSIDE
847         mirrorChars.put(0x2A7A, 0x2A79); // GREATER-THAN WITH CIRCLE INSIDE
848         mirrorChars.put(0x2A7D, 0x2A7E); // LESS-THAN OR SLANTED EQUAL TO
849         mirrorChars.put(0x2A7E, 0x2A7D); // GREATER-THAN OR SLANTED EQUAL TO
850         mirrorChars.put(0x2A7F, 0x2A80); // LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE
851         mirrorChars.put(0x2A80, 0x2A7F); // GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE
852         mirrorChars.put(0x2A81, 0x2A82); // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE
853         mirrorChars.put(0x2A82, 0x2A81); // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE
854         mirrorChars.put(0x2A83, 0x2A84); // LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT
855         mirrorChars.put(0x2A84, 0x2A83); // GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT
856         mirrorChars.put(0x2A8B, 0x2A8C); // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN
857         mirrorChars.put(0x2A8C, 0x2A8B); // GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN
858         mirrorChars.put(0x2A91, 0x2A92); // LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL
859         mirrorChars.put(0x2A92, 0x2A91); // GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL
860         mirrorChars.put(0x2A93, 0x2A94); // LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL
861         mirrorChars.put(0x2A94, 0x2A93); // GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL
862         mirrorChars.put(0x2A95, 0x2A96); // SLANTED EQUAL TO OR LESS-THAN
863         mirrorChars.put(0x2A96, 0x2A95); // SLANTED EQUAL TO OR GREATER-THAN
864         mirrorChars.put(0x2A97, 0x2A98); // SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE
865         mirrorChars.put(0x2A98, 0x2A97); // SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE
866         mirrorChars.put(0x2A99, 0x2A9A); // DOUBLE-LINE EQUAL TO OR LESS-THAN
867         mirrorChars.put(0x2A9A, 0x2A99); // DOUBLE-LINE EQUAL TO OR GREATER-THAN
868         mirrorChars.put(0x2A9B, 0x2A9C); // DOUBLE-LINE SLANTED EQUAL TO OR LESS-THAN
869         mirrorChars.put(0x2A9C, 0x2A9B); // DOUBLE-LINE SLANTED EQUAL TO OR GREATER-THAN
870         mirrorChars.put(0x2AA1, 0x2AA2); // DOUBLE NESTED LESS-THAN
871         mirrorChars.put(0x2AA2, 0x2AA1); // DOUBLE NESTED GREATER-THAN
872         mirrorChars.put(0x2AA6, 0x2AA7); // LESS-THAN CLOSED BY CURVE
873         mirrorChars.put(0x2AA7, 0x2AA6); // GREATER-THAN CLOSED BY CURVE
874         mirrorChars.put(0x2AA8, 0x2AA9); // LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL
875         mirrorChars.put(0x2AA9, 0x2AA8); // GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL
876         mirrorChars.put(0x2AAA, 0x2AAB); // SMALLER THAN
877         mirrorChars.put(0x2AAB, 0x2AAA); // LARGER THAN
878         mirrorChars.put(0x2AAC, 0x2AAD); // SMALLER THAN OR EQUAL TO
879         mirrorChars.put(0x2AAD, 0x2AAC); // LARGER THAN OR EQUAL TO
880         mirrorChars.put(0x2AAF, 0x2AB0); // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN
881         mirrorChars.put(0x2AB0, 0x2AAF); // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN
882         mirrorChars.put(0x2AB3, 0x2AB4); // PRECEDES ABOVE EQUALS SIGN
883         mirrorChars.put(0x2AB4, 0x2AB3); // SUCCEEDS ABOVE EQUALS SIGN
884         mirrorChars.put(0x2ABB, 0x2ABC); // DOUBLE PRECEDES
885         mirrorChars.put(0x2ABC, 0x2ABB); // DOUBLE SUCCEEDS
886         mirrorChars.put(0x2ABD, 0x2ABE); // SUBSET WITH DOT
887         mirrorChars.put(0x2ABE, 0x2ABD); // SUPERSET WITH DOT
888         mirrorChars.put(0x2ABF, 0x2AC0); // SUBSET WITH PLUS SIGN BELOW
889         mirrorChars.put(0x2AC0, 0x2ABF); // SUPERSET WITH PLUS SIGN BELOW
890         mirrorChars.put(0x2AC1, 0x2AC2); // SUBSET WITH MULTIPLICATION SIGN BELOW
891         mirrorChars.put(0x2AC2, 0x2AC1); // SUPERSET WITH MULTIPLICATION SIGN BELOW
892         mirrorChars.put(0x2AC3, 0x2AC4); // SUBSET OF OR EQUAL TO WITH DOT ABOVE
893         mirrorChars.put(0x2AC4, 0x2AC3); // SUPERSET OF OR EQUAL TO WITH DOT ABOVE
894         mirrorChars.put(0x2AC5, 0x2AC6); // SUBSET OF ABOVE EQUALS SIGN
895         mirrorChars.put(0x2AC6, 0x2AC5); // SUPERSET OF ABOVE EQUALS SIGN
896         mirrorChars.put(0x2ACD, 0x2ACE); // SQUARE LEFT OPEN BOX OPERATOR
897         mirrorChars.put(0x2ACE, 0x2ACD); // SQUARE RIGHT OPEN BOX OPERATOR
898         mirrorChars.put(0x2ACF, 0x2AD0); // CLOSED SUBSET
899         mirrorChars.put(0x2AD0, 0x2ACF); // CLOSED SUPERSET
900         mirrorChars.put(0x2AD1, 0x2AD2); // CLOSED SUBSET OR EQUAL TO
901         mirrorChars.put(0x2AD2, 0x2AD1); // CLOSED SUPERSET OR EQUAL TO
902         mirrorChars.put(0x2AD3, 0x2AD4); // SUBSET ABOVE SUPERSET
903         mirrorChars.put(0x2AD4, 0x2AD3); // SUPERSET ABOVE SUBSET
904         mirrorChars.put(0x2AD5, 0x2AD6); // SUBSET ABOVE SUBSET
905         mirrorChars.put(0x2AD6, 0x2AD5); // SUPERSET ABOVE SUPERSET
906         mirrorChars.put(0x2ADE, 0x22A6); // SHORT LEFT TACK
907         mirrorChars.put(0x2AE3, 0x22A9); // DOUBLE VERTICAL BAR LEFT TURNSTILE
908         mirrorChars.put(0x2AE4, 0x22A8); // VERTICAL BAR DOUBLE LEFT TURNSTILE
909         mirrorChars.put(0x2AE5, 0x22AB); // DOUBLE VERTICAL BAR DOUBLE LEFT TURNSTILE
910         mirrorChars.put(0x2AEC, 0x2AED); // DOUBLE STROKE NOT SIGN
911         mirrorChars.put(0x2AED, 0x2AEC); // REVERSED DOUBLE STROKE NOT SIGN
912         mirrorChars.put(0x2AF7, 0x2AF8); // TRIPLE NESTED LESS-THAN
913         mirrorChars.put(0x2AF8, 0x2AF7); // TRIPLE NESTED GREATER-THAN
914         mirrorChars.put(0x2AF9, 0x2AFA); // DOUBLE-LINE SLANTED LESS-THAN OR EQUAL TO
915         mirrorChars.put(0x2AFA, 0x2AF9); // DOUBLE-LINE SLANTED GREATER-THAN OR EQUAL TO
916         mirrorChars.put(0x3008, 0x3009); // LEFT ANGLE BRACKET
917         mirrorChars.put(0x3009, 0x3008); // RIGHT ANGLE BRACKET
918         mirrorChars.put(0x300A, 0x300B); // LEFT DOUBLE ANGLE BRACKET
919         mirrorChars.put(0x300B, 0x300A); // RIGHT DOUBLE ANGLE BRACKET
920         mirrorChars.put(0x300C, 0x300D); // [BEST FIT] LEFT CORNER BRACKET
921         mirrorChars.put(0x300D, 0x300C); // [BEST FIT] RIGHT CORNER BRACKET
922         mirrorChars.put(0x300E, 0x300F); // [BEST FIT] LEFT WHITE CORNER BRACKET
923         mirrorChars.put(0x300F, 0x300E); // [BEST FIT] RIGHT WHITE CORNER BRACKET
924         mirrorChars.put(0x3010, 0x3011); // LEFT BLACK LENTICULAR BRACKET
925         mirrorChars.put(0x3011, 0x3010); // RIGHT BLACK LENTICULAR BRACKET
926         mirrorChars.put(0x3014, 0x3015); // LEFT TORTOISE SHELL BRACKET
927         mirrorChars.put(0x3015, 0x3014); // RIGHT TORTOISE SHELL BRACKET
928         mirrorChars.put(0x3016, 0x3017); // LEFT WHITE LENTICULAR BRACKET
929         mirrorChars.put(0x3017, 0x3016); // RIGHT WHITE LENTICULAR BRACKET
930         mirrorChars.put(0x3018, 0x3019); // LEFT WHITE TORTOISE SHELL BRACKET
931         mirrorChars.put(0x3019, 0x3018); // RIGHT WHITE TORTOISE SHELL BRACKET
932         mirrorChars.put(0x301A, 0x301B); // LEFT WHITE SQUARE BRACKET
933         mirrorChars.put(0x301B, 0x301A); // RIGHT WHITE SQUARE BRACKET
934         mirrorChars.put(0xFF08, 0xFF09); // FULLWIDTH LEFT PARENTHESIS
935         mirrorChars.put(0xFF09, 0xFF08); // FULLWIDTH RIGHT PARENTHESIS
936         mirrorChars.put(0xFF1C, 0xFF1E); // FULLWIDTH LESS-THAN SIGN
937         mirrorChars.put(0xFF1E, 0xFF1C); // FULLWIDTH GREATER-THAN SIGN
938         mirrorChars.put(0xFF3B, 0xFF3D); // FULLWIDTH LEFT SQUARE BRACKET
939         mirrorChars.put(0xFF3D, 0xFF3B); // FULLWIDTH RIGHT SQUARE BRACKET
940         mirrorChars.put(0xFF5B, 0xFF5D); // FULLWIDTH LEFT CURLY BRACKET
941         mirrorChars.put(0xFF5D, 0xFF5B); // FULLWIDTH RIGHT CURLY BRACKET
942         mirrorChars.put(0xFF5F, 0xFF60); // FULLWIDTH LEFT WHITE PARENTHESIS
943         mirrorChars.put(0xFF60, 0xFF5F); // FULLWIDTH RIGHT WHITE PARENTHESIS
944         mirrorChars.put(0xFF62, 0xFF63); // [BEST FIT] HALFWIDTH LEFT CORNER BRACKET
945         mirrorChars.put(0xFF63, 0xFF62); // [BEST FIT] HALFWIDTH RIGHT CORNER BRACKET
946     }
947 }