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