/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.gvt.text;

import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * An attributed character iterator that does the reordering of the characters
 * for bidirectional text. It reorders the characters so they are in visual order.
 * It also assigns a BIDI_LEVEL attribute to each character which can be used
 * to split the reordered ACI into text runs based on direction. ie. characters
 * in a text run will all have the same bidi level.
 *
 * @author <a href="mailto:bella.robinson@cmis.csiro.au">Bella Robinson</a>
 * @version $Id: BidiAttributedCharacterIterator.java,v 1.7 2003/04/11 13:58:47 vhardy Exp $
 */
public class BidiAttributedCharacterIterator implements AttributedCharacterIterator {

    private AttributedCharacterIterator aci;
    private AttributedCharacterIterator reorderedACI;
    private FontRenderContext frc;
    private int chunkStart;
    private int [] newCharOrder;

    private final static Map mirroredGlyphs = new HashMap(50);


    /**
     * Constructs a character iterator that represents the visual display order
     * of characters within bidirectional text.
     *
     * @param aci The character iterator containing the characters in logical
     * order.
     * @param frc The current font render context
     */
    public BidiAttributedCharacterIterator(AttributedCharacterIterator aci, 
                                           FontRenderContext           frc,
                                           int chunkStart) {

        this.frc = frc;
        this.chunkStart = chunkStart;
        aci.first();
        int   numChars    = aci.getEndIndex()-aci.getBeginIndex();
        AttributedString as;

         // Ideally we would do a 'quick' check on chars and
         // attributes to decide if we really need to do bidi or not.
        boolean needsBIDI = true;

        if (false) {
            // Believe it or not this is much slower than the else case
            // but the two are exactly equivilent (including the stripping
            // of null keys/values).
            as = new AttributedString(aci);
        } else {
            StringBuffer strB = new StringBuffer();
            char c = aci.first();
            for (int i = 0; i < numChars; i++) {
                strB.append(c);
                c = aci.next();
            }
            as = new AttributedString(strB.toString());
            int start=aci.getBeginIndex();
            int end  =aci.getEndIndex();
            int index = start;
            while (index < end) {
                aci.setIndex(index);
                Map attrMap = aci.getAttributes();
                int extent  = aci.getRunLimit();
                Map destMap = new HashMap(attrMap.size());
                Iterator i  = attrMap.keySet().iterator();
                while (i.hasNext()) {
                    // Font doesn't like getting attribute sets with
                    // null keys or values so we strip them here.
                    Object key = i.next();
                    if (key == null) continue;
                    Object value = attrMap.get(key);
                    if (value == null) continue;
                    destMap.put(key, value);
                }
                // System.out.println("Run: " + (index-start) + "->" +
                //                    (extent-start) + " of " + numChars);
                as.addAttributes (destMap, index-start, extent-start);
                index = extent;
            }
        }

        // We Just want it to do BIDI for us...
        // In 1.4 we might be able to use the BIDI class...
        TextLayout tl = new TextLayout(as.getIterator(), frc);

        int[] charIndices = new int[numChars];
        int[] charLevels  = new int[numChars];

        int runStart   = 0;
        int currBiDi   = tl.getCharacterLevel(0);
        charIndices[0] = 0;
        charLevels [0] = currBiDi;
        int maxBiDi    = currBiDi;

        for (int i = 1; i < numChars; i++) {
            int newBiDi = tl.getCharacterLevel(i);
            charIndices[i] = i;
            charLevels [i] = newBiDi;

            if (newBiDi != currBiDi) {
                as.addAttribute
                    (GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL,
                     new Integer(currBiDi), runStart, i);
                runStart = i;
                currBiDi  = newBiDi;
                if (newBiDi > maxBiDi) maxBiDi = newBiDi;
            }
        }
        as.addAttribute
            (GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL,
             new Integer(currBiDi), runStart, numChars);

        if ((runStart == 0) && (currBiDi==0)) {
            // This avoids all the mucking about we need to do when
            // bidi is actually performed for cases where it
            // is not actually needed.
            this.aci = this.reorderedACI = as.getIterator();
            newCharOrder = new int[numChars];
            for (int i=0; i<numChars; i++)
                newCharOrder[i] = chunkStart+i;
            return;
        }

        this.aci = as.getIterator();

        //  work out the new character order
        newCharOrder = doBidiReorder(charIndices, charLevels, 
                                           numChars, maxBiDi);

        // construct the string in the new order
        StringBuffer reorderedString = new StringBuffer();
        char c;
        for (int i = 0; i < numChars; i++) {
            c = this.aci.setIndex(newCharOrder[i]);

            // check for mirrored char
            int bidiLevel = tl.getCharacterLevel(newCharOrder[i]);
            if ((bidiLevel & 0x01) != 0) {
                // bidi level is odd so writing dir is right to left
                // So get the mirror version of the char if there
                // is one.
                c = (char)mirrorChar(c);
            }

            reorderedString.append(c);
        }

        // construct the reordered ACI
        AttributedString reorderedAS 
            = new AttributedString(reorderedString.toString());
        Map [] attrs = new Map[numChars];
        int start=this.aci.getBeginIndex();
        int end  =this.aci.getEndIndex();
        int index = start;
        while (index < end) {
            this.aci.setIndex(index);
            Map attrMap = this.aci.getAttributes();
            int extent = this.aci.getRunLimit();
            for (int i=index; i<extent; i++)
                attrs[i-start] = attrMap;
            index = extent;
        }

        runStart=0;
        Map prevAttrMap = attrs[newCharOrder[0]];
        for (int i = 1; i < numChars; i++) {
            Map attrMap = attrs[newCharOrder[i]];
            if (attrMap != prevAttrMap) {
                // Change in attrs set last run...
                reorderedAS.addAttributes(prevAttrMap, runStart, i);
                prevAttrMap = attrMap;
                runStart = i;
            }
        }
        reorderedAS.addAttributes(prevAttrMap, runStart, numChars);

        // transfer any position atttributes to the new first char
        this.aci.first();
        Float x = (Float) this.aci.getAttribute
            (GVTAttributedCharacterIterator.TextAttribute.X);
        Float y = (Float) this.aci.getAttribute
            (GVTAttributedCharacterIterator.TextAttribute.Y);

        if (x != null && !x.isNaN()) {
            reorderedAS.addAttribute
                (GVTAttributedCharacterIterator.TextAttribute.X,
                 new Float(Float.NaN), newCharOrder[0], newCharOrder[0]+1);
            reorderedAS.addAttribute
                (GVTAttributedCharacterIterator.TextAttribute.X, x, 0, 1);
        }
        if (y != null && !y.isNaN()) {
            reorderedAS.addAttribute
                (GVTAttributedCharacterIterator.TextAttribute.Y,
                 new Float(Float.NaN), newCharOrder[0], newCharOrder[0]+1);
            reorderedAS.addAttribute
                (GVTAttributedCharacterIterator.TextAttribute.Y, y, 0, 1);
        }

        // assign arabic form attributes to any arabic chars in the string
        reorderedAS = ArabicTextHandler.assignArabicForms(reorderedAS);

        // Shift the values to match the source text string...
        for (int i=0; i<newCharOrder.length; i++) {
            newCharOrder[i] += chunkStart;
        }
        reorderedACI = reorderedAS.getIterator();
    }

    // Returns an array that give the character index in the source ACI for
    // each character in this ACI.
    public int[] getCharMap() { return newCharOrder; }

    /**
     * Calculates the display order of the characters based on the specified
     * character levels. This method is recursive.
     *
     * @param charIndices An array contianing the original indices of each char.
     * @param charLevels An array containing the current levels of each char.
     * @param numChars The number of chars to reorder.
     *
     * @return An array contianing the reordered character indices.
     */
    private int[] doBidiReorder(int[] charIndices, int[] charLevels, 
                                int numChars, int highestLevel) {
        if (highestLevel == 0) return charIndices;

        // find all groups of chars at the highest level and reverse
        // their order
        int currentIndex = 0;
        while (currentIndex < numChars) {

            // find the first char at the highest index
            while ((currentIndex < numChars) && 
                   (charLevels[currentIndex] < highestLevel)) {
                currentIndex++;
            }
            if (currentIndex == numChars) {
                // have reached the end of the string
                break;
            }
            int startIndex = currentIndex;

            currentIndex++;
            // now find the index where the run at the highestLevel end
            while ((currentIndex < numChars) && 
                   (charLevels[currentIndex] == highestLevel)) {
                currentIndex++;
            }
            int endIndex = currentIndex-1;

            // now reverse the chars between startIndex and endIndex

            // Calculate the middle of the swap region, we include
            // the middle char when region is an odd number of 
            // chars wide so we properly decriment it's charLevel.
            int middle = ((endIndex-startIndex)>>1)+1;
            int tmp;
            for (int i = 0; i<middle; i++) {
                tmp = charIndices[startIndex+i];
                charIndices[startIndex+i] = charIndices[endIndex-i];
                charIndices[endIndex  -i] = tmp;

                charLevels [startIndex+i] = highestLevel-1;
                charLevels [endIndex  -i] = highestLevel-1;
            }
        }
        return doBidiReorder(charIndices, charLevels, numChars, highestLevel-1);
    }



    /**
     * Get the keys of all attributes defined on the iterator's text range.
     */
    public Set getAllAttributeKeys() {
        return reorderedACI.getAllAttributeKeys();
    }

    /**
     * Get the value of the named attribute for the current
     *     character.
     */
    public Object getAttribute(AttributedCharacterIterator.Attribute attribute) {
        return reorderedACI.getAttribute(attribute);
    }

    /**
     * Returns a map with the attributes defined on the current
     * character.
     */
    public Map getAttributes() {
        return reorderedACI.getAttributes();
    }

    /**
     * Get the index of the first character following the
     *     run with respect to all attributes containing the current
     *     character.
     */
    public int getRunLimit() {
        return reorderedACI.getRunLimit();
    }

    /**
     * Get the index of the first character following the
     *      run with respect to the given attribute containing the current
     *      character.
     */
    public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
        return reorderedACI.getRunLimit(attribute);
    }

    /**
     * Get the index of the first character following the
     *     run with respect to the given attributes containing the current
     *     character.
     */
    public int getRunLimit(Set attributes) {
        return reorderedACI.getRunLimit(attributes);
    }

    /**
     * Get the index of the first character of the run with
     *    respect to all attributes containing the current character.
     */
    public int getRunStart() {
        return reorderedACI.getRunStart();
    }

    /**
     * Get the index of the first character of the run with
     *      respect to the given attribute containing the current character.
     * @param attribute The attribute for whose appearance the first offset
     *      is requested.
     */
    public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
        return reorderedACI.getRunStart(attribute);
    }

    /**
     * Get the index of the first character of the run with
     *      respect to the given attributes containing the current character.
     * @param attributes the Set of attributes which begins at the returned index.
     */
    public int getRunStart(Set attributes) {
        return reorderedACI.getRunStart(attributes);
    }

    /**
     * Creates a copy of this iterator.
     */
    public Object clone() {
        return new BidiAttributedCharacterIterator
            ((AttributedCharacterIterator)aci.clone(), frc, chunkStart);
    }

    /**
     * Gets the character at the current position (as returned by getIndex()).
     */
    public char current() {
        return reorderedACI.current();
    }

    /**
     * Sets the position to getBeginIndex() and returns the character at
     * that position.
     */
    public char first() {
        return reorderedACI.first();
    }

    /**
     * Returns the start index of the text.
     */
    public int getBeginIndex() {
        return reorderedACI.getBeginIndex();
    }

    /**
     * Returns the end index of the text.
     */
    public int getEndIndex() {
        return reorderedACI.getEndIndex();
    }

    /**
     * Returns the current index.
     */
    public int getIndex() {
        return reorderedACI.getIndex();
    }

    /**
     * Sets the position to getEndIndex()-1 (getEndIndex() if the text is empty)
     * and returns the character at that position.
     */
    public char last() {
        return reorderedACI.last();
    }

    /**
     * Increments the iterator's index by one and returns the character at
     * the new index.
     */
    public char next() {
        return reorderedACI.next();
    }

    /**
     * Decrements the iterator's index by one and returns the character at the new index.
     */
    public char previous() {
        return reorderedACI.previous();
    }

    /**
     * Sets the position to the specified position in the text and returns that character.
     */
    public char setIndex(int position) {
       return reorderedACI.setIndex(position);
    }


    public static int mirrorChar(int c) {
        switch(c) {
            // set up the mirrored glyph case statement;
        case 0x0028: return 0x0029;  //LEFT PARENTHESIS
        case 0x0029: return 0x0028;  //RIGHT PARENTHESIS
        case 0x003C: return 0x003E;  //LESS-THAN SIGN
        case 0x003E: return 0x003C;  //GREATER-THAN SIGN
        case 0x005B: return 0x005D;  //LEFT SQUARE BRACKET
        case 0x005D: return 0x005B;  //RIGHT SQUARE BRACKET
        case 0x007B: return 0x007D;  //LEFT CURLY BRACKET
        case 0x007D: return 0x007B;  //RIGHT CURLY BRACKET
        case 0x00AB: return 0x00BB;  //LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
        case 0x00BB: return 0x00AB;  //RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
        case 0x2039: return 0x203A;  //SINGLE LEFT-POINTING ANGLE QUOTATION MARK
        case 0x203A: return 0x2039;  //SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
        case 0x2045: return 0x2046;  //LEFT SQUARE BRACKET WITH QUILL
        case 0x2046: return 0x2045;  //RIGHT SQUARE BRACKET WITH QUILL
        case 0x207D: return 0x207E;  //SUPERSCRIPT LEFT PARENTHESIS
        case 0x207E: return 0x207D;  //SUPERSCRIPT RIGHT PARENTHESIS
        case 0x208D: return 0x208E;  //SUBSCRIPT LEFT PARENTHESIS
        case 0x208E: return 0x208D;  //SUBSCRIPT RIGHT PARENTHESIS
        case 0x2208: return 0x220B;  //ELEMENT OF
        case 0x2209: return 0x220C;  //NOT AN ELEMENT OF
        case 0x220A: return 0x220D;  //SMALL ELEMENT OF
        case 0x220B: return 0x2208;  //CONTAINS AS MEMBER
        case 0x220C: return 0x2209;  //DOES NOT CONTAIN AS MEMBER
        case 0x220D: return 0x220A;  //SMALL CONTAINS AS MEMBER
        case 0x223C: return 0x223D;  //TILDE OPERATOR
        case 0x223D: return 0x223C;  //REVERSED TILDE
        case 0x2243: return 0x22CD;  //ASYMPTOTICALLY EQUAL TO
        case 0x2252: return 0x2253;  //APPROXIMATELY EQUAL TO OR THE IMAGE OF
        case 0x2253: return 0x2252;  //IMAGE OF OR APPROXIMATELY EQUAL TO
        case 0x2254: return 0x2255;  //COLON EQUALS
        case 0x2255: return 0x2254;  //EQUALS COLON
        case 0x2264: return 0x2265;  //LESS-THAN OR EQUAL TO
        case 0x2265: return 0x2264;  //GREATER-THAN OR EQUAL TO
        case 0x2266: return 0x2267;  //LESS-THAN OVER EQUAL TO
        case 0x2267: return 0x2266;  //GREATER-THAN OVER EQUAL TO
        case 0x2268: return 0x2269;  //[BEST FIT] LESS-THAN BUT NOT EQUAL TO
        case 0x2269: return 0x2268;  //[BEST FIT] GREATER-THAN BUT NOT EQUAL TO
        case 0x226A: return 0x226B;  //MUCH LESS-THAN
        case 0x226B: return 0x226A;  //MUCH GREATER-THAN
        case 0x226E: return 0x226F;  //[BEST FIT] NOT LESS-THAN
        case 0x226F: return 0x226E;  //[BEST FIT] NOT GREATER-THAN
        case 0x2270: return 0x2271;  //[BEST FIT] NEITHER LESS-THAN NOR EQUAL TO
        case 0x2271: return 0x2270;  //[BEST FIT] NEITHER GREATER-THAN NOR EQUAL TO
        case 0x2272: return 0x2273;  //[BEST FIT] LESS-THAN OR EQUIVALENT TO
        case 0x2273: return 0x2272;  //[BEST FIT] GREATER-THAN OR EQUIVALENT TO
        case 0x2274: return 0x2275;  //[BEST FIT] NEITHER LESS-THAN NOR EQUIVALENT TO
        case 0x2275: return 0x2274;  //[BEST FIT] NEITHER GREATER-THAN NOR EQUIVALENT TO
        case 0x2276: return 0x2277;  //LESS-THAN OR GREATER-THAN
        case 0x2277: return 0x2276;  //GREATER-THAN OR LESS-THAN
        case 0x2278: return 0x2279;  //NEITHER LESS-THAN NOR GREATER-THAN
        case 0x2279: return 0x2278;  //NEITHER GREATER-THAN NOR LESS-THAN
        case 0x227A: return 0x227B;  //PRECEDES
        case 0x227B: return 0x227A;  //SUCCEEDS
        case 0x227C: return 0x227D;  //PRECEDES OR EQUAL TO
        case 0x227D: return 0x227C;  //SUCCEEDS OR EQUAL TO
        case 0x227E: return 0x227F;  //[BEST FIT] PRECEDES OR EQUIVALENT TO
        case 0x227F: return 0x227E;  //[BEST FIT] SUCCEEDS OR EQUIVALENT TO
        case 0x2280: return 0x2281;  //[BEST FIT] DOES NOT PRECEDE
        case 0x2281: return 0x2280;  //[BEST FIT] DOES NOT SUCCEED
        case 0x2282: return 0x2283;  //SUBSET OF
        case 0x2283: return 0x2282;  //SUPERSET OF
        case 0x2284: return 0x2285;  //[BEST FIT] NOT A SUBSET OF
        case 0x2285: return 0x2284;  //[BEST FIT] NOT A SUPERSET OF
        case 0x2286: return 0x2287;  //SUBSET OF OR EQUAL TO
        case 0x2287: return 0x2286;  //SUPERSET OF OR EQUAL TO
        case 0x2288: return 0x2289;  //[BEST FIT] NEITHER A SUBSET OF NOR EQUAL TO
        case 0x2289: return 0x2288;  //[BEST FIT] NEITHER A SUPERSET OF NOR EQUAL TO
        case 0x228A: return 0x228B;  //[BEST FIT] SUBSET OF WITH NOT EQUAL TO
        case 0x228B: return 0x228A;  //[BEST FIT] SUPERSET OF WITH NOT EQUAL TO
        case 0x228F: return 0x2290;  //SQUARE IMAGE OF
        case 0x2290: return 0x228F;  //SQUARE ORIGINAL OF
        case 0x2291: return 0x2292;  //SQUARE IMAGE OF OR EQUAL TO
        case 0x2292: return 0x2291;  //SQUARE ORIGINAL OF OR EQUAL TO
        case 0x22A2: return 0x22A3;  //RIGHT TACK
        case 0x22A3: return 0x22A2;  //LEFT TACK
        case 0x22B0: return 0x22B1;  //PRECEDES UNDER RELATION
        case 0x22B1: return 0x22B0;  //SUCCEEDS UNDER RELATION
        case 0x22B2: return 0x22B3;  //NORMAL SUBGROUP OF
        case 0x22B3: return 0x22B2;  //CONTAINS AS NORMAL SUBGROUP
        case 0x22B4: return 0x22B5;  //NORMAL SUBGROUP OF OR EQUAL TO
        case 0x22B5: return 0x22B4;  //CONTAINS AS NORMAL SUBGROUP OR EQUAL TO
        case 0x22B6: return 0x22B7;  //ORIGINAL OF
        case 0x22B7: return 0x22B6;  //IMAGE OF
        case 0x22C9: return 0x22CA;  //LEFT NORMAL FACTOR SEMIDIRECT PRODUCT
        case 0x22CA: return 0x22C9;  //RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT
        case 0x22CB: return 0x22CC;  //LEFT SEMIDIRECT PRODUCT
        case 0x22CC: return 0x22CB;  //RIGHT SEMIDIRECT PRODUCT
        case 0x22CD: return 0x2243;  //REVERSED TILDE EQUALS
        case 0x22D0: return 0x22D1;  //DOUBLE SUBSET
        case 0x22D1: return 0x22D0;  //DOUBLE SUPERSET
        case 0x22D6: return 0x22D7;  //LESS-THAN WITH DOT
        case 0x22D7: return 0x22D6;  //GREATER-THAN WITH DOT
        case 0x22D8: return 0x22D9;  //VERY MUCH LESS-THAN
        case 0x22D9: return 0x22D8;  //VERY MUCH GREATER-THAN
        case 0x22DA: return 0x22DB;  //LESS-THAN EQUAL TO OR GREATER-THAN
        case 0x22DB: return 0x22DA;  //GREATER-THAN EQUAL TO OR LESS-THAN
        case 0x22DC: return 0x22DD;  //EQUAL TO OR LESS-THAN
        case 0x22DD: return 0x22DC;  //EQUAL TO OR GREATER-THAN
        case 0x22DE: return 0x22DF;  //EQUAL TO OR PRECEDES
        case 0x22DF: return 0x22DE;  //EQUAL TO OR SUCCEEDS
        case 0x22E0: return 0x22E1;  //[BEST FIT] DOES NOT PRECEDE OR EQUAL
        case 0x22E1: return 0x22E0;  //[BEST FIT] DOES NOT SUCCEED OR EQUAL
        case 0x22E2: return 0x22E3;  //[BEST FIT] NOT SQUARE IMAGE OF OR EQUAL TO
        case 0x22E3: return 0x22E2;  //[BEST FIT] NOT SQUARE ORIGINAL OF OR EQUAL TO
        case 0x22E4: return 0x22E5;  //[BEST FIT] SQUARE IMAGE OF OR NOT EQUAL TO
        case 0x22E5: return 0x22E4;  //[BEST FIT] SQUARE ORIGINAL OF OR NOT EQUAL TO
        case 0x22E6: return 0x22E7;  //[BEST FIT] LESS-THAN BUT NOT EQUIVALENT TO
        case 0x22E7: return 0x22E6;  //[BEST FIT] GREATER-THAN BUT NOT EQUIVALENT TO
        case 0x22E8: return 0x22E9;  //[BEST FIT] PRECEDES BUT NOT EQUIVALENT TO
        case 0x22E9: return 0x22E8;  //[BEST FIT] SUCCEEDS BUT NOT EQUIVALENT TO
        case 0x22EA: return 0x22EB;  //[BEST FIT] NOT NORMAL SUBGROUP OF
        case 0x22EB: return 0x22EA;  //[BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP
        case 0x22EC: return 0x22ED;  //[BEST FIT] NOT NORMAL SUBGROUP OF OR EQUAL TO
        case 0x22ED: return 0x22EC;  //[BEST FIT] DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL
        case 0x22F0: return 0x22F1;  //UP RIGHT DIAGONAL ELLIPSIS
        case 0x22F1: return 0x22F0;  //DOWN RIGHT DIAGONAL ELLIPSIS
        case 0x2308: return 0x2309;  //LEFT CEILING
        case 0x2309: return 0x2308;  //RIGHT CEILING
        case 0x230A: return 0x230B;  //LEFT FLOOR
        case 0x230B: return 0x230A;  //RIGHT FLOOR
        case 0x2329: return 0x232A;  //LEFT-POINTING ANGLE BRACKET
        case 0x232A: return 0x2329;  //RIGHT-POINTING ANGLE BRACKET
        case 0x3008: return 0x3009;  //LEFT ANGLE BRACKET
        case 0x3009: return 0x3008;  //RIGHT ANGLE BRACKET
        case 0x300A: return 0x300B;  //LEFT DOUBLE ANGLE BRACKET
        case 0x300B: return 0x300A;  //RIGHT DOUBLE ANGLE BRACKET
        case 0x300C: return 0x300D;  //[BEST FIT] LEFT CORNER BRACKET
        case 0x300D: return 0x300C;  //[BEST FIT] RIGHT CORNER BRACKET
        case 0x300E: return 0x300F;  //[BEST FIT] LEFT WHITE CORNER BRACKET
        case 0x300F: return 0x300E;  //[BEST FIT] RIGHT WHITE CORNER BRACKET
        case 0x3010: return 0x3011;  //LEFT BLACK LENTICULAR BRACKET
        case 0x3011: return 0x3010;  //RIGHT BLACK LENTICULAR BRACKET
        case 0x3014: return 0x3015;  //[BEST FIT] LEFT TORTOISE SHELL BRACKET
        case 0x3015: return 0x3014;  //[BEST FIT] RIGHT TORTOISE SHELL BRACKET
        case 0x3016: return 0x3017;  //LEFT WHITE LENTICULAR BRACKET
        case 0x3017: return 0x3016;  //RIGHT WHITE LENTICULAR BRACKET
        case 0x3018: return 0x3019;  //LEFT WHITE TORTOISE SHELL BRACKET
        case 0x3019: return 0x3018;  //RIGHT WHITE TORTOISE SHELL BRACKET
        case 0x301A: return 0x301B;  //LEFT WHITE SQUARE BRACKET
        case 0x301B: return 0x301A;  //RIGHT WHITE SQUARE BRACKET
        default: break;
        }
        return  c;
    }
}
