Open Source Repository

Home /itextpdf/itextpdf-5.1.2 | Repository Home



com/itextpdf/text/pdf/BidiLine.java
/*
 * $Id: BidiLine.java 4784 2011-03-15 08:33:00Z blowagie $
 *
 * This file is part of the iText (R) project.
 * Copyright (c) 1998-2011 1T3XT BVBA
 * Authors: Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: [email protected]
 */
package com.itextpdf.text.pdf;

import java.util.ArrayList;

import com.itextpdf.text.Chunk;
import com.itextpdf.text.Image;
import com.itextpdf.text.Utilities;

/** Does all the line bidirectional processing with PdfChunk assembly.
 *
 @author Paulo Soares
 */
public class BidiLine {

    protected int runDirection;
    protected int pieceSize = 256;
    protected char text[] new char[pieceSize];
    protected PdfChunk detailChunks[] new PdfChunk[pieceSize];
    protected int totalTextLength = 0;

    protected byte orderLevels[] new byte[pieceSize];
    protected int indexChars[] new int[pieceSize];

    protected ArrayList<PdfChunk> chunks = new ArrayList<PdfChunk>();
    protected int indexChunk = 0;
    protected int indexChunkChar = 0;
    protected int currentChar = 0;

    protected int storedRunDirection;
    protected char storedText[] new char[0];
    protected PdfChunk storedDetailChunks[] new PdfChunk[0];
    protected int storedTotalTextLength = 0;

    protected byte storedOrderLevels[] new byte[0];
    protected int storedIndexChars[] new int[0];

    protected int storedIndexChunk = 0;
    protected int storedIndexChunkChar = 0;
    protected int storedCurrentChar = 0;

    protected boolean shortStore;
//    protected ArabicShaping arabic = new ArabicShaping(ArabicShaping.LETTERS_SHAPE | ArabicShaping.LENGTH_GROW_SHRINK | ArabicShaping.TEXT_DIRECTION_LOGICAL);
    protected static final IntHashtable mirrorChars = new IntHashtable();
    protected int arabicOptions;

    /** Creates new BidiLine */
    public BidiLine() {
    }

    public BidiLine(BidiLine org) {
        runDirection = org.runDirection;
        pieceSize = org.pieceSize;
        text = org.text.clone();
        detailChunks = org.detailChunks.clone();
        totalTextLength = org.totalTextLength;

        orderLevels = org.orderLevels.clone();
        indexChars = org.indexChars.clone();

        chunks = new ArrayList<PdfChunk>(org.chunks);
        indexChunk = org.indexChunk;
        indexChunkChar = org.indexChunkChar;
        currentChar = org.currentChar;

        storedRunDirection = org.storedRunDirection;
        storedText = org.storedText.clone();
        storedDetailChunks = org.storedDetailChunks.clone();
        storedTotalTextLength = org.storedTotalTextLength;

        storedOrderLevels = org.storedOrderLevels.clone();
        storedIndexChars = org.storedIndexChars.clone();

        storedIndexChunk = org.storedIndexChunk;
        storedIndexChunkChar = org.storedIndexChunkChar;
        storedCurrentChar = org.storedCurrentChar;

        shortStore = org.shortStore;
        arabicOptions = org.arabicOptions;
    }

    public boolean isEmpty() {
        return currentChar >= totalTextLength && indexChunk >= chunks.size();
    }

    public void clearChunks() {
        chunks.clear();
        totalTextLength = 0;
        currentChar = 0;
    }

    public boolean getParagraph(int runDirection) {
        this.runDirection = runDirection;
        currentChar = 0;
        totalTextLength = 0;
        boolean hasText = false;
        char c;
        char uniC;
        BaseFont bf;
        for (; indexChunk < chunks.size(); ++indexChunk) {
            PdfChunk ck = chunks.get(indexChunk);
            bf = ck.font().getFont();
            String s = ck.toString();
            int len = s.length();
            for (; indexChunkChar < len; ++indexChunkChar) {
                c = s.charAt(indexChunkChar);
                uniC = (char)bf.getUnicodeEquivalent(c);
                if (uniC == '\r' || uniC == '\n') {
                    // next condition is never true for CID
                    if (uniC == '\r' && indexChunkChar + < len && s.charAt(indexChunkChar + 1== '\n')
                        ++indexChunkChar;
                    ++indexChunkChar;
                    if (indexChunkChar >= len) {
                        indexChunkChar = 0;
                        ++indexChunk;
                    }
                    hasText = true;
                    if (totalTextLength == 0)
                        detailChunks[0= ck;
                    break;
                }
                addPiece(c, ck);
            }
            if (hasText)
                break;
            indexChunkChar = 0;
        }
        if (totalTextLength == 0)
            return hasText;

        // remove trailing WS
        totalTextLength = trimRight(0, totalTextLength - 11;
        if (totalTextLength == 0) {
          return true;
        }

        if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) {
            if (orderLevels.length < totalTextLength) {
                orderLevels = new byte[pieceSize];
                indexChars = new int[pieceSize];
            }
            ArabicLigaturizer.processNumbers(text, 0, totalTextLength, arabicOptions);
            BidiOrder order = new BidiOrder(text, 0, totalTextLength, (byte)(runDirection == PdfWriter.RUN_DIRECTION_RTL ? 0));
            byte od[] = order.getLevels();
            for (int k = 0; k < totalTextLength; ++k) {
                orderLevels[k= od[k];
                indexChars[k= k;
            }
            doArabicShapping();
            mirrorGlyphs();
        }
        totalTextLength = trimRightEx(0, totalTextLength - 11;
        return true;
    }

    public void addChunk(PdfChunk chunk) {
        chunks.add(chunk);
    }

    public void addChunks(ArrayList<PdfChunk> chunks) {
        this.chunks.addAll(chunks);
    }

    public void addPiece(char c, PdfChunk chunk) {
        if (totalTextLength >= pieceSize) {
            char tempText[] = text;
            PdfChunk tempDetailChunks[] = detailChunks;
            pieceSize *= 2;
            text = new char[pieceSize];
            detailChunks = new PdfChunk[pieceSize];
            System.arraycopy(tempText, 0, text, 0, totalTextLength);
            System.arraycopy(tempDetailChunks, 0, detailChunks, 0, totalTextLength);
        }
        text[totalTextLength= c;
        detailChunks[totalTextLength++= chunk;
    }

    public void save() {
        if (indexChunk > 0) {
            if (indexChunk >= chunks.size())
                chunks.clear();
            else {
                for (--indexChunk; indexChunk >= 0; --indexChunk)
                    chunks.remove(indexChunk);
            }
            indexChunk = 0;
        }
        storedRunDirection = runDirection;
        storedTotalTextLength = totalTextLength;
        storedIndexChunk = indexChunk;
        storedIndexChunkChar = indexChunkChar;
        storedCurrentChar = currentChar;
        shortStore = currentChar < totalTextLength;
        if (!shortStore) {
            // long save
            if (storedText.length < totalTextLength) {
                storedText = new char[totalTextLength];
                storedDetailChunks = new PdfChunk[totalTextLength];
            }
            System.arraycopy(text, 0, storedText, 0, totalTextLength);
            System.arraycopy(detailChunks, 0, storedDetailChunks, 0, totalTextLength);
        }
        if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) {
            if (storedOrderLevels.length < totalTextLength) {
                storedOrderLevels = new byte[totalTextLength];
                storedIndexChars = new int[totalTextLength];
            }
            System.arraycopy(orderLevels, currentChar, storedOrderLevels, currentChar, totalTextLength - currentChar);
            System.arraycopy(indexChars, currentChar, storedIndexChars, currentChar, totalTextLength - currentChar);
        }
    }

    public void restore() {
        runDirection = storedRunDirection;
        totalTextLength = storedTotalTextLength;
        indexChunk = storedIndexChunk;
        indexChunkChar = storedIndexChunkChar;
        currentChar = storedCurrentChar;
        if (!shortStore) {
            // long restore
            System.arraycopy(storedText, 0, text, 0, totalTextLength);
            System.arraycopy(storedDetailChunks, 0, detailChunks, 0, totalTextLength);
        }
        if (runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL) {
            System.arraycopy(storedOrderLevels, currentChar, orderLevels, currentChar, totalTextLength - currentChar);
            System.arraycopy(storedIndexChars, currentChar, indexChars, currentChar, totalTextLength - currentChar);
        }
    }

    public void mirrorGlyphs() {
        for (int k = 0; k < totalTextLength; ++k) {
            if ((orderLevels[k1== 1) {
                int mirror = mirrorChars.get(text[k]);
                if (mirror != 0)
                    text[k(char)mirror;
            }
        }
    }

    public void doArabicShapping() {
        int src = 0;
        int dest = 0;
        for (;;) {
            while (src < totalTextLength) {
                char c = text[src];
                if (c >= 0x0600 && c <= 0x06ff)
                    break;
                if (src != dest) {
                    text[dest= text[src];
                    detailChunks[dest= detailChunks[src];
                    orderLevels[dest= orderLevels[src];
                }
                ++src;
                ++dest;
            }
            if (src >= totalTextLength) {
                totalTextLength = dest;
                return;
            }
            int startArabicIdx = src;
            ++src;
            while (src < totalTextLength) {
                char c = text[src];
                if (c < 0x0600 || c > 0x06ff)
                    break;
                ++src;
            }
            int arabicWordSize = src - startArabicIdx;
            int size = ArabicLigaturizer.arabic_shape(text, startArabicIdx, arabicWordSize, text, dest, arabicWordSize, arabicOptions);
            if (startArabicIdx != dest) {
                for (int k = 0; k < size; ++k) {
                    detailChunks[dest= detailChunks[startArabicIdx];
                    orderLevels[dest++= orderLevels[startArabicIdx++];
                }
            }
            else
                dest += size;
        }
    }

    public PdfLine processLine(float leftX, float width, int alignment, int runDirection, int arabicOptions) {
        this.arabicOptions = arabicOptions;
        save();
        boolean isRTL = runDirection == PdfWriter.RUN_DIRECTION_RTL;
        if (currentChar >= totalTextLength) {
            boolean hasText = getParagraph(runDirection);
            if (!hasText)
                return null;
            if (totalTextLength == 0) {
                ArrayList<PdfChunk> ar = new ArrayList<PdfChunk>();
                PdfChunk ck = new PdfChunk("", detailChunks[0]);
                ar.add(ck);
                return new PdfLine(000, alignment, true, ar, isRTL);
            }
        }
        float originalWidth = width;
        int lastSplit = -1;
        if (currentChar != 0)
            currentChar = trimLeftEx(currentChar, totalTextLength - 1);
        int oldCurrentChar = currentChar;
        int uniC = 0;
        PdfChunk ck = null;
        float charWidth = 0;
        PdfChunk lastValidChunk = null;
        boolean splitChar = false;
        boolean surrogate = false;
        for (; currentChar < totalTextLength; ++currentChar) {
            ck = detailChunks[currentChar];
            surrogate = Utilities.isSurrogatePair(text, currentChar);
            if (surrogate)
                uniC = ck.getUnicodeEquivalent(Utilities.convertToUtf32(text, currentChar));
            else
                uniC = ck.getUnicodeEquivalent(text[currentChar]);
            if (PdfChunk.noPrint(uniC))
                continue;
            if (surrogate)
                charWidth = ck.getCharWidth(uniC);
            else
                charWidth = ck.getCharWidth(text[currentChar]);
            splitChar = ck.isExtSplitCharacter(oldCurrentChar, currentChar, totalTextLength, text, detailChunks);
            if (splitChar && Character.isWhitespace((char)uniC))
                lastSplit = currentChar;
            if (width - charWidth < 0) {
              // If the chunk is an image and it is the first one in line, check if resize requested
              // If so, resize to fit the current line width
              if (lastValidChunk == null && ck.isImage()) {
                Image img = ck.getImage();
                if (img.isScaleToFitLineWhenOverflow()) {
                  float scalePercent = width / img.getWidth() 100;
                  img.scalePercent(scalePercent);
                  charWidth = width;
                }
              }
            }
            if (width - charWidth < 0)
                break;
            if (splitChar)
                lastSplit = currentChar;
            width -= charWidth;
          lastValidChunk = ck;
            if (ck.isTab()) {
              Object[] tab = (Object[])ck.getAttribute(Chunk.TAB);
            float tabPosition = ((Float)tab[1]).floatValue();
            boolean newLine = ((Boolean)tab[2]).booleanValue();
            if (newLine && tabPosition < originalWidth - width) {
              return new PdfLine(0, originalWidth, width, alignment, true, createArrayOfPdfChunks(oldCurrentChar, currentChar - 1), isRTL);
            }
            detailChunks[currentChar].adjustLeft(leftX);
            width = originalWidth - tabPosition;
            }
            if (surrogate)
                ++currentChar;
        }
        if (lastValidChunk == null) {
            // not even a single char fit; must output the first char
            ++currentChar;
            if (surrogate)
                ++currentChar;
            return new PdfLine(0, originalWidth, 0, alignment, false, createArrayOfPdfChunks(currentChar - 1, currentChar - 1), isRTL);
        }
        if (currentChar >= totalTextLength) {
            // there was more line than text
            return new PdfLine(0, originalWidth, width, alignment, true, createArrayOfPdfChunks(oldCurrentChar, totalTextLength - 1), isRTL);
        }
        int newCurrentChar = trimRightEx(oldCurrentChar, currentChar - 1);
        if (newCurrentChar < oldCurrentChar) {
            // only WS
            return new PdfLine(0, originalWidth, width, alignment, false, createArrayOfPdfChunks(oldCurrentChar, currentChar - 1), isRTL);
        }
        if (newCurrentChar == currentChar - 1) { // middle of word
            HyphenationEvent he = (HyphenationEvent)lastValidChunk.getAttribute(Chunk.HYPHENATION);
            if (he != null) {
                int word[] = getWord(oldCurrentChar, newCurrentChar);
                if (word != null) {
                    float testWidth = width + getWidth(word[0], currentChar - 1);
                    String pre = he.getHyphenatedWordPre(new String(text, word[0], word[1- word[0]), lastValidChunk.font().getFont(), lastValidChunk.font().size(), testWidth);
                    String post = he.getHyphenatedWordPost();
                    if (pre.length() 0) {
                        PdfChunk extra = new PdfChunk(pre, lastValidChunk);
                        currentChar = word[1- post.length();
                        return new PdfLine(0, originalWidth, testWidth - lastValidChunk.font().width(pre), alignment, false, createArrayOfPdfChunks(oldCurrentChar, word[01, extra), isRTL);
                    }
                }
            }
        }
        if (lastSplit == -|| lastSplit >= newCurrentChar) {
            // no split point or split point ahead of end
            return new PdfLine(0, originalWidth, width + getWidth(newCurrentChar + 1, currentChar - 1), alignment, false, createArrayOfPdfChunks(oldCurrentChar, newCurrentChar), isRTL);
        }
        // standard split
        currentChar = lastSplit + 1;
        newCurrentChar = trimRightEx(oldCurrentChar, lastSplit);
        if (newCurrentChar < oldCurrentChar) {
            // only WS again
            newCurrentChar = currentChar - 1;
        }
        return new PdfLine(0, originalWidth, originalWidth - getWidth(oldCurrentChar, newCurrentChar), alignment, false, createArrayOfPdfChunks(oldCurrentChar, newCurrentChar), isRTL);
    }

    /** Gets the width of a range of characters.
     @param startIdx the first index to calculate
     @param lastIdx the last inclusive index to calculate
     @return the sum of all widths
     */
    public float getWidth(int startIdx, int lastIdx) {
        char c = 0;
        PdfChunk ck = null;
        float width = 0;
        for (; startIdx <= lastIdx; ++startIdx) {
            boolean surrogate = Utilities.isSurrogatePair(text, startIdx);
            if (surrogate) {
                width += detailChunks[startIdx].getCharWidth(Utilities.convertToUtf32(text, startIdx));
                ++startIdx;
            }
            else {
                c = text[startIdx];
                ck = detailChunks[startIdx];
                if (PdfChunk.noPrint(ck.getUnicodeEquivalent(c)))
                    continue;
                width += detailChunks[startIdx].getCharWidth(c);
            }
        }
        return width;
    }

    public ArrayList<PdfChunk> createArrayOfPdfChunks(int startIdx, int endIdx) {
        return createArrayOfPdfChunks(startIdx, endIdx, null);
    }

    public ArrayList<PdfChunk> createArrayOfPdfChunks(int startIdx, int endIdx, PdfChunk extraPdfChunk) {
        boolean bidi = runDirection == PdfWriter.RUN_DIRECTION_LTR || runDirection == PdfWriter.RUN_DIRECTION_RTL;
        if (bidi)
            reorder(startIdx, endIdx);
        ArrayList<PdfChunk> ar = new ArrayList<PdfChunk>();
        PdfChunk refCk = detailChunks[startIdx];
        PdfChunk ck = null;
        StringBuffer buf = new StringBuffer();
        char c;
        int idx = 0;
        for (; startIdx <= endIdx; ++startIdx) {
            idx = bidi ? indexChars[startIdx: startIdx;
            c = text[idx];
            ck = detailChunks[idx];
            if (PdfChunk.noPrint(ck.getUnicodeEquivalent(c)))
                continue;
            if (ck.isImage() || ck.isSeparator() || ck.isTab()) {
                if (buf.length() 0) {
                    ar.add(new PdfChunk(buf.toString(), refCk));
                    buf = new StringBuffer();
                }
                ar.add(ck);
            }
            else if (ck == refCk) {
                buf.append(c);
            }
            else {
                if (buf.length() 0) {
                    ar.add(new PdfChunk(buf.toString(), refCk));
                    buf = new StringBuffer();
                }
                if (!ck.isImage() && !ck.isSeparator() && !ck.isTab())
                    buf.append(c);
                refCk = ck;
            }
        }
        if (buf.length() 0) {
            ar.add(new PdfChunk(buf.toString(), refCk));
        }
        if (extraPdfChunk != null)
            ar.add(extraPdfChunk);
        return ar;
    }

    public int[] getWord(int startIdx, int idx) {
        int last = idx;
        int first = idx;
        // forward
        for (; last < totalTextLength; ++last) {
            if (!Character.isLetter(text[last]))
                break;
        }
        if (last == idx)
            return null;
        // backward
        for (; first >= startIdx; --first) {
            if (!Character.isLetter(text[first]))
                break;
        }
        ++first;
        return new int[]{first, last};
    }

    public int trimRight(int startIdx, int endIdx) {
        int idx = endIdx;
        char c;
        for (; idx >= startIdx; --idx) {
            c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]);
            if (!isWS(c))
                break;
        }
        return idx;
    }

    public int trimLeft(int startIdx, int endIdx) {
        int idx = startIdx;
        char c;
        for (; idx <= endIdx; ++idx) {
            c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]);
            if (!isWS(c))
                break;
        }
        return idx;
    }

    public int trimRightEx(int startIdx, int endIdx) {
        int idx = endIdx;
        char c = 0;
        for (; idx >= startIdx; --idx) {
            c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]);
            if (!isWS(c&& !PdfChunk.noPrint(c))
                break;
        }
        return idx;
    }

    public int trimLeftEx(int startIdx, int endIdx) {
        int idx = startIdx;
        char c = 0;
        for (; idx <= endIdx; ++idx) {
            c = (char)detailChunks[idx].getUnicodeEquivalent(text[idx]);
            if (!isWS(c&& !PdfChunk.noPrint(c))
                break;
        }
        return idx;
    }

    public void reorder(int start, int end) {
        byte maxLevel = orderLevels[start];
        byte minLevel = maxLevel;
        byte onlyOddLevels = maxLevel;
        byte onlyEvenLevels = maxLevel;
        for (int k = start + 1; k <= end; ++k) {
            byte b = orderLevels[k];
            if (b > maxLevel)
                maxLevel = b;
            else if (b < minLevel)
                minLevel = b;
            onlyOddLevels &= b;
            onlyEvenLevels |= b;
        }
        if ((onlyEvenLevels & 1== 0// nothing to do
            return;
        if ((onlyOddLevels & 1== 1) { // single inversion
            flip(start, end + 1);
            return;
        }
        minLevel |= 1;
        for (; maxLevel >= minLevel; --maxLevel) {
            int pstart = start;
            for (;;) {
                for (;pstart <= end; ++pstart) {
                    if (orderLevels[pstart>= maxLevel)
                        break;
                }
                if (pstart > end)
                    break;
                int pend = pstart + 1;
                for (; pend <= end; ++pend) {
                    if (orderLevels[pend< maxLevel)
                        break;
                }
                flip(pstart, pend);
                pstart = pend + 1;
            }
        }
    }

    public void flip(int start, int end) {
        int mid = (start + end2;
        --end;
        for (; start < mid; ++start, --end) {
            int temp = indexChars[start];
            indexChars[start= indexChars[end];
            indexChars[end= temp;
        }
    }

    public static boolean isWS(char c) {
        return c <= ' ';
    }

    static {
        mirrorChars.put(0x00280x0029)// LEFT PARENTHESIS
        mirrorChars.put(0x00290x0028)// RIGHT PARENTHESIS
        mirrorChars.put(0x003C0x003E)// LESS-THAN SIGN
        mirrorChars.put(0x003E0x003C)// GREATER-THAN SIGN
        mirrorChars.put(0x005B0x005D)// LEFT SQUARE BRACKET
        mirrorChars.put(0x005D0x005B)// RIGHT SQUARE BRACKET
        mirrorChars.put(0x007B0x007D)// LEFT CURLY BRACKET
        mirrorChars.put(0x007D0x007B)// RIGHT CURLY BRACKET
        mirrorChars.put(0x00AB0x00BB)// LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
        mirrorChars.put(0x00BB0x00AB)// RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
        mirrorChars.put(0x20390x203A)// SINGLE LEFT-POINTING ANGLE QUOTATION MARK
        mirrorChars.put(0x203A0x2039)// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
        mirrorChars.put(0x20450x2046)// LEFT SQUARE BRACKET WITH QUILL
        mirrorChars.put(0x20460x2045)// RIGHT SQUARE BRACKET WITH QUILL
        mirrorChars.put(0x207D0x207E)// SUPERSCRIPT LEFT PARENTHESIS
        mirrorChars.put(0x207E0x207D)// SUPERSCRIPT RIGHT PARENTHESIS
        mirrorChars.put(0x208D0x208E)// SUBSCRIPT LEFT PARENTHESIS
        mirrorChars.put(0x208E0x208D)// SUBSCRIPT RIGHT PARENTHESIS
        mirrorChars.put(0x22080x220B)// ELEMENT OF
        mirrorChars.put(0x22090x220C)// NOT AN ELEMENT OF
        mirrorChars.put(0x220A0x220D)// SMALL ELEMENT OF
        mirrorChars.put(0x220B0x2208)// CONTAINS AS MEMBER
        mirrorChars.put(0x220C0x2209)// DOES NOT CONTAIN AS MEMBER
        mirrorChars.put(0x220D0x220A)// SMALL CONTAINS AS MEMBER
        mirrorChars.put(0x22150x29F5)// DIVISION SLASH
        mirrorChars.put(0x223C0x223D)// TILDE OPERATOR
        mirrorChars.put(0x223D0x223C)// REVERSED TILDE
        mirrorChars.put(0x22430x22CD)// ASYMPTOTICALLY EQUAL TO
        mirrorChars.put(0x22520x2253)// APPROXIMATELY EQUAL TO OR THE IMAGE OF
        mirrorChars.put(0x22530x2252)// IMAGE OF OR APPROXIMATELY EQUAL TO
        mirrorChars.put(0x22540x2255)// COLON EQUALS
        mirrorChars.put(0x22550x2254)// EQUALS COLON
        mirrorChars.put(0x22640x2265)// LESS-THAN OR EQUAL TO
        mirrorChars.put(0x22650x2264)// GREATER-THAN OR EQUAL TO
        mirrorChars.put(0x22660x2267)// LESS-THAN OVER EQUAL TO
        mirrorChars.put(0x22670x2266)// GREATER-THAN OVER EQUAL TO
        mirrorChars.put(0x22680x2269)// [BEST FIT] LESS-THAN BUT NOT EQUAL TO
        mirrorChars.put(0x22690x2268)// [BEST FIT] GREATER-THAN BUT NOT EQUAL TO
        mirrorChars.put(0x226A0x226B)// MUCH LESS-THAN
        mirrorChars.put(0x226B0x226A)// MUCH GREATER-THAN
        mirrorChars.put(0x226E0x226F)// [BEST FIT] NOT LESS-THAN
        mirrorChars.put(0x226F0x226E)// [BEST FIT] NOT GREATER-THAN
        mirrorChars.put(0x22700x2271)// [BEST FIT] NEITHER LESS-THAN NOR EQUAL TO
        mirrorChars.put(0x22710x2270)// [BEST FIT] NEITHER GREATER-THAN NOR EQUAL TO
        mirrorChars.put(0x22720x2273)// [BEST FIT] LESS-THAN OR EQUIVALENT TO
        mirrorChars.put(0x22730x2272)// [BEST FIT] GREATER-THAN OR EQUIVALENT TO
        mirrorChars.put(0x22740x2275)// [BEST FIT] NEITHER LESS-THAN NOR EQUIVALENT TO
        mirrorChars.put(0x22750x2274)// [BEST FIT] NEITHER GREATER-THAN NOR EQUIVALENT TO
        mirrorChars.put(0x22760x2277)// LESS-THAN OR GREATER-THAN
        mirrorChars.put(0x22770x2276)// GREATER-THAN OR LESS-THAN
        mirrorChars.put(0x22780x2279)// NEITHER LESS-THAN NOR GREATER-THAN
        mirrorChars.put(0x22790x2278)// NEITHER GREATER-THAN NOR LESS-THAN
        mirrorChars.put(0x227A0x227B)// PRECEDES
        mirrorChars.put(0x227B0x227A)// SUCCEEDS
        mirrorChars.put(0x227C0x227D)// PRECEDES OR EQUAL TO
        mirrorChars.put(0x227D0x227C)// SUCCEEDS OR EQUAL TO
        mirrorChars.put(0x227E0x227F)// [BEST FIT] PRECEDES OR EQUIVALENT TO
        mirrorChars.put(0x227F0x227E)// [BEST FIT] SUCCEEDS OR EQUIVALENT TO
        mirrorChars.put(0x22800x2281)// [BEST FIT] DOES NOT PRECEDE
        mirrorChars.put(0x22810x2280)// [BEST FIT] DOES NOT SUCCEED
        mirrorChars.put(0x22820x2283)// SUBSET OF
        mirrorChars.put(0x22830x2282)// SUPERSET OF
        mirrorChars.put(0x22840x2285)// [BEST FIT] NOT A SUBSET OF
        mirrorChars.put(0x22850x2284)// [BEST FIT] NOT A SUPERSET OF
        mirrorChars.put(0x22860x2287)// SUBSET OF OR EQUAL TO
        mirrorChars.put(0x22870x2286)// SUPERSET OF OR EQUAL TO
        mirrorChars.put(0x22880x2289)// [BEST FIT] NEITHER A SUBSET OF NOR EQUAL TO
        mirrorChars.put(0x22890x2288)// [BEST FIT] NEITHER A SUPERSET OF NOR EQUAL TO
        mirrorChars.put(0x228A0x228B)// [BEST FIT] SUBSET OF WITH NOT EQUAL TO
        mirrorChars.put(0x228B0x228A)// [BEST FIT] SUPERSET OF WITH NOT EQUAL TO
        mirrorChars.put(0x228F0x2290)// SQUARE IMAGE OF
        mirrorChars.put(0x22900x228F)// SQUARE ORIGINAL OF
        mirrorChars.put(0x22910x2292)// SQUARE IMAGE OF OR EQUAL TO
        mirrorChars.put(0x22920x2291)// SQUARE ORIGINAL OF OR EQUAL TO
        mirrorChars.put(0x22980x29B8)// CIRCLED DIVISION SLASH
        mirrorChars.put(0x22A20x22A3)// RIGHT TACK
        mirrorChars.put(0x22A30x22A2)// LEFT TACK
        mirrorChars.put(0x22A60x2ADE)// ASSERTION
        mirrorChars.put(0x22A80x2AE4)// TRUE
        mirrorChars.put(0x22A90x2AE3)// FORCES
        mirrorChars.put(0x22AB0x2AE5)// DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE
        mirrorChars.put(0x22B00x22B1)// PRECEDES UNDER RELATION
        mirrorChars.put(0x22B10x22B0)// SUCCEEDS UNDER RELATION
        mirrorChars.put(0x22B20x22B3)// NORMAL SUBGROUP OF
        mirrorChars.put(0x22B30x22B2)// CONTAINS AS NORMAL SUBGROUP
        mirrorChars.put(0x22B40x22B5)// NORMAL SUBGROUP OF OR EQUAL TO
        mirrorChars.put(0x22B50x22B4)// CONTAINS AS NORMAL SUBGROUP OR EQUAL TO
        mirrorChars.put(0x22B60x22B7)// ORIGINAL OF
        mirrorChars.put(0x22B70x22B6)// IMAGE OF
        mirrorChars.put(0x22C90x22CA)// LEFT NORMAL FACTOR SEMIDIRECT PRODUCT
        mirrorChars.put(0x22CA0x22C9)// RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT
        mirrorChars.put(0x22CB0x22CC)// LEFT SEMIDIRECT PRODUCT
        mirrorChars.put(0x22CC0x22CB)// RIGHT SEMIDIRECT PRODUCT
        mirrorChars.put(0x22CD0x2243)// REVERSED TILDE EQUALS
        mirrorChars.put(0x22D00x22D1)// DOUBLE SUBSET
        mirrorChars.put(0x22D10x22D0)// DOUBLE SUPERSET
        mirrorChars.put(0x22D60x22D7)// LESS-THAN WITH DOT
        mirrorChars.put(0x22D70x22D6)// GREATER-THAN WITH DOT
        mirrorChars.put(0x22D80x22D9)// VERY MUCH LESS-THAN
        mirrorChars.put(0x22D90x22D8)// VERY MUCH GREATER-THAN
        mirrorChars.put(0x22DA0x22DB)// LESS-THAN EQUAL TO OR GREATER-THAN
        mirrorChars.put(0x22DB0x22DA)// GREATER-THAN EQUAL TO OR LESS-THAN
        mirrorChars.put(0x22DC0x22DD)// EQUAL TO OR LESS-THAN
        mirrorChars.put(0x22DD0x22DC)// EQUAL TO OR GREATER-THAN
        mirrorChars.put(0x22DE0x22DF)// EQUAL TO OR PRECEDES
        mirrorChars.put(0x22DF0x22DE)// EQUAL TO OR SUCCEEDS
        mirrorChars.put(0x22E00x22E1)// [BEST FIT] DOES NOT PRECEDE OR EQUAL
        mirrorChars.put(0x22E10x22E0)// [BEST FIT] DOES NOT SUCCEED OR EQUAL
        mirrorChars.put(0x22E20x22E3)// [BEST FIT] NOT SQUARE IMAGE OF OR EQUAL TO
        mirrorChars.put(0x22E30x22E2)// [BEST FIT] NOT SQUARE ORIGINAL OF OR EQUAL TO
        mirrorChars.put(0x22E40x22E5)// [BEST FIT] SQUARE IMAGE OF OR NOT EQUAL TO
        mirrorChars.put(0x22E50x22E4)// [BEST FIT] SQUARE ORIGINAL OF OR NOT EQUAL TO
        mirrorChars.put(0x22E60x22E7)// [BEST FIT] LESS-THAN BUT NOT EQUIVALENT TO
        mirrorChars.put(0x22E70x22E6)// [BEST FIT] GREATER-THAN BUT NOT EQUIVALENT TO
        mirrorChars.put(0x22E80x22E9)// [BEST FIT] PRECEDES BUT NOT EQUIVALENT TO
        mirrorChars.put(0x22E90x22E8)// [BEST FIT] SUCCEEDS BUT NOT EQUIVALENT TO
        mirrorChars.put(0x22EA0x22EB)// [BEST FIT] NOT NORMAL SUBGROUP OF
        mirrorChars.put(0x22EB0x22EA)// [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP
        mirrorChars.put(0x22EC0x22ED)// [BEST FIT] NOT NORMAL SUBGROUP OF OR EQUAL TO
        mirrorChars.put(0x22ED0x22EC)// [BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL
        mirrorChars.put(0x22F00x22F1)// UP RIGHT DIAGONAL ELLIPSIS
        mirrorChars.put(0x22F10x22F0)// DOWN RIGHT DIAGONAL ELLIPSIS
        mirrorChars.put(0x22F20x22FA)// ELEMENT OF WITH LONG HORIZONTAL STROKE
        mirrorChars.put(0x22F30x22FB)// ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
        mirrorChars.put(0x22F40x22FC)// SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
        mirrorChars.put(0x22F60x22FD)// ELEMENT OF WITH OVERBAR
        mirrorChars.put(0x22F70x22FE)// SMALL ELEMENT OF WITH OVERBAR
        mirrorChars.put(0x22FA0x22F2)// CONTAINS WITH LONG HORIZONTAL STROKE
        mirrorChars.put(0x22FB0x22F3)// CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
        mirrorChars.put(0x22FC0x22F4)// SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE
        mirrorChars.put(0x22FD0x22F6)// CONTAINS WITH OVERBAR
        mirrorChars.put(0x22FE0x22F7)// SMALL CONTAINS WITH OVERBAR
        mirrorChars.put(0x23080x2309)// LEFT CEILING
        mirrorChars.put(0x23090x2308)// RIGHT CEILING
        mirrorChars.put(0x230A0x230B)// LEFT FLOOR
        mirrorChars.put(0x230B0x230A)// RIGHT FLOOR
        mirrorChars.put(0x23290x232A)// LEFT-POINTING ANGLE BRACKET
        mirrorChars.put(0x232A0x2329)// RIGHT-POINTING ANGLE BRACKET
        mirrorChars.put(0x27680x2769)// MEDIUM LEFT PARENTHESIS ORNAMENT
        mirrorChars.put(0x27690x2768)// MEDIUM RIGHT PARENTHESIS ORNAMENT
        mirrorChars.put(0x276A0x276B)// MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT
        mirrorChars.put(0x276B0x276A)// MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT
        mirrorChars.put(0x276C0x276D)// MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT
        mirrorChars.put(0x276D0x276C)// MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT
        mirrorChars.put(0x276E0x276F)// HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT
        mirrorChars.put(0x276F0x276E)// HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT
        mirrorChars.put(0x27700x2771)// HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT
        mirrorChars.put(0x27710x2770)// HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT
        mirrorChars.put(0x27720x2773)// LIGHT LEFT TORTOISE SHELL BRACKET
        mirrorChars.put(0x27730x2772)// LIGHT RIGHT TORTOISE SHELL BRACKET
        mirrorChars.put(0x27740x2775)// MEDIUM LEFT CURLY BRACKET ORNAMENT
        mirrorChars.put(0x27750x2774)// MEDIUM RIGHT CURLY BRACKET ORNAMENT
        mirrorChars.put(0x27D50x27D6)// LEFT OUTER JOIN
        mirrorChars.put(0x27D60x27D5)// RIGHT OUTER JOIN
        mirrorChars.put(0x27DD0x27DE)// LONG RIGHT TACK
        mirrorChars.put(0x27DE0x27DD)// LONG LEFT TACK
        mirrorChars.put(0x27E20x27E3)// WHITE CONCAVE-SIDED DIAMOND WITH LEFTWARDS TICK
        mirrorChars.put(0x27E30x27E2)// WHITE CONCAVE-SIDED DIAMOND WITH RIGHTWARDS TICK
        mirrorChars.put(0x27E40x27E5)// WHITE SQUARE WITH LEFTWARDS TICK
        mirrorChars.put(0x27E50x27E4)// WHITE SQUARE WITH RIGHTWARDS TICK
        mirrorChars.put(0x27E60x27E7)// MATHEMATICAL LEFT WHITE SQUARE BRACKET
        mirrorChars.put(0x27E70x27E6)// MATHEMATICAL RIGHT WHITE SQUARE BRACKET
        mirrorChars.put(0x27E80x27E9)// MATHEMATICAL LEFT ANGLE BRACKET
        mirrorChars.put(0x27E90x27E8)// MATHEMATICAL RIGHT ANGLE BRACKET
        mirrorChars.put(0x27EA0x27EB)// MATHEMATICAL LEFT DOUBLE ANGLE BRACKET
        mirrorChars.put(0x27EB0x27EA)// MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET
        mirrorChars.put(0x29830x2984)// LEFT WHITE CURLY BRACKET
        mirrorChars.put(0x29840x2983)// RIGHT WHITE CURLY BRACKET
        mirrorChars.put(0x29850x2986)// LEFT WHITE PARENTHESIS
        mirrorChars.put(0x29860x2985)// RIGHT WHITE PARENTHESIS
        mirrorChars.put(0x29870x2988)// Z NOTATION LEFT IMAGE BRACKET
        mirrorChars.put(0x29880x2987)// Z NOTATION RIGHT IMAGE BRACKET
        mirrorChars.put(0x29890x298A)// Z NOTATION LEFT BINDING BRACKET
        mirrorChars.put(0x298A0x2989)// Z NOTATION RIGHT BINDING BRACKET
        mirrorChars.put(0x298B0x298C)// LEFT SQUARE BRACKET WITH UNDERBAR
        mirrorChars.put(0x298C0x298B)// RIGHT SQUARE BRACKET WITH UNDERBAR
        mirrorChars.put(0x298D0x2990)// LEFT SQUARE BRACKET WITH TICK IN TOP CORNER
        mirrorChars.put(0x298E0x298F)// RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
        mirrorChars.put(0x298F0x298E)// LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER
        mirrorChars.put(0x29900x298D)// RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER
        mirrorChars.put(0x29910x2992)// LEFT ANGLE BRACKET WITH DOT
        mirrorChars.put(0x29920x2991)// RIGHT ANGLE BRACKET WITH DOT
        mirrorChars.put(0x29930x2994)// LEFT ARC LESS-THAN BRACKET
        mirrorChars.put(0x29940x2993)// RIGHT ARC GREATER-THAN BRACKET
        mirrorChars.put(0x29950x2996)// DOUBLE LEFT ARC GREATER-THAN BRACKET
        mirrorChars.put(0x29960x2995)// DOUBLE RIGHT ARC LESS-THAN BRACKET
        mirrorChars.put(0x29970x2998)// LEFT BLACK TORTOISE SHELL BRACKET
        mirrorChars.put(0x29980x2997)// RIGHT BLACK TORTOISE SHELL BRACKET
        mirrorChars.put(0x29B80x2298)// CIRCLED REVERSE SOLIDUS
        mirrorChars.put(0x29C00x29C1)// CIRCLED LESS-THAN
        mirrorChars.put(0x29C10x29C0)// CIRCLED GREATER-THAN
        mirrorChars.put(0x29C40x29C5)// SQUARED RISING DIAGONAL SLASH
        mirrorChars.put(0x29C50x29C4)// SQUARED FALLING DIAGONAL SLASH
        mirrorChars.put(0x29CF0x29D0)// LEFT TRIANGLE BESIDE VERTICAL BAR
        mirrorChars.put(0x29D00x29CF)// VERTICAL BAR BESIDE RIGHT TRIANGLE
        mirrorChars.put(0x29D10x29D2)// BOWTIE WITH LEFT HALF BLACK
        mirrorChars.put(0x29D20x29D1)// BOWTIE WITH RIGHT HALF BLACK
        mirrorChars.put(0x29D40x29D5)// TIMES WITH LEFT HALF BLACK
        mirrorChars.put(0x29D50x29D4)// TIMES WITH RIGHT HALF BLACK
        mirrorChars.put(0x29D80x29D9)// LEFT WIGGLY FENCE
        mirrorChars.put(0x29D90x29D8)// RIGHT WIGGLY FENCE
        mirrorChars.put(0x29DA0x29DB)// LEFT DOUBLE WIGGLY FENCE
        mirrorChars.put(0x29DB0x29DA)// RIGHT DOUBLE WIGGLY FENCE
        mirrorChars.put(0x29F50x2215)// REVERSE SOLIDUS OPERATOR
        mirrorChars.put(0x29F80x29F9)// BIG SOLIDUS
        mirrorChars.put(0x29F90x29F8)// BIG REVERSE SOLIDUS
        mirrorChars.put(0x29FC0x29FD)// LEFT-POINTING CURVED ANGLE BRACKET
        mirrorChars.put(0x29FD0x29FC)// RIGHT-POINTING CURVED ANGLE BRACKET
        mirrorChars.put(0x2A2B0x2A2C)// MINUS SIGN WITH FALLING DOTS
        mirrorChars.put(0x2A2C0x2A2B)// MINUS SIGN WITH RISING DOTS
        mirrorChars.put(0x2A2D0x2A2C)// PLUS SIGN IN LEFT HALF CIRCLE
        mirrorChars.put(0x2A2E0x2A2D)// PLUS SIGN IN RIGHT HALF CIRCLE
        mirrorChars.put(0x2A340x2A35)// MULTIPLICATION SIGN IN LEFT HALF CIRCLE
        mirrorChars.put(0x2A350x2A34)// MULTIPLICATION SIGN IN RIGHT HALF CIRCLE
        mirrorChars.put(0x2A3C0x2A3D)// INTERIOR PRODUCT
        mirrorChars.put(0x2A3D0x2A3C)// RIGHTHAND INTERIOR PRODUCT
        mirrorChars.put(0x2A640x2A65)// Z NOTATION DOMAIN ANTIRESTRICTION
        mirrorChars.put(0x2A650x2A64)// Z NOTATION RANGE ANTIRESTRICTION
        mirrorChars.put(0x2A790x2A7A)// LESS-THAN WITH CIRCLE INSIDE
        mirrorChars.put(0x2A7A0x2A79)// GREATER-THAN WITH CIRCLE INSIDE
        mirrorChars.put(0x2A7D0x2A7E)// LESS-THAN OR SLANTED EQUAL TO
        mirrorChars.put(0x2A7E0x2A7D)// GREATER-THAN OR SLANTED EQUAL TO
        mirrorChars.put(0x2A7F0x2A80)// LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE
        mirrorChars.put(0x2A800x2A7F)// GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE
        mirrorChars.put(0x2A810x2A82)// LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE
        mirrorChars.put(0x2A820x2A81)// GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE
        mirrorChars.put(0x2A830x2A84)// LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT
        mirrorChars.put(0x2A840x2A83)// GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT
        mirrorChars.put(0x2A8B0x2A8C)// LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN
        mirrorChars.put(0x2A8C0x2A8B)// GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN
        mirrorChars.put(0x2A910x2A92)// LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL
        mirrorChars.put(0x2A920x2A91)// GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL
        mirrorChars.put(0x2A930x2A94)// LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL
        mirrorChars.put(0x2A940x2A93)// GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL
        mirrorChars.put(0x2A950x2A96)// SLANTED EQUAL TO OR LESS-THAN
        mirrorChars.put(0x2A960x2A95)// SLANTED EQUAL TO OR GREATER-THAN
        mirrorChars.put(0x2A970x2A98)// SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE
        mirrorChars.put(0x2A980x2A97)// SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE
        mirrorChars.put(0x2A990x2A9A)// DOUBLE-LINE EQUAL TO OR LESS-THAN
        mirrorChars.put(0x2A9A0x2A99)// DOUBLE-LINE EQUAL TO OR GREATER-THAN
        mirrorChars.put(0x2A9B0x2A9C)// DOUBLE-LINE SLANTED EQUAL TO OR LESS-THAN
        mirrorChars.put(0x2A9C0x2A9B)// DOUBLE-LINE SLANTED EQUAL TO OR GREATER-THAN
        mirrorChars.put(0x2AA10x2AA2)// DOUBLE NESTED LESS-THAN
        mirrorChars.put(0x2AA20x2AA1)// DOUBLE NESTED GREATER-THAN
        mirrorChars.put(0x2AA60x2AA7)// LESS-THAN CLOSED BY CURVE
        mirrorChars.put(0x2AA70x2AA6)// GREATER-THAN CLOSED BY CURVE
        mirrorChars.put(0x2AA80x2AA9)// LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL
        mirrorChars.put(0x2AA90x2AA8)// GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL
        mirrorChars.put(0x2AAA0x2AAB)// SMALLER THAN
        mirrorChars.put(0x2AAB0x2AAA)// LARGER THAN
        mirrorChars.put(0x2AAC0x2AAD)// SMALLER THAN OR EQUAL TO
        mirrorChars.put(0x2AAD0x2AAC)// LARGER THAN OR EQUAL TO
        mirrorChars.put(0x2AAF0x2AB0)// PRECEDES ABOVE SINGLE-LINE EQUALS SIGN
        mirrorChars.put(0x2AB00x2AAF)// SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN
        mirrorChars.put(0x2AB30x2AB4)// PRECEDES ABOVE EQUALS SIGN
        mirrorChars.put(0x2AB40x2AB3)// SUCCEEDS ABOVE EQUALS SIGN
        mirrorChars.put(0x2ABB0x2ABC)// DOUBLE PRECEDES
        mirrorChars.put(0x2ABC0x2ABB)// DOUBLE SUCCEEDS
        mirrorChars.put(0x2ABD0x2ABE)// SUBSET WITH DOT
        mirrorChars.put(0x2ABE0x2ABD)// SUPERSET WITH DOT
        mirrorChars.put(0x2ABF0x2AC0)// SUBSET WITH PLUS SIGN BELOW
        mirrorChars.put(0x2AC00x2ABF)// SUPERSET WITH PLUS SIGN BELOW
        mirrorChars.put(0x2AC10x2AC2)// SUBSET WITH MULTIPLICATION SIGN BELOW
        mirrorChars.put(0x2AC20x2AC1)// SUPERSET WITH MULTIPLICATION SIGN BELOW
        mirrorChars.put(0x2AC30x2AC4)// SUBSET OF OR EQUAL TO WITH DOT ABOVE
        mirrorChars.put(0x2AC40x2AC3)// SUPERSET OF OR EQUAL TO WITH DOT ABOVE
        mirrorChars.put(0x2AC50x2AC6)// SUBSET OF ABOVE EQUALS SIGN
        mirrorChars.put(0x2AC60x2AC5)// SUPERSET OF ABOVE EQUALS SIGN
        mirrorChars.put(0x2ACD0x2ACE)// SQUARE LEFT OPEN BOX OPERATOR
        mirrorChars.put(0x2ACE0x2ACD)// SQUARE RIGHT OPEN BOX OPERATOR
        mirrorChars.put(0x2ACF0x2AD0)// CLOSED SUBSET
        mirrorChars.put(0x2AD00x2ACF)// CLOSED SUPERSET
        mirrorChars.put(0x2AD10x2AD2)// CLOSED SUBSET OR EQUAL TO
        mirrorChars.put(0x2AD20x2AD1)// CLOSED SUPERSET OR EQUAL TO
        mirrorChars.put(0x2AD30x2AD4)// SUBSET ABOVE SUPERSET
        mirrorChars.put(0x2AD40x2AD3)// SUPERSET ABOVE SUBSET
        mirrorChars.put(0x2AD50x2AD6)// SUBSET ABOVE SUBSET
        mirrorChars.put(0x2AD60x2AD5)// SUPERSET ABOVE SUPERSET
        mirrorChars.put(0x2ADE0x22A6)// SHORT LEFT TACK
        mirrorChars.put(0x2AE30x22A9)// DOUBLE VERTICAL BAR LEFT TURNSTILE
        mirrorChars.put(0x2AE40x22A8)// VERTICAL BAR DOUBLE LEFT TURNSTILE
        mirrorChars.put(0x2AE50x22AB)// DOUBLE VERTICAL BAR DOUBLE LEFT TURNSTILE
        mirrorChars.put(0x2AEC0x2AED)// DOUBLE STROKE NOT SIGN
        mirrorChars.put(0x2AED0x2AEC)// REVERSED DOUBLE STROKE NOT SIGN
        mirrorChars.put(0x2AF70x2AF8)// TRIPLE NESTED LESS-THAN
        mirrorChars.put(0x2AF80x2AF7)// TRIPLE NESTED GREATER-THAN
        mirrorChars.put(0x2AF90x2AFA)// DOUBLE-LINE SLANTED LESS-THAN OR EQUAL TO
        mirrorChars.put(0x2AFA0x2AF9)// DOUBLE-LINE SLANTED GREATER-THAN OR EQUAL TO
        mirrorChars.put(0x30080x3009)// LEFT ANGLE BRACKET
        mirrorChars.put(0x30090x3008)// RIGHT ANGLE BRACKET
        mirrorChars.put(0x300A0x300B)// LEFT DOUBLE ANGLE BRACKET
        mirrorChars.put(0x300B0x300A)// RIGHT DOUBLE ANGLE BRACKET
        mirrorChars.put(0x300C0x300D)// [BEST FIT] LEFT CORNER BRACKET
        mirrorChars.put(0x300D0x300C)// [BEST FIT] RIGHT CORNER BRACKET
        mirrorChars.put(0x300E0x300F)// [BEST FIT] LEFT WHITE CORNER BRACKET
        mirrorChars.put(0x300F0x300E)// [BEST FIT] RIGHT WHITE CORNER BRACKET
        mirrorChars.put(0x30100x3011)// LEFT BLACK LENTICULAR BRACKET
        mirrorChars.put(0x30110x3010)// RIGHT BLACK LENTICULAR BRACKET
        mirrorChars.put(0x30140x3015)// LEFT TORTOISE SHELL BRACKET
        mirrorChars.put(0x30150x3014)// RIGHT TORTOISE SHELL BRACKET
        mirrorChars.put(0x30160x3017)// LEFT WHITE LENTICULAR BRACKET
        mirrorChars.put(0x30170x3016)// RIGHT WHITE LENTICULAR BRACKET
        mirrorChars.put(0x30180x3019)// LEFT WHITE TORTOISE SHELL BRACKET
        mirrorChars.put(0x30190x3018)// RIGHT WHITE TORTOISE SHELL BRACKET
        mirrorChars.put(0x301A0x301B)// LEFT WHITE SQUARE BRACKET
        mirrorChars.put(0x301B0x301A)// RIGHT WHITE SQUARE BRACKET
        mirrorChars.put(0xFF080xFF09)// FULLWIDTH LEFT PARENTHESIS
        mirrorChars.put(0xFF090xFF08)// FULLWIDTH RIGHT PARENTHESIS
        mirrorChars.put(0xFF1C0xFF1E)// FULLWIDTH LESS-THAN SIGN
        mirrorChars.put(0xFF1E0xFF1C)// FULLWIDTH GREATER-THAN SIGN
        mirrorChars.put(0xFF3B0xFF3D)// FULLWIDTH LEFT SQUARE BRACKET
        mirrorChars.put(0xFF3D0xFF3B)// FULLWIDTH RIGHT SQUARE BRACKET
        mirrorChars.put(0xFF5B0xFF5D)// FULLWIDTH LEFT CURLY BRACKET
        mirrorChars.put(0xFF5D0xFF5B)// FULLWIDTH RIGHT CURLY BRACKET
        mirrorChars.put(0xFF5F0xFF60)// FULLWIDTH LEFT WHITE PARENTHESIS
        mirrorChars.put(0xFF600xFF5F)// FULLWIDTH RIGHT WHITE PARENTHESIS
        mirrorChars.put(0xFF620xFF63)// [BEST FIT] HALFWIDTH LEFT CORNER BRACKET
        mirrorChars.put(0xFF630xFF62)// [BEST FIT] HALFWIDTH RIGHT CORNER BRACKET
    }
}